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/58] 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/58] 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/58] 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/58] 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/58] 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/58] 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 4a7254fd35ab51393364b4eb9cf88086bfb0546c Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 19:06:48 +0200 Subject: [PATCH 07/58] Load StartMenuHelper from System32 as fallback --- .../StartMenuDLL/Win11StartButton.cpp | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp index 663d9fbe2..f1c975207 100644 --- a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp +++ b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp @@ -24,29 +24,36 @@ static bool LoadStartButtonTap( void ) if (g_StartButtonTapModule) return true; - wchar_t path[MAX_PATH]; - DWORD pathLength = GetModuleFileName(g_Instance, path, _countof(path)); - if (!pathLength || pathLength >= _countof(path)) - return false; - - wchar_t *name = wcsrchr(path, L'\\'); - if (!name) - return false; - name++; - #ifdef _WIN64 const wchar_t helperName[] = L"StartMenuHelper64.dll"; #else const wchar_t helperName[] = L"StartMenuHelper32.dll"; #endif - const size_t remaining = path + _countof(path) - name; - if (_countof(helperName) > remaining) - return false; - wcscpy_s(name, remaining, helperName); + // Prefer a helper next to StartMenuDLL for local development builds, but + // installed Open-Shell keeps StartMenuHelper in System32. + HMODULE module = NULL; + wchar_t path[MAX_PATH]; + DWORD pathLength = GetModuleFileName(g_Instance, path, _countof(path)); + if (pathLength && pathLength < _countof(path)) + { + wchar_t *name = wcsrchr(path, L'\\'); + if (name) + { + name++; + const size_t remaining = path + _countof(path) - name; + if (_countof(helperName) <= remaining) + { + wcscpy_s(name, remaining, helperName); + module = LoadLibraryEx(path, NULL, + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); + } + } + } + + if (!module) + module = LoadLibraryEx(helperName, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - HMODULE module = LoadLibraryEx(path, NULL, - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); if (!module) { LogToFile(STARTUP_LOG, L"Win11StartButton: unable to load TAP helper 0x%08X", GetLastError()); From 422fe0349cac177ce9711644e782eba469d00310 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 19:06:51 +0200 Subject: [PATCH 08/58] Avoid GetCurrentTime macro conflict in XAML headers --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 6770e6431..13a7dc5d6 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -14,6 +14,7 @@ #include "StringUtils.h" #include "..\StartMenuDLL\LogManager.h" +#undef GetCurrentTime #include #include #include From 0a769cde58b68721e99e0580556f275382e99f5a Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:14:08 +0200 Subject: [PATCH 09/58] Simplify full-path StartMenuHelper loading --- Src/StartMenu/StartMenuDLL/Win11StartButton.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp index f1c975207..5f03a52ba 100644 --- a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp +++ b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp @@ -45,8 +45,7 @@ static bool LoadStartButtonTap( void ) if (_countof(helperName) <= remaining) { wcscpy_s(name, remaining, helperName); - module = LoadLibraryEx(path, NULL, - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); + module = LoadLibrary(path); } } } From c3c353bbb6a9df530651c4bdb1bb65b04a91975a Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:14:11 +0200 Subject: [PATCH 10/58] Use in-class initialization for StartElement --- .../StartMenuHelper/Win11StartButtonTap.cpp | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 13a7dc5d6..1ce348939 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -41,24 +41,14 @@ static HMODULE GetThisModule( void ) struct StartElement { - InstanceHandle parent; + InstanceHandle parent = 0; 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; - } + bool visibilityOverride = false; + bool hitTestOverride = false; + bool startControlResolved = false; + bool isStartControl = false; + unsigned int discoveryOrder = 0; }; class CWin11StartButtonTap; From 2bce76c7a9d5871b7aa98808a3f641ef6e398478 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:29:14 +0200 Subject: [PATCH 11/58] Modernize Win11 TAP synchronization and COM lifetime --- .../StartMenuHelper/Win11StartButtonTap.cpp | 244 ++++++++---------- 1 file changed, 114 insertions(+), 130 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 1ce348939..deaf407c8 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -18,7 +18,11 @@ #include #include #include +#include +#include +#include #include +#include #include static const GUID CLSID_OpenShellStartButtonTap = @@ -26,10 +30,10 @@ static const GUID CLSID_OpenShellStartButtonTap = 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 std::atomic_bool g_StartButtonActive{ false }; +static std::atomic_bool g_StartButtonEnabled{ false }; +static std::atomic_bool g_AllTaskbars{ false }; +static std::atomic_bool g_ConnectStarted{ false }; static HMODULE GetThisModule( void ) { @@ -53,7 +57,7 @@ struct StartElement class CWin11StartButtonTap; static CWin11StartButtonTap *g_Tap = NULL; -static SRWLOCK g_TapLock = SRWLOCK_INIT; +static std::shared_mutex g_TapMutex; static bool ContainsText( const CString &text, const wchar_t *part ) { @@ -85,29 +89,28 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal 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 ) { + { + std::unique_lock lock(g_TapMutex); + if (g_Tap == this) + g_Tap = NULL; + } + 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); + { + HWND dispatch = m_Dispatch; + m_Dispatch = NULL; + SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); + DestroyWindow(dispatch); + } - InterlockedExchange(&g_ConnectStarted, 0); - DeleteCriticalSection(&m_Lock); + g_ConnectStarted.store(false); _AtlModule.Unlock(); } @@ -130,15 +133,15 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal STDMETHODIMP_(ULONG) AddRef( void ) { - return (ULONG)InterlockedIncrement(&m_Refs); + return ++m_Refs; } STDMETHODIMP_(ULONG) Release( void ) { - LONG refs = InterlockedDecrement(&m_Refs); + ULONG refs = --m_Refs; if (!refs) delete this; - return (ULONG)refs; + return refs; } STDMETHODIMP SetSite( IUnknown *site ) @@ -154,22 +157,25 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } m_Advised = false; } - m_Visual.Release(); - m_Site.Release(); - if (!site) { - AcquireSRWLockExclusive(&g_TapLock); + std::unique_lock lock(g_TapMutex); if (g_Tap == this) g_Tap = NULL; - ReleaseSRWLockExclusive(&g_TapLock); + } + + m_Visual.Release(); + m_Site.Release(); - EnterCriticalSection(&m_Lock); + { + std::lock_guard lock(m_Mutex); m_Elements.clear(); m_PrimaryStart = 0; m_NextDiscoveryOrder = 0; - LeaveCriticalSection(&m_Lock); + } + if (!site) + { if (m_Dispatch && GetWindowThreadProcessId(m_Dispatch, NULL) == GetCurrentThreadId()) { HWND dispatch = m_Dispatch; @@ -178,57 +184,29 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal DestroyWindow(dispatch); } - InterlockedExchange(&g_ConnectStarted, 0); + g_ConnectStarted.store(false); return S_OK; } - EnterCriticalSection(&m_Lock); - m_Elements.clear(); - m_PrimaryStart = 0; - m_NextDiscoveryOrder = 0; - LeaveCriticalSection(&m_Lock); - - m_Site = site; + CComPtr newSite = site; + CComPtr newVisual; // 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); + // the lifetime of the diagnostics session. Build the new COM state in + // locals first, and publish it only after the subscription succeeds. + HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&newVisual); + if (FAILED(hr)) return hr; - } + if (!newVisual) + return E_NOINTERFACE; if (!CreateDispatchWindow()) - { - DWORD error = GetLastError(); - m_Visual.Release(); - m_Site.Release(); - InterlockedExchange(&g_ConnectStarted, 0); - return HRESULT_FROM_WIN32(error); - } + return HRESULT_FROM_WIN32(GetLastError()); // 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 + hr = newVisual->AdviseVisualTreeChange(static_cast(this)); + if (FAILED(hr)) { if (m_Dispatch) { @@ -237,10 +215,20 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal 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; + } + + m_Site = std::move(newSite); + m_Visual = std::move(newVisual); + m_Advised = true; + + { + std::unique_lock lock(g_TapMutex); + g_Tap = this; } + + RequestApply(false); LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree advise 0x%08X", hr); return hr; } @@ -261,7 +249,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal // the BSTR fields; copy the values we need but never free callback input. bool interesting = false; - EnterCriticalSection(&m_Lock); + { + std::lock_guard lock(m_Mutex); if (mutationType == Remove) { auto it = m_Elements.find(element.Handle); @@ -300,7 +289,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal interesting = IsStartControlCandidate(record) || IsStartGlyph(record) || IsUnderStartButtonLocked(record.parent); } - LeaveCriticalSection(&m_Lock); + } if (interesting) RequestApply(false); @@ -345,8 +334,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } if (msg == WM_OS_STARTBUTTON_APPLY && tap) { - bool enabled = InterlockedCompareExchange(&g_StartButtonActive, 0, 0) != 0 && - InterlockedCompareExchange(&g_StartButtonEnabled, 0, 0) != 0; + bool enabled = g_StartButtonActive.load() && g_StartButtonEnabled.load(); tap->ApplyState(enabled); return 0; } @@ -393,7 +381,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal InstanceHandle GetStartAncestor( InstanceHandle handle ) { InstanceHandle result = 0; - EnterCriticalSection(&m_Lock); + { + std::lock_guard lock(m_Mutex); auto it = m_Elements.find(handle); if (it != m_Elements.end()) { @@ -411,7 +400,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal parent = pit->second.parent; } } - LeaveCriticalSection(&m_Lock); + } return result; } @@ -505,7 +494,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal void SetControlClassification( InstanceHandle handle, bool isStartControl ) { - EnterCriticalSection(&m_Lock); + { + std::lock_guard lock(m_Mutex); auto it = m_Elements.find(handle); if (it != m_Elements.end()) { @@ -513,7 +503,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal it->second.isStartControl = isStartControl; m_PrimaryStart = FindPrimaryStartLocked(); } - LeaveCriticalSection(&m_Lock); + } } HRESULT FindProperty( InstanceHandle handle, const wchar_t *name, unsigned int *index, CString *typeName ) @@ -575,7 +565,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal void SetOverrideFlags( InstanceHandle handle, bool *visibility, bool *hitTest ) { - EnterCriticalSection(&m_Lock); + { + std::lock_guard lock(m_Mutex); auto it = m_Elements.find(handle); if (it != m_Elements.end()) { @@ -584,7 +575,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (hitTest) it->second.hitTestOverride = *hitTest; } - LeaveCriticalSection(&m_Lock); + } } void ApplyState( bool enabled ) @@ -593,10 +584,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return; std::vector> elements; - EnterCriticalSection(&m_Lock); + { + std::lock_guard lock(m_Mutex); 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 @@ -617,11 +609,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } InstanceHandle primaryStart = 0; - EnterCriticalSection(&m_Lock); + { + std::lock_guard lock(m_Mutex); primaryStart = m_PrimaryStart; - LeaveCriticalSection(&m_Lock); + } - const bool allTaskbars = InterlockedCompareExchange(&g_AllTaskbars, 0, 0) != 0; + const bool allTaskbars = g_AllTaskbars.load(); for (size_t i = 0; i < elements.size(); i++) { @@ -687,33 +680,26 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } } - LONG m_Refs; - bool m_Advised; - HWND m_Dispatch; - CRITICAL_SECTION m_Lock; + std::atomic m_Refs{ 1 }; + bool m_Advised = false; + HWND m_Dispatch = NULL; + std::mutex m_Mutex; CComPtr m_Site; CComPtr m_Visual; - InstanceHandle m_PrimaryStart; - unsigned int m_NextDiscoveryOrder; + InstanceHandle m_PrimaryStart = 0; + unsigned int m_NextDiscoveryOrder = 0; std::unordered_map m_Elements; }; -static CWin11StartButtonTap *GetTapRef( void ) +static CComPtr GetTapRef( void ) { - CWin11StartButtonTap *tap = NULL; - AcquireSRWLockShared(&g_TapLock); - tap = g_Tap; - if (tap) - tap->AddRef(); - ReleaseSRWLockShared(&g_TapLock); - return tap; + std::shared_lock lock(g_TapMutex); + return CComPtr(g_Tap); } class CStartButtonTapFactory: public IClassFactory { public: - CStartButtonTapFactory( void ) { m_Refs = 1; } - STDMETHODIMP QueryInterface( REFIID riid, void **ppv ) { if (!ppv) @@ -726,8 +712,8 @@ class CStartButtonTapFactory: public IClassFactory return S_OK; } - STDMETHODIMP_(ULONG) AddRef( void ) { return (ULONG)InterlockedIncrement(&m_Refs); } - STDMETHODIMP_(ULONG) Release( void ) { return (ULONG)InterlockedDecrement(&m_Refs); } + STDMETHODIMP_(ULONG) AddRef( void ) { return ++m_Refs; } + STDMETHODIMP_(ULONG) Release( void ) { return --m_Refs; } STDMETHODIMP CreateInstance( IUnknown *outer, REFIID riid, void **ppv ) { @@ -755,7 +741,7 @@ class CStartButtonTapFactory: public IClassFactory } private: - LONG m_Refs; + std::atomic m_Refs{ 1 }; }; static CStartButtonTapFactory g_Factory; @@ -785,12 +771,12 @@ static DWORD WINAPI ConnectAttemptThread( LPVOID param ) return 0; } -static DWORD FinishConnectThread( HMODULE moduleReference, HMODULE runtime, bool resetConnectionState ) +static DWORD FinishConnectThread( HMODULE moduleReference, HMODULE runtime, bool connected ) { if (runtime) FreeLibrary(runtime); - if (resetConnectionState) - InterlockedExchange(&g_ConnectStarted, 0); + if (!connected) + g_ConnectStarted.store(false); // Keep a private StartMenuHelper reference while this worker is running. // Release it atomically with thread termination so a failed diagnostics @@ -804,20 +790,20 @@ 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); + return FinishConnectThread(moduleReference, NULL, false); InitXamlDiagnosticsEx_t init = (InitXamlDiagnosticsEx_t)GetProcAddress(runtime, "InitializeXamlDiagnosticsEx"); if (!init) - return FinishConnectThread(moduleReference, runtime, true); + return FinishConnectThread(moduleReference, runtime, false); HMODULE module = GetThisModule(); wchar_t dllPath[MAX_PATH]; if (!module || !GetModuleFileName(module, dllPath, _countof(dllPath))) - return FinishConnectThread(moduleReference, runtime, true); + return FinishConnectThread(moduleReference, runtime, false); 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 retry = 0; retry < 8 && g_StartButtonActive.load(); retry++) { for (int i = 0; i < _countof(endpoints); i++) { @@ -843,18 +829,19 @@ static DWORD WINAPI ConnectThread( LPVOID param ) } LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); - return FinishConnectThread(moduleReference, runtime, true); + return FinishConnectThread(moduleReference, runtime, false); } static void EnsureConnection( void ) { - if (InterlockedCompareExchange(&g_ConnectStarted, 1, 0) != 0) + bool expected = false; + if (!g_ConnectStarted.compare_exchange_strong(expected, true)) return; HMODULE moduleReference = NULL; if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&ConnectThread, &moduleReference)) { - InterlockedExchange(&g_ConnectStarted, 0); + g_ConnectStarted.store(false); return; } @@ -866,21 +853,20 @@ static void EnsureConnection( void ) else { FreeLibrary(moduleReference); - InterlockedExchange(&g_ConnectStarted, 0); + g_ConnectStarted.store(false); } } extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) { - InterlockedExchange(&g_StartButtonEnabled, enabled ? 1 : 0); - InterlockedExchange(&g_AllTaskbars, allTaskbars ? 1 : 0); - InterlockedExchange(&g_StartButtonActive, 1); + g_StartButtonEnabled.store(enabled != FALSE); + g_AllTaskbars.store(allTaskbars != FALSE); + g_StartButtonActive.store(true); - CWin11StartButtonTap *tap = GetTapRef(); + auto tap = GetTapRef(); if (tap) { tap->RequestApply(false); - tap->Release(); return; } @@ -889,16 +875,15 @@ extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) { - InterlockedExchange(&g_StartButtonEnabled, enabled ? 1 : 0); - InterlockedExchange(&g_AllTaskbars, allTaskbars ? 1 : 0); + g_StartButtonEnabled.store(enabled != FALSE); + g_AllTaskbars.store(allTaskbars != FALSE); - CWin11StartButtonTap *tap = GetTapRef(); + auto tap = GetTapRef(); if (tap) { tap->RequestApply(false); - tap->Release(); } - else if (InterlockedCompareExchange(&g_StartButtonActive, 0, 0)) + else if (g_StartButtonActive.load()) { EnsureConnection(); } @@ -906,14 +891,13 @@ extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) extern "C" void StopWin11StartButtonTap( void ) { - InterlockedExchange(&g_StartButtonActive, 0); + g_StartButtonActive.store(false); - CWin11StartButtonTap *tap = GetTapRef(); + auto tap = GetTapRef(); if (tap) { HRESULT hr = tap->Deactivate(); if (FAILED(hr)) LogToFile(STARTUP_LOG, L"Win11StartButtonTap: deactivate failed 0x%08X", hr); - tap->Release(); } } From 789076a0c7ebed7421db5612f18e539df5459a07 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:29:32 +0200 Subject: [PATCH 12/58] Keep connection state after successful TAP attach --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index deaf407c8..4986ea2d4 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -822,7 +822,7 @@ static DWORD WINAPI ConnectThread( LPVOID param ) if (SUCCEEDED(last)) { LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoints[i]); - return FinishConnectThread(moduleReference, runtime, false); + return FinishConnectThread(moduleReference, runtime, true); } } Sleep(500); From 676632bc47d7de7d81a011732fc832f70847822e Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:31:21 +0200 Subject: [PATCH 13/58] Polish RAII locking in Win11 TAP --- .../StartMenuHelper/Win11StartButtonTap.cpp | 87 +++++++++---------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 4986ea2d4..c57d12bff 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -251,44 +251,44 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal { std::lock_guard lock(m_Mutex); - if (mutationType == Remove) - { - auto it = m_Elements.find(element.Handle); - if (it != m_Elements.end()) + if (mutationType == Remove) { - 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; + 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 + else if (mutationType == Add) { - record.discoveryOrder = ++m_NextDiscoveryOrder; - } - m_Elements[element.Handle] = record; + StartElement record; + record.parent = relation.Parent; + record.type = element.Type ? element.Type : L""; + record.name = element.Name ? element.Name : L""; - interesting = IsStartControlCandidate(record) || IsStartGlyph(record) || - IsUnderStartButtonLocked(record.parent); - } + 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); + } } if (interesting) @@ -380,9 +380,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal InstanceHandle GetStartAncestor( InstanceHandle handle ) { + std::lock_guard lock(m_Mutex); InstanceHandle result = 0; - { - std::lock_guard lock(m_Mutex); auto it = m_Elements.find(handle); if (it != m_Elements.end()) { @@ -400,7 +399,6 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal parent = pit->second.parent; } } - } return result; } @@ -494,8 +492,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal void SetControlClassification( InstanceHandle handle, bool isStartControl ) { - { - std::lock_guard lock(m_Mutex); + std::lock_guard lock(m_Mutex); auto it = m_Elements.find(handle); if (it != m_Elements.end()) { @@ -503,7 +500,6 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal it->second.isStartControl = isStartControl; m_PrimaryStart = FindPrimaryStartLocked(); } - } } HRESULT FindProperty( InstanceHandle handle, const wchar_t *name, unsigned int *index, CString *typeName ) @@ -565,8 +561,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal void SetOverrideFlags( InstanceHandle handle, bool *visibility, bool *hitTest ) { - { - std::lock_guard lock(m_Mutex); + std::lock_guard lock(m_Mutex); auto it = m_Elements.find(handle); if (it != m_Elements.end()) { @@ -575,7 +570,6 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (hitTest) it->second.hitTestOverride = *hitTest; } - } } void ApplyState( bool enabled ) @@ -586,8 +580,9 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal std::vector> elements; { std::lock_guard lock(m_Mutex); - for (auto it = m_Elements.begin(); it != m_Elements.end(); ++it) - elements.push_back(*it); + elements.reserve(m_Elements.size()); + for (const auto &element : m_Elements) + elements.push_back(element); } // Start and Task View share ExperienceToggleButton#LaunchListButton on @@ -611,7 +606,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal InstanceHandle primaryStart = 0; { std::lock_guard lock(m_Mutex); - primaryStart = m_PrimaryStart; + primaryStart = m_PrimaryStart; } const bool allTaskbars = g_AllTaskbars.load(); From 39b84dcf9143e5ee2d71a43ad1029c72a804256d Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:38:39 +0200 Subject: [PATCH 14/58] Remove redundant XAML connection attempt thread --- .../StartMenuHelper/Win11StartButtonTap.cpp | 32 +++---------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index c57d12bff..f69f8edec 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -750,22 +750,6 @@ HRESULT GetWin11StartButtonTapClassObject( REFCLSID clsid, REFIID riid, LPVOID * 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 connected ) { if (runtime) @@ -802,18 +786,8 @@ static DWORD WINAPI ConnectThread( LPVOID param ) { 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; + last = init(endpoints[i], GetCurrentProcessId(), NULL, + dllPath, CLSID_OpenShellStartButtonTap, NULL); if (SUCCEEDED(last)) { LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoints[i]); @@ -829,6 +803,8 @@ static DWORD WINAPI ConnectThread( LPVOID param ) static void EnsureConnection( void ) { + // Connection probing retries endpoints and may sleep, so keep it off the + // Explorer taskbar thread. ConnectThread is the only worker we need here. bool expected = false; if (!g_ConnectStarted.compare_exchange_strong(expected, true)) return; From d95836f01b62ef2999e05a4763a90e66c992be4e Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:54:20 +0200 Subject: [PATCH 15/58] Polish XAML TAP state handling --- .../StartMenuHelper/Win11StartButtonTap.cpp | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index f69f8edec..45168ef42 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include static const GUID CLSID_OpenShellStartButtonTap = @@ -95,7 +94,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal ~CWin11StartButtonTap( void ) { { - std::unique_lock lock(g_TapMutex); + std::unique_lock lock(g_TapMutex); if (g_Tap == this) g_Tap = NULL; } @@ -110,7 +109,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal DestroyWindow(dispatch); } - g_ConnectStarted.store(false); + g_ConnectStarted = false; _AtlModule.Unlock(); } @@ -159,7 +158,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } { - std::unique_lock lock(g_TapMutex); + std::unique_lock lock(g_TapMutex); if (g_Tap == this) g_Tap = NULL; } @@ -168,7 +167,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_Site.Release(); { - std::lock_guard lock(m_Mutex); + std::lock_guard lock(m_Mutex); m_Elements.clear(); m_PrimaryStart = 0; m_NextDiscoveryOrder = 0; @@ -184,11 +183,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal DestroyWindow(dispatch); } - g_ConnectStarted.store(false); + g_ConnectStarted = false; return S_OK; } - CComPtr newSite = site; CComPtr newVisual; // XAML Diagnostics keeps the TAP site object and its module loaded for @@ -219,12 +217,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return hr; } - m_Site = std::move(newSite); - m_Visual = std::move(newVisual); + m_Site = site; + m_Visual = newVisual; m_Advised = true; { - std::unique_lock lock(g_TapMutex); + std::unique_lock lock(g_TapMutex); g_Tap = this; } @@ -250,7 +248,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal bool interesting = false; { - std::lock_guard lock(m_Mutex); + std::lock_guard lock(m_Mutex); if (mutationType == Remove) { auto it = m_Elements.find(element.Handle); @@ -334,7 +332,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } if (msg == WM_OS_STARTBUTTON_APPLY && tap) { - bool enabled = g_StartButtonActive.load() && g_StartButtonEnabled.load(); + bool enabled = g_StartButtonActive && g_StartButtonEnabled; tap->ApplyState(enabled); return 0; } @@ -380,7 +378,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal InstanceHandle GetStartAncestor( InstanceHandle handle ) { - std::lock_guard lock(m_Mutex); + std::lock_guard lock(m_Mutex); InstanceHandle result = 0; auto it = m_Elements.find(handle); if (it != m_Elements.end()) @@ -492,7 +490,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal void SetControlClassification( InstanceHandle handle, bool isStartControl ) { - std::lock_guard lock(m_Mutex); + std::lock_guard lock(m_Mutex); auto it = m_Elements.find(handle); if (it != m_Elements.end()) { @@ -561,7 +559,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal void SetOverrideFlags( InstanceHandle handle, bool *visibility, bool *hitTest ) { - std::lock_guard lock(m_Mutex); + std::lock_guard lock(m_Mutex); auto it = m_Elements.find(handle); if (it != m_Elements.end()) { @@ -579,7 +577,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal std::vector> elements; { - std::lock_guard lock(m_Mutex); + std::lock_guard lock(m_Mutex); elements.reserve(m_Elements.size()); for (const auto &element : m_Elements) elements.push_back(element); @@ -605,11 +603,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal InstanceHandle primaryStart = 0; { - std::lock_guard lock(m_Mutex); + std::lock_guard lock(m_Mutex); primaryStart = m_PrimaryStart; } - const bool allTaskbars = g_AllTaskbars.load(); + const bool allTaskbars = g_AllTaskbars; for (size_t i = 0; i < elements.size(); i++) { @@ -688,7 +686,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal static CComPtr GetTapRef( void ) { - std::shared_lock lock(g_TapMutex); + std::shared_lock lock(g_TapMutex); return CComPtr(g_Tap); } @@ -755,7 +753,7 @@ static DWORD FinishConnectThread( HMODULE moduleReference, HMODULE runtime, bool if (runtime) FreeLibrary(runtime); if (!connected) - g_ConnectStarted.store(false); + g_ConnectStarted = false; // Keep a private StartMenuHelper reference while this worker is running. // Release it atomically with thread termination so a failed diagnostics @@ -782,7 +780,7 @@ static DWORD WINAPI ConnectThread( LPVOID param ) const wchar_t *endpoints[] = { L"VisualDiagConnection1", L"VisualDiagConnection2" }; HRESULT last = E_FAIL; - for (int retry = 0; retry < 8 && g_StartButtonActive.load(); retry++) + for (int retry = 0; retry < 8 && g_StartButtonActive; retry++) { for (int i = 0; i < _countof(endpoints); i++) { @@ -812,7 +810,7 @@ static void EnsureConnection( void ) HMODULE moduleReference = NULL; if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&ConnectThread, &moduleReference)) { - g_ConnectStarted.store(false); + g_ConnectStarted = false; return; } @@ -824,15 +822,15 @@ static void EnsureConnection( void ) else { FreeLibrary(moduleReference); - g_ConnectStarted.store(false); + g_ConnectStarted = false; } } extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) { - g_StartButtonEnabled.store(enabled != FALSE); - g_AllTaskbars.store(allTaskbars != FALSE); - g_StartButtonActive.store(true); + g_StartButtonEnabled = enabled != FALSE; + g_AllTaskbars = allTaskbars != FALSE; + g_StartButtonActive = true; auto tap = GetTapRef(); if (tap) @@ -846,15 +844,15 @@ extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) { - g_StartButtonEnabled.store(enabled != FALSE); - g_AllTaskbars.store(allTaskbars != FALSE); + g_StartButtonEnabled = enabled != FALSE; + g_AllTaskbars = allTaskbars != FALSE; auto tap = GetTapRef(); if (tap) { tap->RequestApply(false); } - else if (g_StartButtonActive.load()) + else if (g_StartButtonActive) { EnsureConnection(); } @@ -862,7 +860,7 @@ extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) extern "C" void StopWin11StartButtonTap( void ) { - g_StartButtonActive.store(false); + g_StartButtonActive = false; auto tap = GetTapRef(); if (tap) From 941bef152d3db0362ab94ec11508992067955d4e Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:03:16 +0200 Subject: [PATCH 16/58] Unify XAML connection failure cleanup --- .../StartMenuHelper/Win11StartButtonTap.cpp | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 45168ef42..a987ecd95 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -808,22 +808,19 @@ static void EnsureConnection( void ) return; HMODULE moduleReference = NULL; - if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&ConnectThread, &moduleReference)) - { - g_ConnectStarted = false; - return; - } - - HANDLE thread = CreateThread(NULL, 0, ConnectThread, moduleReference, 0, NULL); - if (thread) - { - CloseHandle(thread); - } - else + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&ConnectThread, &moduleReference)) { + HANDLE thread = CreateThread(NULL, 0, ConnectThread, moduleReference, 0, NULL); + if (thread) + { + CloseHandle(thread); + return; + } FreeLibrary(moduleReference); - g_ConnectStarted = false; } + + // A worker was not started, so connection can be attempted again later. + g_ConnectStarted = false; } extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) From b7af69cbd5f490ad5af779a2e4a1f7bf09275cd1 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:11:29 +0200 Subject: [PATCH 17/58] Harden XAML TAP lifecycle and teardown --- .../StartMenuHelper/Win11StartButtonTap.cpp | 362 +++++++++++++----- 1 file changed, 258 insertions(+), 104 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index a987ecd95..1fe5ecd29 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -28,6 +28,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 std::atomic_bool g_StartButtonActive{ false }; static std::atomic_bool g_StartButtonEnabled{ false }; @@ -99,14 +100,30 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal g_Tap = NULL; } - if (m_Visual && m_Advised) - m_Visual->UnadviseVisualTreeChange(static_cast(this)); - if (m_Dispatch && GetWindowThreadProcessId(m_Dispatch, NULL) == GetCurrentThreadId()) { - HWND dispatch = m_Dispatch; - m_Dispatch = NULL; - SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); - DestroyWindow(dispatch); + std::lock_guard lock(m_LifecycleMutex); + if (m_Visual && m_Advised) + { + HRESULT hr = m_Visual->UnadviseVisualTreeChange(static_cast(this)); + if (FAILED(hr)) + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: final visual tree unadvise failed 0x%08X", hr); + else + m_Advised = false; + } + + ResetElements(); + HRESULT hr = DestroyDispatchWindow(); + if (FAILED(hr)) + { + // Never leave a window pointing at an object that is being destroyed. + HWND dispatch = m_Dispatch; + if (dispatch) + SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); + m_Dispatch = NULL; + } + + m_Visual.Release(); + m_Site.Release(); } g_ConnectStarted = false; @@ -145,17 +162,14 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal 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; - } + std::lock_guard lifecycleLock(m_LifecycleMutex); + + // A diagnostics endpoint can call SetSite again on the same TAP object. + // Tear down the previous subscription first so callbacks never outlive + // the site/service state they were registered against. + HRESULT hr = DeactivateLocked(); + if (FAILED(hr)) + return hr; { std::unique_lock lock(g_TapMutex); @@ -165,69 +179,37 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_Visual.Release(); m_Site.Release(); - - { - std::lock_guard lock(m_Mutex); - m_Elements.clear(); - m_PrimaryStart = 0; - m_NextDiscoveryOrder = 0; - } + ResetElements(); if (!site) { - if (m_Dispatch && GetWindowThreadProcessId(m_Dispatch, NULL) == GetCurrentThreadId()) - { - HWND dispatch = m_Dispatch; - m_Dispatch = NULL; - SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); - DestroyWindow(dispatch); - } - g_ConnectStarted = false; return S_OK; } CComPtr newVisual; - - // XAML Diagnostics keeps the TAP site object and its module loaded for - // the lifetime of the diagnostics session. Build the new COM state in - // locals first, and publish it only after the subscription succeeds. - HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&newVisual); + hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&newVisual); if (FAILED(hr)) return hr; if (!newVisual) return E_NOINTERFACE; - if (!CreateDispatchWindow()) - return HRESULT_FROM_WIN32(GetLastError()); - - // Advise replays the existing tree. OnVisualTreeChange only records - // element handles; all property access is dispatched afterwards. - hr = newVisual->AdviseVisualTreeChange(static_cast(this)); - if (FAILED(hr)) - { - if (m_Dispatch) - { - HWND dispatch = m_Dispatch; - m_Dispatch = NULL; - SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); - DestroyWindow(dispatch); - } - LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree advise 0x%08X", hr); - return hr; - } - m_Site = site; m_Visual = newVisual; - m_Advised = true; { std::unique_lock lock(g_TapMutex); g_Tap = this; } - RequestApply(false); - LogToFile(STARTUP_LOG, L"Win11StartButton: visual tree advise 0x%08X", hr); + // Stop can race with the asynchronous diagnostics connection. In that + // case retain the site for a later restart but do not subscribe yet. + if (!g_StartButtonActive) + return S_OK; + + hr = ActivateLocked(); + if (FAILED(hr)) + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); return hr; } @@ -289,8 +271,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } } - if (interesting) - RequestApply(false); + // AdviseVisualTreeChange replays the existing tree synchronously. Do not + // queue partial-state work during that replay; ActivateLocked performs one + // complete apply after Advise has returned and the dispatch window exists. + if (interesting && m_Advised) + RequestApply(false, g_StartButtonActive && g_StartButtonEnabled); return S_OK; } @@ -299,67 +284,229 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return S_OK; } - void RequestApply( bool synchronous ) + HRESULT Activate( void ) { - 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); + std::lock_guard lock(m_LifecycleMutex); + return ActivateLocked(); } 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; + std::lock_guard lock(m_LifecycleMutex); + return DeactivateLocked(); } private: - static LRESULT CALLBACK DispatchProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) + void ResetElements( void ) { - CWin11StartButtonTap *tap = (CWin11StartButtonTap*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (msg == WM_NCCREATE) + std::lock_guard lock(m_Mutex); + m_Elements.clear(); + m_PrimaryStart = 0; + m_NextDiscoveryOrder = 0; + } + + HRESULT RequestApply( bool synchronous, bool enabled ) + { + HWND dispatch = m_Dispatch; + if (!dispatch || !IsWindow(dispatch)) + return HRESULT_FROM_WIN32(ERROR_INVALID_WINDOW_HANDLE); + + if (!synchronous) { - CREATESTRUCT *create = (CREATESTRUCT*)lParam; - tap = (CWin11StartButtonTap*)create->lpCreateParams; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)tap); + if (!PostMessage(dispatch, WM_OS_STARTBUTTON_APPLY, enabled ? 1 : 0, 0)) + return HRESULT_FROM_WIN32(GetLastError()); + return S_OK; } - if (msg == WM_OS_STARTBUTTON_APPLY && tap) + + DWORD_PTR result = 0; + SetLastError(ERROR_SUCCESS); + LRESULT sent = SendMessageTimeout(dispatch, WM_OS_STARTBUTTON_APPLY, enabled ? 1 : 0, 0, + SMTO_ABORTIFHUNG | SMTO_BLOCK, 2000, &result); + if (sent) + return S_OK; + + DWORD error = GetLastError(); + if (!error) + error = ERROR_TIMEOUT; + return HRESULT_FROM_WIN32(error); + } + + HRESULT ActivateLocked( void ) + { + if (!m_Visual) + return E_UNEXPECTED; + + if (!m_Advised) { - bool enabled = g_StartButtonActive && g_StartButtonEnabled; - tap->ApplyState(enabled); - return 0; + ResetElements(); + + // Advise replays the existing tree. The replay only records handles; + // no property work is queued until m_Advised becomes true below. + HRESULT hr = m_Visual->AdviseVisualTreeChange(static_cast(this)); + if (FAILED(hr)) + { + ResetElements(); + return hr; + } + m_Advised = true; } - return DefWindowProc(hwnd, msg, wParam, lParam); + + HRESULT hr = CreateDispatchWindow(); + if (FAILED(hr)) + { + // If the callback was registered by this activation, undo it. If + // unadvise itself fails, keep the subscription state intact so a + // later activation can retry only the dispatch-window creation. + if (m_Visual && m_Advised) + { + HRESULT unadvise = m_Visual->UnadviseVisualTreeChange( + static_cast(this)); + if (SUCCEEDED(unadvise)) + { + m_Advised = false; + ResetElements(); + } + else + { + LogToFile(STARTUP_LOG, + L"Win11StartButtonTap: rollback unadvise failed 0x%08X", unadvise); + } + } + return hr; + } + + return RequestApply(false, g_StartButtonActive && g_StartButtonEnabled); } + HRESULT DeactivateLocked( void ) + { + // Restore our XAML overrides before removing the callback. If the UI + // thread is unavailable, keep the subscription alive rather than leave + // the native Start button hidden with no path left to restore it. + HWND dispatch = m_Dispatch; + if (dispatch && IsWindow(dispatch)) + { + HRESULT hr = RequestApply(true, false); + if (FAILED(hr)) + { + LogToFile(STARTUP_LOG, + L"Win11StartButtonTap: synchronous restore failed 0x%08X", hr); + return hr; + } + } + + if (m_Visual && m_Advised) + { + 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; + } + + ResetElements(); + return DestroyDispatchWindow(); + } - bool CreateDispatchWindow( void ) + HRESULT CreateDispatchWindow( void ) { - if (m_Dispatch) - return true; + HWND dispatch = m_Dispatch; + if (dispatch) + { + if (IsWindow(dispatch)) + return S_OK; + m_Dispatch = NULL; + } static const wchar_t CLASS_NAME[] = L"OpenShell.Win11StartButtonTap"; WNDCLASS wc = {}; HMODULE module = GetThisModule(); if (!module) - return false; + return E_FAIL; wc.lpfnWndProc = DispatchProc; wc.hInstance = module; wc.lpszClassName = CLASS_NAME; - if (!RegisterClass(&wc) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) - return false; + if (!RegisterClass(&wc)) + { + DWORD error = GetLastError(); + if (error != ERROR_CLASS_ALREADY_EXISTS) + return HRESULT_FROM_WIN32(error); + } - m_Dispatch = CreateWindowEx(0, CLASS_NAME, L"", 0, 0, 0, 0, 0, + dispatch = CreateWindowEx(0, CLASS_NAME, L"", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, module, this); - return m_Dispatch != NULL; + if (!dispatch) + return HRESULT_FROM_WIN32(GetLastError()); + + m_Dispatch = dispatch; + return S_OK; + } + + HRESULT DestroyDispatchWindow( void ) + { + HWND dispatch = m_Dispatch; + if (!dispatch) + return S_OK; + if (!IsWindow(dispatch)) + { + m_Dispatch = NULL; + return S_OK; + } + + if (GetWindowThreadProcessId(dispatch, NULL) == GetCurrentThreadId()) + { + SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); + if (!DestroyWindow(dispatch)) + return HRESULT_FROM_WIN32(GetLastError()); + m_Dispatch = NULL; + return S_OK; + } + + DWORD_PTR result = 0; + SetLastError(ERROR_SUCCESS); + LRESULT sent = SendMessageTimeout(dispatch, WM_OS_STARTBUTTON_DESTROY, 0, 0, + SMTO_ABORTIFHUNG | SMTO_BLOCK, 2000, &result); + if (!sent && IsWindow(dispatch)) + { + DWORD error = GetLastError(); + if (!error) + error = ERROR_TIMEOUT; + return HRESULT_FROM_WIN32(error); + } + + if (IsWindow(dispatch)) + return E_FAIL; + + m_Dispatch = NULL; + return S_OK; + } + + 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_DESTROY) + { + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + DestroyWindow(hwnd); + return 0; + } + if (msg == WM_OS_STARTBUTTON_APPLY && tap) + { + tap->ApplyState(wParam != 0); + return 0; + } + return DefWindowProc(hwnd, msg, wParam, lParam); } bool IsUnderStartButtonLocked( InstanceHandle parent ) const @@ -674,8 +821,9 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } std::atomic m_Refs{ 1 }; - bool m_Advised = false; - HWND m_Dispatch = NULL; + std::atomic_bool m_Advised{ false }; + std::atomic m_Dispatch{ NULL }; + std::mutex m_LifecycleMutex; std::mutex m_Mutex; CComPtr m_Site; CComPtr m_Visual; @@ -716,12 +864,11 @@ class CStartButtonTapFactory: public IClassFactory if (outer) return CLASS_E_NOAGGREGATION; - CWin11StartButtonTap *tap = new CWin11StartButtonTap(); + CComPtr tap; + tap.Attach(new CWin11StartButtonTap()); if (!tap) return E_OUTOFMEMORY; - HRESULT hr = tap->QueryInterface(riid, ppv); - tap->Release(); - return hr; + return tap->QueryInterface(riid, ppv); } STDMETHODIMP LockServer( BOOL lock ) @@ -832,7 +979,9 @@ extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) auto tap = GetTapRef(); if (tap) { - tap->RequestApply(false); + HRESULT hr = tap->Activate(); + if (FAILED(hr)) + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: start activation failed 0x%08X", hr); return; } @@ -844,12 +993,17 @@ extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) g_StartButtonEnabled = enabled != FALSE; g_AllTaskbars = allTaskbars != FALSE; + if (!g_StartButtonActive) + return; + auto tap = GetTapRef(); if (tap) { - tap->RequestApply(false); + HRESULT hr = tap->Activate(); + if (FAILED(hr)) + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: update activation failed 0x%08X", hr); } - else if (g_StartButtonActive) + else { EnsureConnection(); } From bd5b5fa88eadfc1144453e60cb1b8e971fc96b96 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:13:05 +0200 Subject: [PATCH 18/58] Guard queued XAML apply work during shutdown --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 1fe5ecd29..1cdbcdf26 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -503,7 +503,9 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } if (msg == WM_OS_STARTBUTTON_APPLY && tap) { - tap->ApplyState(wParam != 0); + // A queued enable request must never re-apply overrides after Stop. + bool enabled = wParam != 0 && g_StartButtonActive && g_StartButtonEnabled; + tap->ApplyState(enabled); return 0; } return DefWindowProc(hwnd, msg, wParam, lParam); @@ -829,6 +831,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal CComPtr m_Visual; InstanceHandle m_PrimaryStart = 0; unsigned int m_NextDiscoveryOrder = 0; + // Keep the replayed ancestry while subscribed. Parent links are required to + // recognize glyphs under a Start control, and the map is cleared on unadvise. std::unordered_map m_Elements; }; From 19740e807244f7adc3ea1dc4a1082482ff1e62c0 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:16:40 +0200 Subject: [PATCH 19/58] Serialize TAP enable state across teardown --- .../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 1cdbcdf26..cfc8d4ba5 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -218,6 +218,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (!ppv) return E_POINTER; *ppv = NULL; + + std::lock_guard lock(m_LifecycleMutex); if (!m_Site) return E_FAIL; return m_Site->QueryInterface(riid, ppv); @@ -375,11 +377,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return hr; } + m_AllowEnable = true; return RequestApply(false, g_StartButtonActive && g_StartButtonEnabled); } HRESULT DeactivateLocked( void ) { + // Reject any already-queued enable work before restoring state. This is + // independent of the global active flag so SetSite replacement is safe + // even while Open-Shell itself remains active. + m_AllowEnable = false; + // Restore our XAML overrides before removing the callback. If the UI // thread is unavailable, keep the subscription alive rather than leave // the native Start button hidden with no path left to restore it. @@ -503,8 +511,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } if (msg == WM_OS_STARTBUTTON_APPLY && tap) { - // A queued enable request must never re-apply overrides after Stop. - bool enabled = wParam != 0 && g_StartButtonActive && g_StartButtonEnabled; + // A queued enable request must never re-apply overrides once teardown + // has started, even if Open-Shell is replacing the diagnostics site. + bool enabled = wParam != 0 && tap->m_AllowEnable && + g_StartButtonActive && g_StartButtonEnabled; tap->ApplyState(enabled); return 0; } @@ -824,6 +834,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal std::atomic m_Refs{ 1 }; std::atomic_bool m_Advised{ false }; + std::atomic_bool m_AllowEnable{ false }; std::atomic m_Dispatch{ NULL }; std::mutex m_LifecycleMutex; std::mutex m_Mutex; From 08d46b04baaf657b9b99abd2a2485e0cc7dcf490 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:20:20 +0200 Subject: [PATCH 20/58] Avoid lifecycle lock across COM site queries --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index cfc8d4ba5..dcbf4b3a1 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -218,8 +218,6 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (!ppv) return E_POINTER; *ppv = NULL; - - std::lock_guard lock(m_LifecycleMutex); if (!m_Site) return E_FAIL; return m_Site->QueryInterface(riid, ppv); From 05b6638e2eb6f1c278e00bd09e07c964854c7c52 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:21:58 +0200 Subject: [PATCH 21/58] Verify XAML override restoration before teardown --- .../StartMenuHelper/Win11StartButtonTap.cpp | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index dcbf4b3a1..a628e3e7f 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -323,7 +323,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal LRESULT sent = SendMessageTimeout(dispatch, WM_OS_STARTBUTTON_APPLY, enabled ? 1 : 0, 0, SMTO_ABORTIFHUNG | SMTO_BLOCK, 2000, &result); if (sent) - return S_OK; + return static_cast(result); DWORD error = GetLastError(); if (!error) @@ -513,8 +513,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal // has started, even if Open-Shell is replacing the diagnostics site. bool enabled = wParam != 0 && tap->m_AllowEnable && g_StartButtonActive && g_StartButtonEnabled; - tap->ApplyState(enabled); - return 0; + return static_cast(tap->ApplyState(enabled)); } return DefWindowProc(hwnd, msg, wParam, lParam); } @@ -727,11 +726,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } } - void ApplyState( bool enabled ) + HRESULT ApplyState( bool enabled ) { if (!m_Visual) - return; + return E_UNEXPECTED; + HRESULT firstError = S_OK; std::vector> elements; { std::lock_guard lock(m_Mutex); @@ -740,21 +740,29 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal elements.push_back(element); } - // 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++) + // Classification is only needed when applying overrides. During teardown, + // use the classifications that produced the existing overrides; querying + // new candidates adds risk and work without helping restoration. + if (enabled) { - StartElement &record = elements[i].second; - if (!IsStartControlCandidate(record) || record.startControlResolved) - continue; - - bool isStartControl = false; - if (SUCCEEDED(ResolveStartControl(elements[i].first, record, &isStartControl))) + for (size_t i = 0; i < elements.size(); i++) { - record.startControlResolved = true; - record.isStartControl = isStartControl; - SetControlClassification(elements[i].first, isStartControl); + StartElement &record = elements[i].second; + if (!IsStartControlCandidate(record) || record.startControlResolved) + continue; + + bool isStartControl = false; + HRESULT hr = ResolveStartControl(elements[i].first, record, &isStartControl); + if (SUCCEEDED(hr)) + { + record.startControlResolved = true; + record.isStartControl = isStartControl; + SetControlClassification(elements[i].first, isStartControl); + } + else if (SUCCEEDED(firstError)) + { + firstError = hr; + } } } @@ -784,6 +792,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal bool value = true; SetOverrideFlags(handle, NULL, &value); } + else if (SUCCEEDED(firstError)) + { + firstError = hr; + } } } else if (record.hitTestOverride) @@ -794,6 +806,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal bool value = false; SetOverrideFlags(handle, NULL, &value); } + else if (SUCCEEDED(firstError)) + { + firstError = hr; + } } continue; } @@ -816,6 +832,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal bool value = true; SetOverrideFlags(handle, &value, NULL); } + else if (SUCCEEDED(firstError)) + { + firstError = hr; + } } } else if (record.visibilityOverride) @@ -826,8 +846,14 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal bool value = false; SetOverrideFlags(handle, &value, NULL); } + else if (SUCCEEDED(firstError)) + { + firstError = hr; + } } } + + return firstError; } std::atomic m_Refs{ 1 }; From 1b4e24c1244e275af9756c1a1d7fd1ec20bbf4ff Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:23:00 +0200 Subject: [PATCH 22/58] Drain queued TAP work before dispatch teardown --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index a628e3e7f..49f8b26e1 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -466,6 +466,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (GetWindowThreadProcessId(dispatch, NULL) == GetCurrentThreadId()) { + DrainApplyMessages(dispatch); SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); if (!DestroyWindow(dispatch)) return HRESULT_FROM_WIN32(GetLastError()); @@ -492,6 +493,14 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return S_OK; } + static void DrainApplyMessages( HWND hwnd ) + { + MSG pending; + while (PeekMessage(&pending, hwnd, WM_OS_STARTBUTTON_APPLY, WM_OS_STARTBUTTON_APPLY, PM_REMOVE)) + { + } + } + static LRESULT CALLBACK DispatchProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) { CWin11StartButtonTap *tap = (CWin11StartButtonTap*)GetWindowLongPtr(hwnd, GWLP_USERDATA); @@ -503,6 +512,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } if (msg == WM_OS_STARTBUTTON_DESTROY) { + DrainApplyMessages(hwnd); SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); DestroyWindow(hwnd); return 0; From bee430c69672edb2c8c21a46310900981e676324 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:27:24 +0200 Subject: [PATCH 23/58] Preserve TAP state when restore dispatch is unavailable --- .../StartMenuHelper/Win11StartButtonTap.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 49f8b26e1..df77483c9 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -305,6 +305,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_NextDiscoveryOrder = 0; } + bool HasOverrides( void ) + { + std::lock_guard lock(m_Mutex); + for (const auto &element : m_Elements) + { + if (element.second.visibilityOverride || element.second.hitTestOverride) + return true; + } + return false; + } + HRESULT RequestApply( bool synchronous, bool enabled ) { HWND dispatch = m_Dispatch; @@ -400,6 +411,13 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return hr; } } + else if (HasOverrides()) + { + // Do not discard bookkeeping for live overrides if the UI dispatch + // path vanished. Keeping the subscription/state is safer than + // leaving a native Start property overridden with no retry path. + return HRESULT_FROM_WIN32(ERROR_INVALID_WINDOW_HANDLE); + } if (m_Visual && m_Advised) { From 969d10406554eae702904578f4297ba11b3de6d0 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:21:21 +0200 Subject: [PATCH 24/58] Simplify Windows 11 TAP teardown and dispatch lifecycle --- .../StartMenuHelper/Win11StartButtonTap.cpp | 84 +++++++------------ 1 file changed, 28 insertions(+), 56 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index df77483c9..69deaa9f6 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -94,39 +94,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal ~CWin11StartButtonTap( void ) { - { - std::unique_lock lock(g_TapMutex); - if (g_Tap == this) - g_Tap = NULL; - } - - { - std::lock_guard lock(m_LifecycleMutex); - if (m_Visual && m_Advised) - { - HRESULT hr = m_Visual->UnadviseVisualTreeChange(static_cast(this)); - if (FAILED(hr)) - LogToFile(STARTUP_LOG, L"Win11StartButtonTap: final visual tree unadvise failed 0x%08X", hr); - else - m_Advised = false; - } - - ResetElements(); - HRESULT hr = DestroyDispatchWindow(); - if (FAILED(hr)) - { - // Never leave a window pointing at an object that is being destroyed. - HWND dispatch = m_Dispatch; - if (dispatch) - SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); - m_Dispatch = NULL; - } - - m_Visual.Release(); - m_Site.Release(); - } - - g_ConnectStarted = false; + DestroyDispatchWindow(); _AtlModule.Unlock(); } @@ -197,16 +165,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_Site = site; m_Visual = newVisual; + // A late connection must not publish a TAP after Stop has completed. { std::unique_lock lock(g_TapMutex); + if (!g_StartButtonActive) + { + g_ConnectStarted = false; + return S_OK; + } g_Tap = this; } - // Stop can race with the asynchronous diagnostics connection. In that - // case retain the site for a later restart but do not subscribe yet. - if (!g_StartButtonActive) - return S_OK; - hr = ActivateLocked(); if (FAILED(hr)) LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); @@ -293,7 +262,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal HRESULT Deactivate( void ) { std::lock_guard lock(m_LifecycleMutex); - return DeactivateLocked(); + HRESULT hr = DeactivateLocked(); + if (SUCCEEDED(hr) && !g_StartButtonActive) + { + std::unique_lock tapLock(g_TapMutex); + if (g_Tap == this) + { + g_Tap = NULL; + g_ConnectStarted = false; + } + } + return hr; } private: @@ -322,24 +301,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (!dispatch || !IsWindow(dispatch)) return HRESULT_FROM_WIN32(ERROR_INVALID_WINDOW_HANDLE); - if (!synchronous) + if (synchronous) { - if (!PostMessage(dispatch, WM_OS_STARTBUTTON_APPLY, enabled ? 1 : 0, 0)) - return HRESULT_FROM_WIN32(GetLastError()); - return S_OK; - } - - DWORD_PTR result = 0; - SetLastError(ERROR_SUCCESS); - LRESULT sent = SendMessageTimeout(dispatch, WM_OS_STARTBUTTON_APPLY, enabled ? 1 : 0, 0, - SMTO_ABORTIFHUNG | SMTO_BLOCK, 2000, &result); - if (sent) + DWORD_PTR result = 0; + if (!SendMessageTimeout(dispatch, WM_OS_STARTBUTTON_APPLY, enabled ? 1 : 0, 0, + SMTO_ABORTIFHUNG | SMTO_BLOCK, 2000, &result)) + return HRESULT_FROM_WIN32(ERROR_TIMEOUT); return static_cast(result); + } - DWORD error = GetLastError(); - if (!error) - error = ERROR_TIMEOUT; - return HRESULT_FROM_WIN32(error); + return PostMessage(dispatch, WM_OS_STARTBUTTON_APPLY, enabled ? 1 : 0, 0) + ? S_OK : HRESULT_FROM_WIN32(GetLastError()); } HRESULT ActivateLocked( void ) @@ -482,10 +454,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return S_OK; } + // Detach the window from the TAP before teardown on either thread. + SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); if (GetWindowThreadProcessId(dispatch, NULL) == GetCurrentThreadId()) { DrainApplyMessages(dispatch); - SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); if (!DestroyWindow(dispatch)) return HRESULT_FROM_WIN32(GetLastError()); m_Dispatch = NULL; @@ -531,7 +504,6 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (msg == WM_OS_STARTBUTTON_DESTROY) { DrainApplyMessages(hwnd); - SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); DestroyWindow(hwnd); return 0; } From 5e3a2de94b353a10b9d30cab587b6c846aa8c2cf Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:35:53 +0200 Subject: [PATCH 25/58] Preserve TAP window state on failed teardown and guard lifecycle publication --- .../StartMenuHelper/Win11StartButtonTap.cpp | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 69deaa9f6..e8f0b87a5 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -178,7 +178,15 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal hr = ActivateLocked(); if (FAILED(hr)) + { + // The diagnostics runtime may release this TAP after SetSite fails. + // Do not leave a non-owning global pointer to that object. + std::unique_lock lock(g_TapMutex); + if (g_Tap == this) + g_Tap = NULL; + g_ConnectStarted = false; LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); + } return hr; } @@ -454,11 +462,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return S_OK; } - // Detach the window from the TAP before teardown on either thread. - SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); if (GetWindowThreadProcessId(dispatch, NULL) == GetCurrentThreadId()) { DrainApplyMessages(dispatch); + SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); if (!DestroyWindow(dispatch)) return HRESULT_FROM_WIN32(GetLastError()); m_Dispatch = NULL; @@ -501,12 +508,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal tap = (CWin11StartButtonTap*)create->lpCreateParams; SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)tap); } - if (msg == WM_OS_STARTBUTTON_DESTROY) - { - DrainApplyMessages(hwnd); - DestroyWindow(hwnd); - return 0; - } + if (msg == WM_OS_STARTBUTTON_DESTROY && tap) + return static_cast(tap->DestroyDispatchWindow()); if (msg == WM_OS_STARTBUTTON_APPLY && tap) { // A queued enable request must never re-apply overrides once teardown @@ -1050,9 +1053,13 @@ extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) extern "C" void StopWin11StartButtonTap( void ) { - g_StartButtonActive = false; - - auto tap = GetTapRef(); + CComPtr tap; + { + // Serialize stop with SetSite publishing the global TAP pointer. + std::unique_lock lock(g_TapMutex); + g_StartButtonActive = false; + tap = g_Tap; + } if (tap) { HRESULT hr = tap->Deactivate(); From 1e89949df086db0623960aaa363c20572e9409cf Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:38:42 +0200 Subject: [PATCH 26/58] Keep subscribed TAP available if rollback unadvise fails --- .../StartMenuHelper/Win11StartButtonTap.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index e8f0b87a5..60f4960d7 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -179,12 +179,16 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal hr = ActivateLocked(); if (FAILED(hr)) { - // The diagnostics runtime may release this TAP after SetSite fails. - // Do not leave a non-owning global pointer to that object. - std::unique_lock lock(g_TapMutex); - if (g_Tap == this) - g_Tap = NULL; - g_ConnectStarted = false; + // A failed activation without a subscription cannot retain a + // non-owning global TAP pointer. A failed unadvise, however, must + // retain the subscribed callback so activation can be retried. + if (!m_Advised) + { + std::unique_lock lock(g_TapMutex); + if (g_Tap == this) + g_Tap = NULL; + g_ConnectStarted = false; + } LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); } return hr; From dd7ff3e23dda8b25132808354a4372be50926c91 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:09:05 +0200 Subject: [PATCH 27/58] Align TAP dispatch window class with DLL lifetime --- .../StartMenuHelper/Win11StartButtonTap.cpp | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 60f4960d7..7c1418d74 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -29,6 +29,8 @@ static const GUID CLSID_OpenShellStartButtonTap = static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; static const UINT WM_OS_STARTBUTTON_DESTROY = WM_APP + 0x35C; +static const wchar_t DISPATCH_WINDOW_CLASS[] = L"OpenShell.Win11StartButtonTap"; +static std::mutex g_DispatchClassMutex; static std::atomic_bool g_StartButtonActive{ false }; static std::atomic_bool g_StartButtonEnabled{ false }; @@ -165,15 +167,21 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_Site = site; m_Visual = newVisual; - // A late connection must not publish a TAP after Stop has completed. + // A late connection must not retain a diagnostics site after Stop. + bool active; { std::unique_lock lock(g_TapMutex); - if (!g_StartButtonActive) - { + active = g_StartButtonActive; + if (active) + g_Tap = this; + else g_ConnectStarted = false; - return S_OK; - } - g_Tap = this; + } + if (!active) + { + m_Visual.Release(); + m_Site.Release(); + return S_OK; } hr = ActivateLocked(); @@ -430,15 +438,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_Dispatch = NULL; } - static const wchar_t CLASS_NAME[] = L"OpenShell.Win11StartButtonTap"; WNDCLASS wc = {}; HMODULE module = GetThisModule(); if (!module) return E_FAIL; + // Window classes registered by DLLs survive FreeLibrary. Register + // and create together, and unregister after the last window closes. + std::lock_guard classLock(g_DispatchClassMutex); wc.lpfnWndProc = DispatchProc; wc.hInstance = module; - wc.lpszClassName = CLASS_NAME; + wc.lpszClassName = DISPATCH_WINDOW_CLASS; if (!RegisterClass(&wc)) { DWORD error = GetLastError(); @@ -446,10 +456,14 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return HRESULT_FROM_WIN32(error); } - dispatch = CreateWindowEx(0, CLASS_NAME, L"", 0, 0, 0, 0, 0, + dispatch = CreateWindowEx(0, DISPATCH_WINDOW_CLASS, L"", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, module, this); if (!dispatch) - return HRESULT_FROM_WIN32(GetLastError()); + { + DWORD error = GetLastError(); + UnregisterClass(DISPATCH_WINDOW_CLASS, module); + return HRESULT_FROM_WIN32(error); + } m_Dispatch = dispatch; return S_OK; @@ -463,6 +477,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (!IsWindow(dispatch)) { m_Dispatch = NULL; + std::lock_guard classLock(g_DispatchClassMutex); + UnregisterClass(DISPATCH_WINDOW_CLASS, GetThisModule()); return S_OK; } @@ -471,8 +487,15 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal DrainApplyMessages(dispatch); SetWindowLongPtr(dispatch, GWLP_USERDATA, 0); if (!DestroyWindow(dispatch)) - return HRESULT_FROM_WIN32(GetLastError()); + { + DWORD error = GetLastError(); + if (IsWindow(dispatch)) + SetWindowLongPtr(dispatch, GWLP_USERDATA, (LONG_PTR)this); + return HRESULT_FROM_WIN32(error); + } m_Dispatch = NULL; + std::lock_guard classLock(g_DispatchClassMutex); + UnregisterClass(DISPATCH_WINDOW_CLASS, GetThisModule()); return S_OK; } From 60a63a15d1737827aec918737b6cff140fb36b4b Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:11:13 +0200 Subject: [PATCH 28/58] Destroy TAP dispatch window before releasing diagnostics callback --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 7c1418d74..04a9593ed 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -411,9 +411,15 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return HRESULT_FROM_WIN32(ERROR_INVALID_WINDOW_HANDLE); } + // Unadvise may release the final XAML callback reference. + // The dispatch window must be gone before that can happen. + HRESULT hr = DestroyDispatchWindow(); + if (FAILED(hr)) + return hr; + if (m_Visual && m_Advised) { - HRESULT hr = m_Visual->UnadviseVisualTreeChange( + hr = m_Visual->UnadviseVisualTreeChange( static_cast(this)); if (FAILED(hr)) { @@ -425,7 +431,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } ResetElements(); - return DestroyDispatchWindow(); + return S_OK; } HRESULT CreateDispatchWindow( void ) From 3d9c8b18fffb25aa8016f98c0a7bc968f0ee114b Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:29:58 +0200 Subject: [PATCH 29/58] Hold published XAML TAP using an owned COM reference --- .../StartMenuHelper/Win11StartButtonTap.cpp | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 04a9593ed..6c3078385 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -58,7 +58,7 @@ struct StartElement }; class CWin11StartButtonTap; -static CWin11StartButtonTap *g_Tap = NULL; +static CComPtr &PublishedTap( void ); static std::shared_mutex g_TapMutex; static bool ContainsText( const CString &text, const wchar_t *part ) @@ -141,15 +141,15 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (FAILED(hr)) return hr; + CComPtr oldTap; { std::unique_lock lock(g_TapMutex); - if (g_Tap == this) - g_Tap = NULL; + if (PublishedTap().p == this) + oldTap.Attach(PublishedTap().Detach()); } m_Visual.Release(); m_Site.Release(); - ResetElements(); if (!site) { @@ -167,13 +167,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_Site = site; m_Visual = newVisual; - // A late connection must not retain a diagnostics site after Stop. + // A published TAP owns a COM reference until it is deactivated. + CComPtr replacedTap; bool active; { std::unique_lock lock(g_TapMutex); active = g_StartButtonActive; if (active) - g_Tap = this; + { + replacedTap.Attach(PublishedTap().Detach()); + PublishedTap() = this; + } else g_ConnectStarted = false; } @@ -192,10 +196,13 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal // retain the subscribed callback so activation can be retried. if (!m_Advised) { - std::unique_lock lock(g_TapMutex); - if (g_Tap == this) - g_Tap = NULL; - g_ConnectStarted = false; + CComPtr releasedTap; + { + std::unique_lock lock(g_TapMutex); + if (PublishedTap().p == this) + releasedTap.Attach(PublishedTap().Detach()); + g_ConnectStarted = false; + } } LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); } @@ -281,15 +288,19 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal HRESULT Deactivate( void ) { - std::lock_guard lock(m_LifecycleMutex); - HRESULT hr = DeactivateLocked(); - if (SUCCEEDED(hr) && !g_StartButtonActive) + CComPtr releasedTap; + HRESULT hr; { - std::unique_lock tapLock(g_TapMutex); - if (g_Tap == this) + std::lock_guard lock(m_LifecycleMutex); + hr = DeactivateLocked(); + if (SUCCEEDED(hr)) { - g_Tap = NULL; - g_ConnectStarted = false; + std::unique_lock tapLock(g_TapMutex); + if (!g_StartButtonActive && PublishedTap().p == this) + { + releasedTap.Attach(PublishedTap().Detach()); + g_ConnectStarted = false; + } } } return hr; @@ -907,10 +918,16 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal std::unordered_map m_Elements; }; +static CComPtr &PublishedTap( void ) +{ + static CComPtr tap; + return tap; +} + static CComPtr GetTapRef( void ) { std::shared_lock lock(g_TapMutex); - return CComPtr(g_Tap); + return PublishedTap(); } class CStartButtonTapFactory: public IClassFactory @@ -1091,7 +1108,7 @@ extern "C" void StopWin11StartButtonTap( void ) // Serialize stop with SetSite publishing the global TAP pointer. std::unique_lock lock(g_TapMutex); g_StartButtonActive = false; - tap = g_Tap; + tap = PublishedTap(); } if (tap) { From 03f3d789469fcf5896149b60ae9ae14541a8bb90 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:32:35 +0200 Subject: [PATCH 30/58] Deduplicate XAML override transitions and TAP activation --- .../StartMenuHelper/Win11StartButtonTap.cpp | 125 ++++++------------ 1 file changed, 39 insertions(+), 86 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 6c3078385..cbe8fee39 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -821,83 +821,46 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal const bool allTaskbars = g_AllTaskbars; + // Both properties share the same set/restore behavior. + auto applyOverride = [&](InstanceHandle handle, const wchar_t *property, const wchar_t *value, + bool shouldOverride, bool hasOverride, bool visibility) + { + if (shouldOverride == hasOverride) + return; + HRESULT hr = shouldOverride ? SetPropertyText(handle, property, value) : + ClearPropertyByName(handle, property); + if (SUCCEEDED(hr)) + { + bool updated = shouldOverride; + if (visibility) + SetOverrideFlags(handle, &updated, NULL); + else + SetOverrideFlags(handle, NULL, &updated); + } + else if (SUCCEEDED(firstError)) + firstError = hr; + }; + 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 (SUCCEEDED(firstError)) - { - firstError = hr; - } - } - } - else if (record.hitTestOverride) - { - HRESULT hr = ClearPropertyByName(handle, L"IsHitTestVisible"); - if (SUCCEEDED(hr)) - { - bool value = false; - SetOverrideFlags(handle, NULL, &value); - } - else if (SUCCEEDED(firstError)) - { - firstError = hr; - } - } + applyOverride(handle, L"IsHitTestVisible", L"False", + enabled && target, record.hitTestOverride, false); 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 (SUCCEEDED(firstError)) - { - firstError = hr; - } - } - } - else if (record.visibilityOverride) - { - HRESULT hr = ClearPropertyByName(handle, L"Visibility"); - if (SUCCEEDED(hr)) - { - bool value = false; - SetOverrideFlags(handle, &value, NULL); - } - else if (SUCCEEDED(firstError)) - { - firstError = hr; - } - } + applyOverride(handle, L"Visibility", L"Collapsed", + enabled && target, record.visibilityOverride, true); } return firstError; @@ -1062,43 +1025,33 @@ static void EnsureConnection( void ) g_ConnectStarted = false; } -extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) +static void ActivateCurrentTap( void ) { - g_StartButtonEnabled = enabled != FALSE; - g_AllTaskbars = allTaskbars != FALSE; - g_StartButtonActive = true; - auto tap = GetTapRef(); - if (tap) + if (!tap) { - HRESULT hr = tap->Activate(); - if (FAILED(hr)) - LogToFile(STARTUP_LOG, L"Win11StartButtonTap: start activation failed 0x%08X", hr); + EnsureConnection(); return; } + HRESULT hr = tap->Activate(); + if (FAILED(hr)) + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); +} - EnsureConnection(); +extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) +{ + g_StartButtonEnabled = enabled != FALSE; + g_AllTaskbars = allTaskbars != FALSE; + g_StartButtonActive = true; + ActivateCurrentTap(); } extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) { g_StartButtonEnabled = enabled != FALSE; g_AllTaskbars = allTaskbars != FALSE; - - if (!g_StartButtonActive) - return; - - auto tap = GetTapRef(); - if (tap) - { - HRESULT hr = tap->Activate(); - if (FAILED(hr)) - LogToFile(STARTUP_LOG, L"Win11StartButtonTap: update activation failed 0x%08X", hr); - } - else - { - EnsureConnection(); - } + if (g_StartButtonActive) + ActivateCurrentTap(); } extern "C" void StopWin11StartButtonTap( void ) From 30200abde3fb2d218a4b0c469034db35a0edc513 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:41:09 +0200 Subject: [PATCH 31/58] Restore tracked XAML overrides without tree lookup and release TAP site --- .../StartMenuHelper/Win11StartButtonTap.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index cbe8fee39..a105e824c 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -148,9 +148,6 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal oldTap.Attach(PublishedTap().Detach()); } - m_Visual.Release(); - m_Site.Release(); - if (!site) { g_ConnectStarted = false; @@ -442,6 +439,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } ResetElements(); + m_Visual.Release(); + m_Site.Release(); return S_OK; } @@ -845,6 +844,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal { InstanceHandle handle = elements[i].first; StartElement record = elements[i].second; + + // Restoration depends on what we changed, not on whether a + // dynamically rebuilt XAML tree still has the same ancestry. + if (!enabled) + { + if (record.hitTestOverride) + applyOverride(handle, L"IsHitTestVisible", L"False", false, true, false); + if (record.visibilityOverride) + applyOverride(handle, L"Visibility", L"Collapsed", false, true, true); + continue; + } if (record.isStartControl) { bool target = allTaskbars || !primaryStart || handle == primaryStart; From 58a9ffafae0e959c2942a36a986541076e0a4f3e Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:56:29 +0200 Subject: [PATCH 32/58] Avoid XAML diagnostics connection when replacement Start button is disabled --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index a105e824c..682167d7a 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -1035,12 +1035,14 @@ static void EnsureConnection( void ) g_ConnectStarted = false; } -static void ActivateCurrentTap( void ) +static void ActivateCurrentTap( bool enabled ) { auto tap = GetTapRef(); if (!tap) { - EnsureConnection(); + // A disabled replacement needs no injected XAML diagnostics session. + if (enabled) + EnsureConnection(); return; } HRESULT hr = tap->Activate(); @@ -1053,7 +1055,7 @@ extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) g_StartButtonEnabled = enabled != FALSE; g_AllTaskbars = allTaskbars != FALSE; g_StartButtonActive = true; - ActivateCurrentTap(); + ActivateCurrentTap(enabled != FALSE); } extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) @@ -1061,7 +1063,7 @@ extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) g_StartButtonEnabled = enabled != FALSE; g_AllTaskbars = allTaskbars != FALSE; if (g_StartButtonActive) - ActivateCurrentTap(); + ActivateCurrentTap(enabled != FALSE); } extern "C" void StopWin11StartButtonTap( void ) From e9039e362cd0195b8f48925f45a711669496adbf Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:58:17 +0200 Subject: [PATCH 33/58] Load Windows 11 TAP helper only when replacement is enabled --- .../StartMenuDLL/Win11StartButton.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp index 5f03a52ba..f3f7eefd8 100644 --- a/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp +++ b/Src/StartMenu/StartMenuDLL/Win11StartButton.cpp @@ -81,18 +81,28 @@ static bool LoadStartButtonTap( void ) void StartWin11StartButtonMonitor( void ) { - if (!IsWin11() || !LoadStartButtonTap()) + if (!IsWin11() || !GetSettingBool(L"EnableStartButton") || !LoadStartButtonTap()) return; - g_StartButtonTapStart(GetSettingBool(L"EnableStartButton"), GetSettingBool(L"AllTaskbars")); + g_StartButtonTapStart(TRUE, GetSettingBool(L"AllTaskbars")); } void UpdateWin11StartButtonMonitor( void ) { - if (!IsWin11() || !g_StartButtonTapModule) + if (!IsWin11()) return; - g_StartButtonTapUpdate(GetSettingBool(L"EnableStartButton"), GetSettingBool(L"AllTaskbars")); + BOOL enabled = GetSettingBool(L"EnableStartButton"); + BOOL allTaskbars = GetSettingBool(L"AllTaskbars"); + if (!g_StartButtonTapModule) + { + // Start on first enable if the helper was not needed at startup. + if (enabled && LoadStartButtonTap()) + g_StartButtonTapStart(enabled, allTaskbars); + return; + } + + g_StartButtonTapUpdate(enabled, allTaskbars); } void StopWin11StartButtonMonitor( void ) From 88d99dd6ab7281dd83eae6c8c854c1a453a7573a Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:27:34 +0200 Subject: [PATCH 34/58] Simplify TAP connection worker cleanup without changing endpoint probing --- .../StartMenuHelper/Win11StartButtonTap.cpp | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 682167d7a..f5845ec1e 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -960,55 +960,52 @@ HRESULT GetWin11StartButtonTapClassObject( REFCLSID clsid, REFIID riid, LPVOID * typedef HRESULT (WINAPI *InitXamlDiagnosticsEx_t)( LPCWSTR, DWORD, LPCWSTR, LPCWSTR, CLSID, LPCWSTR ); -static DWORD FinishConnectThread( HMODULE moduleReference, HMODULE runtime, bool connected ) -{ - if (runtime) - FreeLibrary(runtime); - if (!connected) - g_ConnectStarted = false; - - // 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, false); - - InitXamlDiagnosticsEx_t init = (InitXamlDiagnosticsEx_t)GetProcAddress(runtime, "InitializeXamlDiagnosticsEx"); - if (!init) - return FinishConnectThread(moduleReference, runtime, false); - - HMODULE module = GetThisModule(); - wchar_t dllPath[MAX_PATH]; - if (!module || !GetModuleFileName(module, dllPath, _countof(dllPath))) - return FinishConnectThread(moduleReference, runtime, false); - - const wchar_t *endpoints[] = { L"VisualDiagConnection1", L"VisualDiagConnection2" }; HRESULT last = E_FAIL; - for (int retry = 0; retry < 8 && g_StartButtonActive; retry++) + HMODULE runtime = LoadLibraryEx(L"Windows.UI.Xaml.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (runtime) { - for (int i = 0; i < _countof(endpoints); i++) + InitXamlDiagnosticsEx_t init = (InitXamlDiagnosticsEx_t)GetProcAddress(runtime, "InitializeXamlDiagnosticsEx"); + if (init) { - last = init(endpoints[i], GetCurrentProcessId(), NULL, - dllPath, CLSID_OpenShellStartButtonTap, NULL); - if (SUCCEEDED(last)) + HMODULE module = GetThisModule(); + wchar_t dllPath[MAX_PATH]; + if (module && GetModuleFileName(module, dllPath, _countof(dllPath))) { - LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoints[i]); - return FinishConnectThread(moduleReference, runtime, true); + const wchar_t *endpoints[] = { L"VisualDiagConnection1", L"VisualDiagConnection2" }; + for (int retry = 0; retry < 8 && g_StartButtonActive; retry++) + { + for (int i = 0; i < _countof(endpoints) && g_StartButtonActive; i++) + { + last = init(endpoints[i], GetCurrentProcessId(), NULL, + dllPath, CLSID_OpenShellStartButtonTap, NULL); + if (SUCCEEDED(last)) + { + LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoints[i]); + break; + } + } + if (SUCCEEDED(last)) + break; + if (g_StartButtonActive && retry + 1 < 8) + Sleep(500); + } } } - Sleep(500); + FreeLibrary(runtime); } - LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); - return FinishConnectThread(moduleReference, runtime, false); + if (FAILED(last)) + { + g_ConnectStarted = false; + LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); + } + + // Release the worker's module reference atomically with thread termination. + FreeLibraryAndExitThread(moduleReference, 0); + return 0; } static void EnsureConnection( void ) From a26f18bf529acdb5663f59ece21d74678c39f5ae Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:36:53 +0200 Subject: [PATCH 35/58] Use direct synchronized TAP reference instead of redundant accessor --- .../StartMenuHelper/Win11StartButtonTap.cpp | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index f5845ec1e..aa0314982 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -58,7 +58,7 @@ struct StartElement }; class CWin11StartButtonTap; -static CComPtr &PublishedTap( void ); +extern CComPtr g_Tap; static std::shared_mutex g_TapMutex; static bool ContainsText( const CString &text, const wchar_t *part ) @@ -144,8 +144,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal CComPtr oldTap; { std::unique_lock lock(g_TapMutex); - if (PublishedTap().p == this) - oldTap.Attach(PublishedTap().Detach()); + if (g_Tap.p == this) + oldTap.Attach(g_Tap.Detach()); } if (!site) @@ -172,8 +172,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal active = g_StartButtonActive; if (active) { - replacedTap.Attach(PublishedTap().Detach()); - PublishedTap() = this; + replacedTap.Attach(g_Tap.Detach()); + g_Tap = this; } else g_ConnectStarted = false; @@ -196,8 +196,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal CComPtr releasedTap; { std::unique_lock lock(g_TapMutex); - if (PublishedTap().p == this) - releasedTap.Attach(PublishedTap().Detach()); + if (g_Tap.p == this) + releasedTap.Attach(g_Tap.Detach()); g_ConnectStarted = false; } } @@ -293,9 +293,9 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (SUCCEEDED(hr)) { std::unique_lock tapLock(g_TapMutex); - if (!g_StartButtonActive && PublishedTap().p == this) + if (!g_StartButtonActive && g_Tap.p == this) { - releasedTap.Attach(PublishedTap().Detach()); + releasedTap.Attach(g_Tap.Detach()); g_ConnectStarted = false; } } @@ -891,16 +891,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal std::unordered_map m_Elements; }; -static CComPtr &PublishedTap( void ) -{ - static CComPtr tap; - return tap; -} +CComPtr g_Tap; static CComPtr GetTapRef( void ) { std::shared_lock lock(g_TapMutex); - return PublishedTap(); + return g_Tap; } class CStartButtonTapFactory: public IClassFactory @@ -1070,7 +1066,7 @@ extern "C" void StopWin11StartButtonTap( void ) // Serialize stop with SetSite publishing the global TAP pointer. std::unique_lock lock(g_TapMutex); g_StartButtonActive = false; - tap = PublishedTap(); + tap = g_Tap; } if (tap) { From 22e92d947b71c1e7341f54780e66bad37ca10834 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:37:35 +0200 Subject: [PATCH 36/58] Probe unique XAML diagnostics endpoint names per attempt --- .../StartMenuHelper/Win11StartButtonTap.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index aa0314982..6cc7711e0 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -970,21 +970,17 @@ static DWORD WINAPI ConnectThread( LPVOID param ) wchar_t dllPath[MAX_PATH]; if (module && GetModuleFileName(module, dllPath, _countof(dllPath))) { - const wchar_t *endpoints[] = { L"VisualDiagConnection1", L"VisualDiagConnection2" }; for (int retry = 0; retry < 8 && g_StartButtonActive; retry++) { - for (int i = 0; i < _countof(endpoints) && g_StartButtonActive; i++) - { - last = init(endpoints[i], GetCurrentProcessId(), NULL, - dllPath, CLSID_OpenShellStartButtonTap, NULL); - if (SUCCEEDED(last)) - { - LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoints[i]); - break; - } - } + wchar_t endpoint[64]; + swprintf_s(endpoint, L"VisualDiagConnection%d", retry + 1); + last = init(endpoint, GetCurrentProcessId(), NULL, + dllPath, CLSID_OpenShellStartButtonTap, NULL); if (SUCCEEDED(last)) + { + LogToFile(STARTUP_LOG, L"Win11StartButton: connected using %s", endpoint); break; + } if (g_StartButtonActive && retry + 1 < 8) Sleep(500); } From 277b6c81f5971bcdb800f4eace1cf4c4ba0188fd Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:41:51 +0200 Subject: [PATCH 37/58] Prevent visual-tree callbacks from queuing apply work during TAP shutdown --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 6cc7711e0..20cb1cf44 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -267,7 +267,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal // AdviseVisualTreeChange replays the existing tree synchronously. Do not // queue partial-state work during that replay; ActivateLocked performs one // complete apply after Advise has returned and the dispatch window exists. - if (interesting && m_Advised) + if (interesting && m_Advised && m_AllowEnable) RequestApply(false, g_StartButtonActive && g_StartButtonEnabled); return S_OK; } From 411800ab68714da4a4a6146088cb9b77216a3e5e Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:42:10 +0200 Subject: [PATCH 38/58] Keep TAP dispatch alive until visual-tree unadvise succeeds --- .../StartMenuHelper/Win11StartButtonTap.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 20cb1cf44..04a3ba2e5 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -419,15 +419,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return HRESULT_FROM_WIN32(ERROR_INVALID_WINDOW_HANDLE); } - // Unadvise may release the final XAML callback reference. - // The dispatch window must be gone before that can happen. - HRESULT hr = DestroyDispatchWindow(); - if (FAILED(hr)) - return hr; - + // Keep the dispatch window available if Unadvise fails: the callback + // remains registered and shutdown must remain retryable. Callers hold + // a COM reference to this TAP while deactivation is in progress. if (m_Visual && m_Advised) { - hr = m_Visual->UnadviseVisualTreeChange( + HRESULT hr = m_Visual->UnadviseVisualTreeChange( static_cast(this)); if (FAILED(hr)) { @@ -438,6 +435,10 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal m_Advised = false; } + HRESULT hr = DestroyDispatchWindow(); + if (FAILED(hr)) + return hr; + ResetElements(); m_Visual.Release(); m_Site.Release(); From 1e4e2b89c4867c4ba9737e679d2a4a29aad3f89a Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:46:49 +0200 Subject: [PATCH 39/58] Stop XAML monitor after taskbar cleanup to prevent redundant restart --- Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp index 157027cd4..3f873aad9 100644 --- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp +++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp @@ -3291,7 +3291,6 @@ static DWORD WINAPI ExitThreadProc( void *param ) static void CleanStartMenuDLL( void ) { - StopWin11StartButtonMonitor(); ClearIatHook(g_DwmpBTRHook); g_DwmpBTRHook=NULL; @@ -3345,6 +3344,7 @@ static void CleanStartMenuDLL( void ) } ResetHotCorners(); UpdateTaskBars(TASKBAR_CLEAR); + StopWin11StartButtonMonitor(); g_WinStartButton=NULL; for (id_taskbar_map::const_iterator it=g_TaskbarInfos.begin();it!=g_TaskbarInfos.end();++it) { From 468efc19dac670f13dec0f44afc378c401a7bbd3 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:49:02 +0200 Subject: [PATCH 40/58] Report actual Win32 failure from synchronous TAP dispatch --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 04a3ba2e5..aedf7585c 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -332,9 +332,13 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (synchronous) { DWORD_PTR result = 0; + SetLastError(ERROR_SUCCESS); if (!SendMessageTimeout(dispatch, WM_OS_STARTBUTTON_APPLY, enabled ? 1 : 0, 0, SMTO_ABORTIFHUNG | SMTO_BLOCK, 2000, &result)) - return HRESULT_FROM_WIN32(ERROR_TIMEOUT); + { + DWORD error = GetLastError(); + return HRESULT_FROM_WIN32(error ? error : ERROR_TIMEOUT); + } return static_cast(result); } From 6ff655ead9ecdf8f027333d7e0fbc7517645d509 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:50:29 +0200 Subject: [PATCH 41/58] Inline single-use TAP reference lookup and remove helper --- .../StartMenuHelper/Win11StartButtonTap.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index aedf7585c..8c455e9d9 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -898,12 +898,6 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal CComPtr g_Tap; -static CComPtr GetTapRef( void ) -{ - std::shared_lock lock(g_TapMutex); - return g_Tap; -} - class CStartButtonTapFactory: public IClassFactory { public: @@ -1031,7 +1025,11 @@ static void EnsureConnection( void ) static void ActivateCurrentTap( bool enabled ) { - auto tap = GetTapRef(); + CComPtr tap; + { + std::shared_lock lock(g_TapMutex); + tap = g_Tap; + } if (!tap) { // A disabled replacement needs no injected XAML diagnostics session. From 7b0f05a67f720d5fff948296e6f3404c2609025f Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:57:50 +0200 Subject: [PATCH 42/58] Reject truncated helper DLL paths before XAML diagnostics connection --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 8c455e9d9..d780d96e2 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -967,7 +967,8 @@ static DWORD WINAPI ConnectThread( LPVOID param ) { HMODULE module = GetThisModule(); wchar_t dllPath[MAX_PATH]; - if (module && GetModuleFileName(module, dllPath, _countof(dllPath))) + DWORD pathLength = module ? GetModuleFileName(module, dllPath, _countof(dllPath)) : 0; + if (pathLength > 0 && pathLength < _countof(dllPath)) { for (int retry = 0; retry < 8 && g_StartButtonActive; retry++) { From e1e86f18a56cf2b51d3947953c4e46fa248db4b3 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:48:10 +0200 Subject: [PATCH 43/58] Simplify SetSite to single-site ownership and explicit activation lifecycle --- .../StartMenuHelper/Win11StartButtonTap.cpp | 82 +++++++++---------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index d780d96e2..06c7585a4 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -134,73 +134,65 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal { std::lock_guard lifecycleLock(m_LifecycleMutex); - // A diagnostics endpoint can call SetSite again on the same TAP object. - // Tear down the previous subscription first so callbacks never outlive - // the site/service state they were registered against. - HRESULT hr = DeactivateLocked(); - if (FAILED(hr)) - return hr; - - CComPtr oldTap; - { - std::unique_lock lock(g_TapMutex); - if (g_Tap.p == this) - oldTap.Attach(g_Tap.Detach()); - } - if (!site) { - g_ConnectStarted = false; + HRESULT hr = DeactivateLocked(); + if (FAILED(hr)) + return hr; + + // Keep a local reference while releasing the published one. + CComPtr releasedTap; + { + std::unique_lock lock(g_TapMutex); + if (g_Tap.p == this) + releasedTap.Attach(g_Tap.Detach()); + g_ConnectStarted = false; + } return S_OK; } - CComPtr newVisual; - hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&newVisual); + // A TAP has one diagnostics site. Replacing it would make callback + // ownership and unadvise ordering ambiguous. + if (m_Site) + return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED); + + CComPtr visual; + HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&visual); if (FAILED(hr)) return hr; - if (!newVisual) + if (!visual) return E_NOINTERFACE; - m_Site = site; - m_Visual = newVisual; - - // A published TAP owns a COM reference until it is deactivated. - CComPtr replacedTap; - bool active; { std::unique_lock lock(g_TapMutex); - active = g_StartButtonActive; - if (active) + if (!g_StartButtonActive) { - replacedTap.Attach(g_Tap.Detach()); - g_Tap = this; - } - else g_ConnectStarted = false; + return S_OK; + } } - if (!active) + + m_Site = site; + m_Visual = visual; + hr = ActivateLocked(); + if (FAILED(hr) && !m_Advised) { m_Visual.Release(); m_Site.Release(); - return S_OK; } - hr = ActivateLocked(); - if (FAILED(hr)) + if (SUCCEEDED(hr) || m_Advised) { - // A failed activation without a subscription cannot retain a - // non-owning global TAP pointer. A failed unadvise, however, must - // retain the subscribed callback so activation can be retried. - if (!m_Advised) + CComPtr replacedTap; { - CComPtr releasedTap; - { - std::unique_lock lock(g_TapMutex); - if (g_Tap.p == this) - releasedTap.Attach(g_Tap.Detach()); - g_ConnectStarted = false; - } + std::unique_lock lock(g_TapMutex); + replacedTap.Attach(g_Tap.Detach()); + g_Tap = this; } + } + else + { + g_ConnectStarted = false; LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); } return hr; From 15605127e3dcb871d20c1b5b72419944ca394708 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:48:25 +0200 Subject: [PATCH 44/58] Guard SetSite publication against concurrent shutdown --- .../StartMenuHelper/Win11StartButtonTap.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 06c7585a4..2311023fa 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -184,10 +184,24 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (SUCCEEDED(hr) || m_Advised) { CComPtr replacedTap; + bool stopped; { std::unique_lock lock(g_TapMutex); - replacedTap.Attach(g_Tap.Detach()); - g_Tap = this; + stopped = !g_StartButtonActive; + if (!stopped) + { + replacedTap.Attach(g_Tap.Detach()); + g_Tap = this; + } + } + if (stopped) + { + HRESULT shutdown = DeactivateLocked(); + if (FAILED(shutdown)) + { + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: late shutdown failed 0x%08X", shutdown); + return shutdown; + } } } else From 8eef672289e96d0655eaaf6ab62f8f3dacb068a7 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:48:42 +0200 Subject: [PATCH 45/58] Reset TAP connect guard on late site shutdown --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 2311023fa..03d17ca10 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -196,6 +196,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } if (stopped) { + g_ConnectStarted = false; HRESULT shutdown = DeactivateLocked(); if (FAILED(shutdown)) { From 19b393f3da8de7c0e1d7faa6add732e2171a3ab3 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:50:47 +0200 Subject: [PATCH 46/58] Reject competing TAP instances without replacing the subscribed owner --- .../StartMenuHelper/Win11StartButtonTap.cpp | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 03d17ca10..3ef068708 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -170,6 +170,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal g_ConnectStarted = false; return S_OK; } + if (g_Tap && g_Tap.p != this) + return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); } m_Site = site; @@ -183,26 +185,29 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (SUCCEEDED(hr) || m_Advised) { - CComPtr replacedTap; bool stopped; + bool competingTap; { std::unique_lock lock(g_TapMutex); stopped = !g_StartButtonActive; - if (!stopped) - { - replacedTap.Attach(g_Tap.Detach()); + competingTap = g_Tap && g_Tap.p != this; + if (!stopped && !competingTap) g_Tap = this; - } } - if (stopped) + if (stopped || competingTap) { - g_ConnectStarted = false; HRESULT shutdown = DeactivateLocked(); if (FAILED(shutdown)) { - LogToFile(STARTUP_LOG, L"Win11StartButtonTap: late shutdown failed 0x%08X", shutdown); + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: rejected site cleanup failed 0x%08X", shutdown); return shutdown; } + if (stopped) + { + g_ConnectStarted = false; + return S_OK; + } + return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); } } else From 883a7cc93498db59549798357d6fff84415d8686 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:54:08 +0200 Subject: [PATCH 47/58] Use one publication result instead of duplicate TAP race flags --- .../StartMenuHelper/Win11StartButtonTap.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 3ef068708..a05b9a227 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -185,16 +185,17 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (SUCCEEDED(hr) || m_Advised) { - bool stopped; - bool competingTap; + HRESULT publishResult = S_OK; { std::unique_lock lock(g_TapMutex); - stopped = !g_StartButtonActive; - competingTap = g_Tap && g_Tap.p != this; - if (!stopped && !competingTap) + if (!g_StartButtonActive) + publishResult = S_FALSE; + else if (g_Tap && g_Tap.p != this) + publishResult = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + else g_Tap = this; } - if (stopped || competingTap) + if (publishResult != S_OK) { HRESULT shutdown = DeactivateLocked(); if (FAILED(shutdown)) @@ -202,12 +203,12 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal LogToFile(STARTUP_LOG, L"Win11StartButtonTap: rejected site cleanup failed 0x%08X", shutdown); return shutdown; } - if (stopped) + if (publishResult == S_FALSE) { g_ConnectStarted = false; return S_OK; } - return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + return publishResult; } } else From 59abedc24a06811eeb71ddf9f728af9f6fb750b5 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:54:27 +0200 Subject: [PATCH 48/58] Prevent stale TAP instances from resetting active connection state --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index a05b9a227..01bc00908 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -146,7 +146,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal std::unique_lock lock(g_TapMutex); if (g_Tap.p == this) releasedTap.Attach(g_Tap.Detach()); - g_ConnectStarted = false; + if (!g_Tap) + g_ConnectStarted = false; } return S_OK; } @@ -213,7 +214,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } else { - g_ConnectStarted = false; + { + std::unique_lock lock(g_TapMutex); + if (!g_Tap) + g_ConnectStarted = false; + } LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); } return hr; From 20ab1ad4f25c2a8962df6ce6894593a3006636c1 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:58:06 +0200 Subject: [PATCH 49/58] Keep late TAP shutdown from clearing a newer connection attempt --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 01bc00908..a04b46d49 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -206,7 +206,9 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } if (publishResult == S_FALSE) { - g_ConnectStarted = false; + std::unique_lock lock(g_TapMutex); + if (!g_StartButtonActive && !g_Tap) + g_ConnectStarted = false; return S_OK; } return publishResult; From aa04fff4a8db6b1a39f561c1517651baf6a6304a Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:03:25 +0200 Subject: [PATCH 50/58] Synchronize GetSite with SetSite without calling COM under the lifecycle lock --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index a04b46d49..4ec2874e9 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -231,9 +231,13 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (!ppv) return E_POINTER; *ppv = NULL; - if (!m_Site) - return E_FAIL; - return m_Site->QueryInterface(riid, ppv); + + CComPtr site; + { + std::lock_guard lock(m_LifecycleMutex); + site = m_Site; + } + return site ? site->QueryInterface(riid, ppv) : E_FAIL; } STDMETHODIMP OnVisualTreeChange( ParentChildRelation relation, VisualElement element, VisualMutationType mutationType ) From 4279bc730e4f9fc45f31313db9ca91ab6edd5385 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:06:29 +0200 Subject: [PATCH 51/58] Make GetSite reentrancy-safe using existing state mutex for site snapshots --- .../StartMenuHelper/Win11StartButtonTap.cpp | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 4ec2874e9..f198a5c41 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -154,8 +154,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal // A TAP has one diagnostics site. Replacing it would make callback // ownership and unadvise ordering ambiguous. - if (m_Site) - return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED); + { + std::lock_guard lock(m_Mutex); + if (m_Site) + return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED); + } CComPtr visual; HRESULT hr = site->QueryInterface(__uuidof(IVisualTreeService), (void**)&visual); @@ -175,13 +178,21 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); } - m_Site = site; + CComPtr newSite(site); + { + std::lock_guard lock(m_Mutex); + m_Site.Attach(newSite.Detach()); + } m_Visual = visual; hr = ActivateLocked(); if (FAILED(hr) && !m_Advised) { m_Visual.Release(); - m_Site.Release(); + CComPtr releasedSite; + { + std::lock_guard lock(m_Mutex); + releasedSite.Attach(m_Site.Detach()); + } } if (SUCCEEDED(hr) || m_Advised) @@ -234,7 +245,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal CComPtr site; { - std::lock_guard lock(m_LifecycleMutex); + std::lock_guard lock(m_Mutex); site = m_Site; } return site ? site->QueryInterface(riid, ppv) : E_FAIL; @@ -469,7 +480,11 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal ResetElements(); m_Visual.Release(); - m_Site.Release(); + CComPtr releasedSite; + { + std::lock_guard lock(m_Mutex); + releasedSite.Attach(m_Site.Detach()); + } return S_OK; } @@ -910,7 +925,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal std::atomic_bool m_AllowEnable{ false }; std::atomic m_Dispatch{ NULL }; std::mutex m_LifecycleMutex; - std::mutex m_Mutex; + std::mutex m_Mutex; // Protects the site reference and visual-element state. CComPtr m_Site; CComPtr m_Visual; InstanceHandle m_PrimaryStart = 0; From 2bf09c8b0ad3dddda54ef58c22d0cc61261d3bda Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:15:04 +0200 Subject: [PATCH 52/58] Reserve TAP ownership before XAML subscription and simplify SetSite rollback --- .../StartMenuHelper/Win11StartButtonTap.cpp | 58 ++++++------------- 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index f198a5c41..735dddfdd 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -167,6 +167,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (!visual) return E_NOINTERFACE; + // Reserve the only published TAP before AdviseVisualTreeChange can + // register a callback. A second TAP must never reach that call. { std::unique_lock lock(g_TapMutex); if (!g_StartButtonActive) @@ -176,6 +178,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } if (g_Tap && g_Tap.p != this) return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + g_Tap = this; } CComPtr newSite(site); @@ -185,54 +188,29 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal } m_Visual = visual; hr = ActivateLocked(); - if (FAILED(hr) && !m_Advised) + if (FAILED(hr)) { - m_Visual.Release(); - CComPtr releasedSite; - { - std::lock_guard lock(m_Mutex); - releasedSite.Attach(m_Site.Detach()); - } - } + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); - if (SUCCEEDED(hr) || m_Advised) - { - HRESULT publishResult = S_OK; + // If Unadvise failed, retain the owner and its callback until a + // later deactivation can complete. Otherwise release the claim. + if (!m_Advised) { - std::unique_lock lock(g_TapMutex); - if (!g_StartButtonActive) - publishResult = S_FALSE; - else if (g_Tap && g_Tap.p != this) - publishResult = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); - else - g_Tap = this; - } - if (publishResult != S_OK) - { - HRESULT shutdown = DeactivateLocked(); - if (FAILED(shutdown)) - { - LogToFile(STARTUP_LOG, L"Win11StartButtonTap: rejected site cleanup failed 0x%08X", shutdown); - return shutdown; - } - if (publishResult == S_FALSE) + CComPtr releasedTap; { std::unique_lock lock(g_TapMutex); - if (!g_StartButtonActive && !g_Tap) + if (g_Tap.p == this) + releasedTap.Attach(g_Tap.Detach()); + if (!g_Tap) g_ConnectStarted = false; - return S_OK; } - return publishResult; - } - } - else - { - { - std::unique_lock lock(g_TapMutex); - if (!g_Tap) - g_ConnectStarted = false; + m_Visual.Release(); + CComPtr releasedSite; + { + std::lock_guard lock(m_Mutex); + releasedSite.Attach(m_Site.Detach()); + } } - LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); } return hr; } From 70bd5440617ae35d71346f5d53e526213dfb4867 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:45:56 +0200 Subject: [PATCH 53/58] Release TAP owner after successful Unadvise even when Start races Stop --- .../StartMenuHelper/Win11StartButtonTap.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 735dddfdd..e1e93cb8e 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -306,7 +306,9 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (SUCCEEDED(hr)) { std::unique_lock tapLock(g_TapMutex); - if (!g_StartButtonActive && g_Tap.p == this) + // A successful teardown must release its owner even if Start + // became active while Unadvise was running. + if (g_Tap.p == this) { releasedTap.Attach(g_Tap.Detach()); g_ConnectStarted = false; @@ -1057,7 +1059,19 @@ static void ActivateCurrentTap( bool enabled ) } HRESULT hr = tap->Activate(); if (FAILED(hr)) + { LogToFile(STARTUP_LOG, L"Win11StartButtonTap: activation failed 0x%08X", hr); + + // Stop may have released the owner while this call was waiting for + // its lifecycle lock. Reconnect only if there is still no owner. + bool needsConnection; + { + std::shared_lock lock(g_TapMutex); + needsConnection = !g_Tap; + } + if (enabled && g_StartButtonActive && needsConnection) + EnsureConnection(); + } } extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) From f207d02fd50d047182d59c73f31734cf3d5d5743 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:56:59 +0200 Subject: [PATCH 54/58] Serialize TAP connection state and remove cross-thread connection flag resets --- .../StartMenuHelper/Win11StartButtonTap.cpp | 67 ++++++++++++------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index e1e93cb8e..cb0186b1c 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -35,7 +35,7 @@ static std::mutex g_DispatchClassMutex; static std::atomic_bool g_StartButtonActive{ false }; static std::atomic_bool g_StartButtonEnabled{ false }; static std::atomic_bool g_AllTaskbars{ false }; -static std::atomic_bool g_ConnectStarted{ false }; + static HMODULE GetThisModule( void ) { @@ -60,6 +60,9 @@ struct StartElement class CWin11StartButtonTap; extern CComPtr g_Tap; static std::shared_mutex g_TapMutex; +// All connection-state changes are serialized with TAP ownership. +enum class ConnectionState { Idle, Connecting, Connected }; +static ConnectionState g_ConnectionState = ConnectionState::Idle; static bool ContainsText( const CString &text, const wchar_t *part ) { @@ -146,8 +149,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal std::unique_lock lock(g_TapMutex); if (g_Tap.p == this) releasedTap.Attach(g_Tap.Detach()); - if (!g_Tap) - g_ConnectStarted = false; + if (!g_Tap && g_ConnectionState == ConnectionState::Connected) + g_ConnectionState = ConnectionState::Idle; } return S_OK; } @@ -172,10 +175,7 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal { std::unique_lock lock(g_TapMutex); if (!g_StartButtonActive) - { - g_ConnectStarted = false; return S_OK; - } if (g_Tap && g_Tap.p != this) return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); g_Tap = this; @@ -201,8 +201,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal std::unique_lock lock(g_TapMutex); if (g_Tap.p == this) releasedTap.Attach(g_Tap.Detach()); - if (!g_Tap) - g_ConnectStarted = false; + if (!g_Tap && g_ConnectionState == ConnectionState::Connected) + g_ConnectionState = ConnectionState::Idle; } m_Visual.Release(); CComPtr releasedSite; @@ -311,7 +311,8 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal if (g_Tap.p == this) { releasedTap.Attach(g_Tap.Detach()); - g_ConnectStarted = false; + if (g_ConnectionState == ConnectionState::Connected) + g_ConnectionState = ConnectionState::Idle; } } } @@ -1008,11 +1009,15 @@ static DWORD WINAPI ConnectThread( LPVOID param ) FreeLibrary(runtime); } - if (FAILED(last)) { - g_ConnectStarted = false; - LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); + std::unique_lock lock(g_TapMutex); + // Only the worker that owns Connecting may complete this attempt. + if (g_ConnectionState == ConnectionState::Connecting) + g_ConnectionState = SUCCEEDED(last) && g_StartButtonActive + ? ConnectionState::Connected : ConnectionState::Idle; } + if (FAILED(last)) + LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); // Release the worker's module reference atomically with thread termination. FreeLibraryAndExitThread(moduleReference, 0); @@ -1023,9 +1028,13 @@ static void EnsureConnection( void ) { // Connection probing retries endpoints and may sleep, so keep it off the // Explorer taskbar thread. ConnectThread is the only worker we need here. - bool expected = false; - if (!g_ConnectStarted.compare_exchange_strong(expected, true)) - return; + { + std::unique_lock lock(g_TapMutex); + if (!g_StartButtonActive || !g_StartButtonEnabled || + g_Tap || g_ConnectionState != ConnectionState::Idle) + return; + g_ConnectionState = ConnectionState::Connecting; + } HMODULE moduleReference = NULL; if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&ConnectThread, &moduleReference)) @@ -1039,8 +1048,10 @@ static void EnsureConnection( void ) FreeLibrary(moduleReference); } - // A worker was not started, so connection can be attempted again later. - g_ConnectStarted = false; + // No worker owns this reservation if thread creation failed. + std::unique_lock lock(g_TapMutex); + if (g_ConnectionState == ConnectionState::Connecting) + g_ConnectionState = ConnectionState::Idle; } static void ActivateCurrentTap( bool enabled ) @@ -1076,17 +1087,25 @@ static void ActivateCurrentTap( bool enabled ) extern "C" void StartWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) { - g_StartButtonEnabled = enabled != FALSE; - g_AllTaskbars = allTaskbars != FALSE; - g_StartButtonActive = true; + { + std::unique_lock lock(g_TapMutex); + g_StartButtonEnabled = enabled != FALSE; + g_AllTaskbars = allTaskbars != FALSE; + g_StartButtonActive = true; + } ActivateCurrentTap(enabled != FALSE); } extern "C" void UpdateWin11StartButtonTap( BOOL enabled, BOOL allTaskbars ) { - g_StartButtonEnabled = enabled != FALSE; - g_AllTaskbars = allTaskbars != FALSE; - if (g_StartButtonActive) + bool active; + { + std::unique_lock lock(g_TapMutex); + g_StartButtonEnabled = enabled != FALSE; + g_AllTaskbars = allTaskbars != FALSE; + active = g_StartButtonActive; + } + if (active) ActivateCurrentTap(enabled != FALSE); } @@ -1098,6 +1117,8 @@ extern "C" void StopWin11StartButtonTap( void ) std::unique_lock lock(g_TapMutex); g_StartButtonActive = false; tap = g_Tap; + if (!tap && g_ConnectionState == ConnectionState::Connected) + g_ConnectionState = ConnectionState::Idle; } if (tap) { From 593561f218cf97d447adc72bada7fd7018b0835a Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:58:58 +0200 Subject: [PATCH 55/58] Reconcile latest Start request after overlapping TAP Stop teardown --- Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index cb0186b1c..7933e2699 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -1059,6 +1059,8 @@ static void ActivateCurrentTap( bool enabled ) CComPtr tap; { std::shared_lock lock(g_TapMutex); + if (!g_StartButtonActive) + return; tap = g_Tap; } if (!tap) @@ -1126,4 +1128,8 @@ extern "C" void StopWin11StartButtonTap( void ) if (FAILED(hr)) LogToFile(STARTUP_LOG, L"Win11StartButtonTap: deactivate failed 0x%08X", hr); } + // Start may have won the race while Stop was waiting for teardown. + // Reconcile with the latest requested state after releasing the TAP lock. + if (g_StartButtonActive) + ActivateCurrentTap(g_StartButtonEnabled); } From 1734bd04a7a4d863fa2ee456124244bb7aecd484 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:01:45 +0200 Subject: [PATCH 56/58] Retry only interrupted failed diagnostics attempts after Stop/Start races --- .../StartMenuHelper/Win11StartButtonTap.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 7933e2699..45aa445ac 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -63,6 +63,7 @@ static std::shared_mutex g_TapMutex; // All connection-state changes are serialized with TAP ownership. enum class ConnectionState { Idle, Connecting, Connected }; static ConnectionState g_ConnectionState = ConnectionState::Idle; +static bool g_RetryInterruptedConnection = false; // Guarded by g_TapMutex. static bool ContainsText( const CString &text, const wchar_t *part ) { @@ -975,6 +976,8 @@ HRESULT GetWin11StartButtonTapClassObject( REFCLSID clsid, REFIID riid, LPVOID * typedef HRESULT (WINAPI *InitXamlDiagnosticsEx_t)( LPCWSTR, DWORD, LPCWSTR, LPCWSTR, CLSID, LPCWSTR ); +static void EnsureConnection( void ); + static DWORD WINAPI ConnectThread( LPVOID param ) { HMODULE moduleReference = (HMODULE)param; @@ -1009,15 +1012,24 @@ static DWORD WINAPI ConnectThread( LPVOID param ) FreeLibrary(runtime); } + bool retry = false; { std::unique_lock lock(g_TapMutex); - // Only the worker that owns Connecting may complete this attempt. + // A stopped worker owns Connecting until its final result is known. + // Retry just once if a newer Start overlapped its failed attempt. if (g_ConnectionState == ConnectionState::Connecting) + { + retry = FAILED(last) && g_RetryInterruptedConnection && + g_StartButtonActive && g_StartButtonEnabled && !g_Tap; g_ConnectionState = SUCCEEDED(last) && g_StartButtonActive ? ConnectionState::Connected : ConnectionState::Idle; + } + g_RetryInterruptedConnection = false; } if (FAILED(last)) LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); + if (retry) + EnsureConnection(); // Release the worker's module reference atomically with thread termination. FreeLibraryAndExitThread(moduleReference, 0); @@ -1034,6 +1046,7 @@ static void EnsureConnection( void ) g_Tap || g_ConnectionState != ConnectionState::Idle) return; g_ConnectionState = ConnectionState::Connecting; + g_RetryInterruptedConnection = false; } HMODULE moduleReference = NULL; @@ -1119,6 +1132,8 @@ extern "C" void StopWin11StartButtonTap( void ) std::unique_lock lock(g_TapMutex); g_StartButtonActive = false; tap = g_Tap; + if (g_ConnectionState == ConnectionState::Connecting) + g_RetryInterruptedConnection = true; if (!tap && g_ConnectionState == ConnectionState::Connected) g_ConnectionState = ConnectionState::Idle; } From 1712e1c22a1985091f93aad6067642a31417fdb1 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:05:54 +0200 Subject: [PATCH 57/58] Recover one interrupted Start when TAP worker creation fails --- .../StartMenuHelper/Win11StartButtonTap.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 45aa445ac..8c4af54c3 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -1062,9 +1062,19 @@ static void EnsureConnection( void ) } // No worker owns this reservation if thread creation failed. - std::unique_lock lock(g_TapMutex); - if (g_ConnectionState == ConnectionState::Connecting) - g_ConnectionState = ConnectionState::Idle; + bool retry = false; + { + std::unique_lock lock(g_TapMutex); + if (g_ConnectionState == ConnectionState::Connecting) + { + retry = g_RetryInterruptedConnection && g_StartButtonActive && + g_StartButtonEnabled && !g_Tap; + g_ConnectionState = ConnectionState::Idle; + g_RetryInterruptedConnection = false; + } + } + if (retry) + EnsureConnection(); } static void ActivateCurrentTap( bool enabled ) From 5d8315885cf7351737d96a1f4f6098bef727b5ea Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:09:02 +0200 Subject: [PATCH 58/58] Fold interrupted-worker retry into one TAP connection state --- .../StartMenuHelper/Win11StartButtonTap.cpp | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp index 8c4af54c3..748efa98d 100644 --- a/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp +++ b/Src/StartMenu/StartMenuHelper/Win11StartButtonTap.cpp @@ -61,9 +61,8 @@ class CWin11StartButtonTap; extern CComPtr g_Tap; static std::shared_mutex g_TapMutex; // All connection-state changes are serialized with TAP ownership. -enum class ConnectionState { Idle, Connecting, Connected }; +enum class ConnectionState { Idle, Connecting, Interrupted, Connected }; static ConnectionState g_ConnectionState = ConnectionState::Idle; -static bool g_RetryInterruptedConnection = false; // Guarded by g_TapMutex. static bool ContainsText( const CString &text, const wchar_t *part ) { @@ -1015,16 +1014,16 @@ static DWORD WINAPI ConnectThread( LPVOID param ) bool retry = false; { std::unique_lock lock(g_TapMutex); - // A stopped worker owns Connecting until its final result is known. - // Retry just once if a newer Start overlapped its failed attempt. - if (g_ConnectionState == ConnectionState::Connecting) + // Interrupted retains the worker reservation across Stop/Start. + // A failed interrupted attempt may retry once for the latest Start. + if (g_ConnectionState == ConnectionState::Connecting || + g_ConnectionState == ConnectionState::Interrupted) { - retry = FAILED(last) && g_RetryInterruptedConnection && + retry = FAILED(last) && g_ConnectionState == ConnectionState::Interrupted && g_StartButtonActive && g_StartButtonEnabled && !g_Tap; g_ConnectionState = SUCCEEDED(last) && g_StartButtonActive ? ConnectionState::Connected : ConnectionState::Idle; } - g_RetryInterruptedConnection = false; } if (FAILED(last)) LogToFile(STARTUP_LOG, L"Win11StartButton: connection failed 0x%08X", last); @@ -1046,7 +1045,6 @@ static void EnsureConnection( void ) g_Tap || g_ConnectionState != ConnectionState::Idle) return; g_ConnectionState = ConnectionState::Connecting; - g_RetryInterruptedConnection = false; } HMODULE moduleReference = NULL; @@ -1065,12 +1063,12 @@ static void EnsureConnection( void ) bool retry = false; { std::unique_lock lock(g_TapMutex); - if (g_ConnectionState == ConnectionState::Connecting) + if (g_ConnectionState == ConnectionState::Connecting || + g_ConnectionState == ConnectionState::Interrupted) { - retry = g_RetryInterruptedConnection && g_StartButtonActive && - g_StartButtonEnabled && !g_Tap; + retry = g_ConnectionState == ConnectionState::Interrupted && + g_StartButtonActive && g_StartButtonEnabled && !g_Tap; g_ConnectionState = ConnectionState::Idle; - g_RetryInterruptedConnection = false; } } if (retry) @@ -1143,7 +1141,7 @@ extern "C" void StopWin11StartButtonTap( void ) g_StartButtonActive = false; tap = g_Tap; if (g_ConnectionState == ConnectionState::Connecting) - g_RetryInterruptedConnection = true; + g_ConnectionState = ConnectionState::Interrupted; if (!tap && g_ConnectionState == ConnectionState::Connected) g_ConnectionState = ConnectionState::Idle; }