From 21e3d35cf0d42f3da77127b645cf34d8583691b8 Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:08:45 +0200 Subject: [PATCH 1/2] Hide the native Windows 11 Start button custom Start button is enabled Windows 11 renders the Start button through XAML, so the older HWND-based hiding logic cannot remove the native glyph. This caused custom Open-Shell buttons to be drawn on top of the Windows Start icon. The change uses the Windows XAML diagnostics TAP to: - collapse the native Start glyph - disable hit testing on the underlying native Start control - preserve the native control's layout slot - restore the original XAML properties when replacement is disabled or Open-Shell exits - respect the All taskbars setting The replacement button itself is not moved or resized by this code, so Windows remains responsible for centered taskbar layout. TAP mechanism usage was inspired by TranslucentTB and UWPSpy projects. Fixes #1631 --- Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 3 + .../StartMenuDLL/StartMenuDLL.vcxproj | 2 + .../StartMenuDLL/StartMenuDLL.vcxproj.filters | 8 +- Src/StartMenu/StartMenuDLL/TapControl.cpp | 89 +++ Src/StartMenu/StartMenuDLL/TapControl.h | 9 + Src/StartMenu/StartMenuHelper/ExplorerTap.cpp | 610 ++++++++++++++++++ Src/StartMenu/StartMenuHelper/ExplorerTap.h | 10 + .../StartMenuHelper/StartMenuHelper.vcxproj | 2 + .../StartMenuHelper.vcxproj.filters | 6 + .../StartMenuHelper/StartMenuHelper32.def | 2 + .../StartMenuHelper/StartMenuHelper64.def | 2 + 11 files changed, 742 insertions(+), 1 deletion(-) create mode 100644 Src/StartMenu/StartMenuDLL/TapControl.cpp create mode 100644 Src/StartMenu/StartMenuDLL/TapControl.h create mode 100644 Src/StartMenu/StartMenuHelper/ExplorerTap.cpp create mode 100644 Src/StartMenu/StartMenuHelper/ExplorerTap.h diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp index 583c5b765..2a333403c 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 "TapControl.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); } + UpdateTap(); } /////////////////////////////////////////////////////////////////////////////// @@ -3340,6 +3342,7 @@ static void CleanStartMenuDLL( void ) } ResetHotCorners(); UpdateTaskBars(TASKBAR_CLEAR); + StopTap(); g_WinStartButton=NULL; for (id_taskbar_map::const_iterator it=g_TaskbarInfos.begin();it!=g_TaskbarInfos.end();++it) { diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj b/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj index 0412e588c..fc16d0c0a 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..527038eb6 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 @@ -204,4 +210,4 @@ Resource Files - + \ No newline at end of file diff --git a/Src/StartMenu/StartMenuDLL/TapControl.cpp b/Src/StartMenu/StartMenuDLL/TapControl.cpp new file mode 100644 index 000000000..9f83e11bc --- /dev/null +++ b/Src/StartMenu/StartMenuDLL/TapControl.cpp @@ -0,0 +1,89 @@ +#include "stdafx.h" +#include "../StartMenuHelper/ExplorerTap.h" +#include "TapControl.h" +#include "Settings.h" +#include "LogManager.h" +#include "ResourceHelper.h" + +using UpdateExplorerTap_t = decltype(&UpdateExplorerTap); +using StopExplorerTap_t = decltype(&StopExplorerTap); + +static HMODULE g_explorerTapModule = NULL; +static UpdateExplorerTap_t g_updateExplorerTap = NULL; +static StopExplorerTap_t g_stopExplorerTap = NULL; + +#ifdef _WIN64 +#define TAP_HELPER_NAME L"StartMenuHelper64.dll" +#else +#define TAP_HELPER_NAME L"StartMenuHelper32.dll" +#endif + +static bool LoadExplorerTap() +{ + if (g_explorerTapModule) + return true; + + // Prefer a helper next to StartMenuDLL for local development builds + wchar_t path[MAX_PATH]; + GetModuleFileName(g_Instance, path, _countof(path)); + PathRemoveFileSpec(path); + PathAppend(path, TAP_HELPER_NAME); + + auto module = LoadLibrary(path); + + // Fall-back to System32 (standard location for installed versions). + if (!module) + module = LoadLibraryEx(TAP_HELPER_NAME, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (!module) + { + LogToFile(STARTUP_LOG, L"TapControl: unable to load TAP helper 0x%08X", GetLastError()); + return false; + } + + auto update = (UpdateExplorerTap_t)GetProcAddress(module, "UpdateExplorerTap"); + auto stop = (StopExplorerTap_t)GetProcAddress(module, "StopExplorerTap"); + if (!update || !stop) + { + LogToFile(STARTUP_LOG, L"TapControl: TAP helper exports are unavailable"); + FreeLibrary(module); + return false; + } + + g_explorerTapModule = module; + g_updateExplorerTap = update; + g_stopExplorerTap = stop; + return true; +} + +void UpdateTap() +{ + if (!IsWin11()) + return; + + ExplorerTapSettings settings{}; + settings.startButtonEnabled = GetSettingBool(L"EnableStartButton"); + settings.startButtonAllTaskbars = GetSettingBool(L"AllTaskbars"); + + // Start on first enable if the helper was not needed at startup. + if (settings.startButtonEnabled) + LoadExplorerTap(); + + if (g_updateExplorerTap) + g_updateExplorerTap(&settings); +} + +void StopTap() +{ + if (g_stopExplorerTap) + g_stopExplorerTap(); + + // Drop only StartMenuDLL's explicit reference. XAML Diagnostics owns the TAP + // module reference for the diagnostics session after a successful connection. + if (g_explorerTapModule) + FreeLibrary(g_explorerTapModule); + + g_explorerTapModule = NULL; + g_updateExplorerTap = NULL; + g_stopExplorerTap = NULL; +} diff --git a/Src/StartMenu/StartMenuDLL/TapControl.h b/Src/StartMenu/StartMenuDLL/TapControl.h new file mode 100644 index 000000000..97e9557b6 --- /dev/null +++ b/Src/StartMenu/StartMenuDLL/TapControl.h @@ -0,0 +1,9 @@ +#pragma once + +// Windows 11 draws the native Taskbar and Start button in XAML. +// These helpers control our TAP mechanism that allows to do changes in the XAML tree. + +// Send updated settings to the TAP. +void UpdateTap(); +// Stop the TAP and clean up resources. +void StopTap(); diff --git a/Src/StartMenu/StartMenuHelper/ExplorerTap.cpp b/Src/StartMenu/StartMenuHelper/ExplorerTap.cpp new file mode 100644 index 000000000..87d309d78 --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ExplorerTap.cpp @@ -0,0 +1,610 @@ +// Windows 11 draws the native Taskbar and Start button in XAML. +// We use XAML Diagnostics TAP to modify the XAML tree. + +// This is the TAP implementation that is loaded into Explorer's process by XAML Diagnostics. +// It allows us to examine XAML tree changes and modify properties of XAML elements. + +// Currently we support: +// - Hiding the Start button glyph (the icon) and disabling hit testing on the Start button control. + +#include "stdafx.h" +#include "ExplorerTap.h" +#include "Settings.h" +#include "StringUtils.h" +#include "..\StartMenuDLL\LogManager.h" + +#undef GetCurrentTime +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +using namespace Microsoft::WRL::Wrappers; + + +// TAP instance and mutex for global access. +static std::mutex g_tapMutex; +static CComPtr g_tap; +static ExplorerTapSettings g_tapSettings{}; + +static std::atomic_bool g_connectionInProgress{ false }; + + +static HMODULE GetThisModule() +{ + HMODULE module = NULL; + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)&GetThisModule, &module); + return module; +} + +// scope_exit is a simple RAII helper that executes a callable on destruction. +template +class scope_exit +{ +public: + explicit scope_exit(F&& f) : f_(std::forward(f)) {} + ~scope_exit() { f_(); } + +private: + F f_; +}; + +namespace wux = ABI::Windows::UI::Xaml; +namespace wf = ABI::Windows::Foundation; + +static const GUID CLSID_OpenShellExplorerTap = { 0x7d15741f, 0x2f3b, 0x4971, { 0xb8, 0x91, 0x6a, 0x5d, 0x42, 0xd7, 0x1a, 0x34 } }; + +class ATL_NO_VTABLE ExplorerTap : + public CComObjectRootEx, + public CComCoClass, + public IObjectWithSite, + public IVisualTreeServiceCallback2 +{ +public: + BEGIN_COM_MAP(ExplorerTap) + COM_INTERFACE_ENTRY(IObjectWithSite) + COM_INTERFACE_ENTRY(IVisualTreeServiceCallback) + COM_INTERFACE_ENTRY(IVisualTreeServiceCallback2) + END_COM_MAP() + + DECLARE_NO_REGISTRY() + DECLARE_NOT_AGGREGATABLE(ExplorerTap) + + ExplorerTap() + { + RoGetActivationFactory(HStringReference(RuntimeClass_Windows_UI_Xaml_Media_VisualTreeHelper).Get(), IID_PPV_ARGS(&m_visualTreeHelper)); + m_dispatchWindow.Create(HWND_MESSAGE, nullptr, nullptr, 0, 0, 0U, this); + } + + ~ExplorerTap() + { + if (m_dispatchWindow.IsWindow()) + m_dispatchWindow.DestroyWindow(); + } + + STDMETHODIMP SetSite(IUnknown *site) + { + // This is called by XAML Diagnostics to establish the connection. + // It is expected to be called just once (with valid site) and then once more (with NULL) to disconnect. + + // Make sure to reset the connection flag once we are finished (either successfully or with an error) + auto cleanup = scope_exit([]() { + g_connectionInProgress = false; + }); + + if (!site) + return S_OK; + + if (m_site) + return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED); + + HRESULT hr = site->QueryInterface(IID_PPV_ARGS(&m_site)); + if (FAILED(hr)) + return hr; + + hr = site->QueryInterface(IID_PPV_ARGS(&m_visualTreeService)); + if (FAILED(hr)) + return hr; + + // Advise replays the existing tree. + hr = m_visualTreeService->AdviseVisualTreeChange(static_cast(this)); + if (FAILED(hr)) + return hr; + + // We have a valid connection; store the TAP instance for global access. + { + std::unique_lock lock(g_tapMutex); + g_tap = this; + } + + return S_OK; + } + + STDMETHODIMP GetSite(REFIID riid, void **ppv) + { + if (!ppv) + return E_POINTER; + *ppv = NULL; + + return m_site->QueryInterface(riid, ppv); + } + + STDMETHODIMP OnVisualTreeChange(ParentChildRelation relation, VisualElement element, VisualMutationType mutationType) + { + bool interesting = false; + + { + if (mutationType == Remove) + { + std::lock_guard lock(m_mutex); + auto it = std::find(m_elements.begin(), m_elements.end(), element.Handle); + if (it != m_elements.end()) + { + m_elements.erase(it); + interesting = true; + } + } + else if (mutationType == Add) + { + if (IsStartControl(element)) + { + // Start button control was added. + StartElement record; + record.handle = element.Handle; + + std::lock_guard lock(m_mutex); + m_elements.push_back(record); + + interesting = true; + } + else if (InstanceHandle parent; IsStartGlyph(element, parent)) + { + // Start glyph was added. + // Store glyph handle to the parent Start button record so we can hide it later. + std::lock_guard lock(m_mutex); + auto it = std::find(m_elements.begin(), m_elements.end(), parent); + if (it != m_elements.end()) + { + it->glyph = element.Handle; + interesting = true; + } + } + } + } + + if (interesting) + RequestApply(); + + return S_OK; + } + + STDMETHODIMP OnElementStateChanged(InstanceHandle, VisualElementState, LPCWSTR) + { + return S_OK; + } + + void Update() + { + // Called when button settings were chaged. + // Apply state according to current settings. + RequestApply(); + } + + HRESULT Deactivate() + { + // Unadvise the TAP to stop receiving tree change notifications. + // This will also free reference to our TAP instance (that was held by XAML Diagnostics) and allow the TAP to be destroyed. + HRESULT hr = m_visualTreeService->UnadviseVisualTreeChange(static_cast(this)); + if (FAILED(hr)) + { + LogToFile(STARTUP_LOG, L"Win11StartButtonTap: visual tree unadvise failed 0x%08X", hr); + return hr; + } + + // Destroy the dispatch window to reject any already-queued apply work. + // Also no new work will get queued because the TAP is no longer advised. + m_dispatchWindow.DestroyWindow(); + + // Restore the original state of any elements that were modified. + { + std::unique_lock lock(g_tapMutex); + g_tapSettings.startButtonEnabled = FALSE; + } + ApplyState(); + + return S_OK; + } + +private: + + bool IsStartControl(const VisualElement& element) + { + if (!element.Type || !element.Name) + return false; + + // Type has to contain "ExperienceToggleButton" (the Start button is a subclass of that type). + if (wcsstr(element.Type, L"ExperienceToggleButton")) + { + // Older taskbar implementations may expose a distinct x:Name. + if (_wcsicmp(element.Name, L"StartButton") == 0) + return true; + + // Newer taskbar implementation uses a LaunchListButton with AutomationId "StartButton" (the glyph is a child Icon element). + if (wcscmp(element.Name, L"LaunchListButton") == 0) + { + auto automationId = GetPropertyString(element.Handle, L"Windows.UI.Xaml.Automation.AutomationProperties.AutomationId"); + if (automationId.CompareNoCase(L"StartButton") == 0) + return true; + } + } + + return false; + } + + bool IsStartGlyph(const VisualElement& element, InstanceHandle& parent) + { + if (!element.Name) + return false; + + // The Start glyph is an Icon element that is a descendant of a Start button control. + if (wcscmp(element.Name, L"Icon") == 0) + { + auto ancestor = FindStartAncestor(element.Handle); + if (ancestor) + { + parent = ancestor; + return true; + } + } + + return false; + } + + void RequestApply() + { + if (m_dispatchWindow.IsWindow()) + m_dispatchWindow.PostMessage(WM_OS_STARTBUTTON_APPLY, 0); + } + + void ApplyState() + { + ExplorerTapSettings settings{}; + { + std::unique_lock lock(g_tapMutex); + settings = g_tapSettings; + } + + const bool enabled = settings.startButtonEnabled; + const bool allTaskbars = settings.startButtonAllTaskbars; + + // Both properties share the same set/restore behavior. + auto applyOverride = [&](InstanceHandle handle, const wchar_t* property, const wchar_t* value, const wchar_t* valueType, bool shouldOverride, bool& hasOverride) + { + if (shouldOverride != hasOverride) + { + HRESULT hr = shouldOverride ? SetPropertyText(handle, property, value, valueType) : ClearPropertyByName(handle, property); + if (SUCCEEDED(hr)) + hasOverride = shouldOverride; + } + }; + + // Apply the requested state to all known Start button elements. + std::lock_guard lock(m_mutex); + for (size_t i = 0; i < m_elements.size(); i++) + { + auto& element = m_elements[i]; + + bool target = allTaskbars || (i == 0); // Only the first Start button is the main taskbar; all others are secondary taskbars. + applyOverride(element.handle, L"Windows.UI.Xaml.UIElement.IsHitTestVisible", L"False", L"Windows.Foundation.Boolean", enabled && target, element.hitTestOverride); + applyOverride(element.glyph, L"Windows.UI.Xaml.UIElement.Visibility", L"Collapsed", L"Windows.UI.Xaml.Visibility", enabled && target, element.visibilityOverride); + } + } + + template + CComPtr FromHandle(InstanceHandle handle) + { + CComPtr element; + m_site->GetIInspectableFromHandle(handle, &element); + if (element) + return CComQIPtr(element); + + return nullptr; + } + + InstanceHandle ToHandle(IInspectable* element) + { + InstanceHandle handle = 0; + m_site->GetHandleFromIInspectable(element, &handle); + return handle; + } + + // Find the nearest ancestor of the given element that is a Start button control (if any). + InstanceHandle FindStartAncestor(InstanceHandle elementHandle) + { + auto element = FromHandle(elementHandle); + if (element) + { + CComPtr parent; + m_visualTreeHelper->GetParent(element, &parent); + if (parent) + { + auto parentHandle = ToHandle(parent); + if (parentHandle) + { + { + // Check whether the parent is a Start button control. + std::unique_lock lock(m_mutex); + if (std::find(m_elements.begin(), m_elements.end(), parentHandle) != m_elements.end()) + return parentHandle; + } + + // Recurse up the tree to find a Start button ancestor. + return FindStartAncestor(parentHandle); + } + } + } + + // No Start button ancestor found. + return 0; + } + + InstanceHandle GetProperty(InstanceHandle handle, const wchar_t* name) + { + unsigned int index = 0; + if (SUCCEEDED(m_visualTreeService->GetPropertyIndex(handle, name, &index))) + { + InstanceHandle valueHandle = 0; + if (SUCCEEDED(m_visualTreeService->GetProperty(handle, index, &valueHandle))) + return valueHandle; + } + return 0; + } + + CStringW GetPropertyString(InstanceHandle handle, const wchar_t* name) + { + CStringW result; + + auto valueHandle = GetProperty(handle, name); + if (valueHandle) + { + auto value = FromHandle(valueHandle); + if (value) + { + HSTRING string{}; + if (SUCCEEDED(value->GetString(&string))) + { + result = WindowsGetStringRawBuffer(string, nullptr); + WindowsDeleteString(string); + } + } + } + + return result; + } + + HRESULT SetPropertyText(InstanceHandle handle, const wchar_t *name, const wchar_t *valueText, const wchar_t *valueType) + { + unsigned int index = 0; + HRESULT hr = m_visualTreeService->GetPropertyIndex(handle, name, &index); + if (FAILED(hr)) + return hr; + + CComBSTR type(valueType), value(valueText); + InstanceHandle created = 0; + hr = m_visualTreeService->CreateInstance(type, value, &created); + if (FAILED(hr)) + return hr; + + return m_visualTreeService->SetProperty(handle, created, index); + } + + HRESULT ClearPropertyByName(InstanceHandle handle, const wchar_t *name) + { + unsigned int index = 0; + HRESULT hr = m_visualTreeService->GetPropertyIndex(handle, name, &index); + if (FAILED(hr)) + return hr; + + return m_visualTreeService->ClearProperty(handle, index); + } + + class DispatchWindow : public CWindowImpl + { + public: + // DECLARE_WND_CLASS(L"OpenShell.ExplorerTap") + BEGIN_MSG_MAP(DispatchWindow) + MESSAGE_HANDLER(WM_NCCREATE, OnNcCreate) + MESSAGE_HANDLER(WM_OS_STARTBUTTON_APPLY, OnApply) + END_MSG_MAP() + LRESULT OnNcCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + CREATESTRUCT* create = (CREATESTRUCT*)lParam; + SetWindowLongPtr(GWLP_USERDATA, (LONG_PTR)create->lpCreateParams); + return TRUE; + } + LRESULT OnApply(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) + { + ExplorerTap* tap = (ExplorerTap*)GetWindowLongPtr(GWLP_USERDATA); + if (tap) + tap->ApplyState(); + return TRUE; + } + }; + + static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B; + + DispatchWindow m_dispatchWindow; + CComPtr m_visualTreeHelper; + CComPtr m_site; + CComPtr m_visualTreeService; + + std::mutex m_mutex; + struct StartElement + { + InstanceHandle handle = 0; + InstanceHandle glyph = 0; + bool visibilityOverride = false; + bool hitTestOverride = false; + + bool operator==(InstanceHandle handle) const + { + return this->handle == handle; + } + }; + std::vector m_elements; +}; + +OBJECT_ENTRY_AUTO(CLSID_OpenShellExplorerTap, ExplorerTap) + + +typedef HRESULT (WINAPI *InitXamlDiagnosticsEx_t)( LPCWSTR, DWORD, LPCWSTR, LPCWSTR, CLSID, LPCWSTR ); + +static DWORD WINAPI ConnectThread(LPVOID param) +{ + HRESULT hr = E_FAIL; + HMODULE runtime = LoadLibraryEx(L"Windows.UI.Xaml.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (runtime) + { + auto init = (InitXamlDiagnosticsEx_t)GetProcAddress(runtime, "InitializeXamlDiagnosticsEx"); + if (init) + { + wchar_t dllPath[MAX_PATH]; + GetModuleFileName(GetThisModule(), dllPath, _countof(dllPath)); + + for (int retry = 0; retry < 8; retry++) + { + // Try to connect to the XAML diagnostics service. This will fail if the service is not yet available (e.g. Explorer has not yet initialized its XAML tree). Retry a few times with a delay. + hr = init(L"VisualDiagConnection1", GetCurrentProcessId(), L"", dllPath, CLSID_OpenShellExplorerTap, NULL); + if (SUCCEEDED(hr)) + { + LogToFile(STARTUP_LOG, L"ExplorerTap: connected (%d attempts)", retry + 1); + break; + } + + Sleep(500); + } + } + FreeLibrary(runtime); + } + + if (FAILED(hr)) + { + LogToFile(STARTUP_LOG, L"ExplorerTap: connection failed 0x%08X", hr); + + // Allow connection attempts to be retried on the next occurrence if the thread creation failed. + g_connectionInProgress = false; + } + + // Release the worker's module reference atomically with thread termination. + FreeLibraryAndExitThread((HMODULE)param, 0); + return 0; +} + +static void EnsureConnection() +{ + // Connection probing retries endpoints and may sleep, so keep it off the + // Explorer taskbar thread. ConnectThread is the only worker we need here. + + // Prevent multiple concurrent connection attempts. If a previous attempt is still in progress, let it complete and retry on the next occurrence. + bool expected = false; + if (!g_connectionInProgress.compare_exchange_strong(expected, true)) + return; + + HMODULE moduleReference = NULL; + 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); + // The worker thread owns the module reference and will free it on exit. + return; + } + + FreeLibrary(moduleReference); + } + + // Allow connection attempts to be retried on the next occurrence if the thread creation failed. + g_connectionInProgress = false; +} + + +extern "C" void UpdateExplorerTap(const ExplorerTapSettings* settings) +{ + CComPtr tap; + { + std::unique_lock lock(g_tapMutex); + tap = g_tap; + g_tapSettings = *settings; + } + + if (tap) + { + tap->Update(); + } + else + { + if (settings->startButtonEnabled) + EnsureConnection(); + } +} + +extern "C" void StopExplorerTap() +{ + CComPtr tap; + { + std::unique_lock lock(g_tapMutex); + tap = g_tap; + g_tap = nullptr; + } + + if (tap) + { + if (SUCCEEDED(tap->Deactivate())) + { + // XAML diagnostics runtime loaded the TAP module and owns a reference for the diagnostics session. Release it now that the TAP is no longer advised. + FreeLibrary(GetThisModule()); + } + } +} + +// WinRT wrappers (these API are not available on Win7) + +#pragma warning(suppress: 4273) // C4273: 'RoGetActivationFactory': inconsistent dll linkage +HRESULT WINAPI RoGetActivationFactory(HSTRING activatableClassId, REFIID iid, void** factory) +{ + static auto p = static_cast((void*)GetProcAddress(GetModuleHandle(L"combase.dll"), "RoGetActivationFactory")); + if (p) + return p(activatableClassId, iid, factory); + return E_NOTIMPL; +} + +STDAPI WindowsCreateStringReference(PCWSTR sourceString, UINT32 length, HSTRING_HEADER* hstringHeader, HSTRING* string) +{ + static auto p = static_cast((void*)GetProcAddress(GetModuleHandle(L"combase.dll"), "WindowsCreateStringReference")); + if (p) + return p(sourceString, length, hstringHeader, string); + return E_NOTIMPL; +} + +STDAPI WindowsDeleteString(HSTRING string) +{ + static auto p = static_cast((void*)GetProcAddress(GetModuleHandle(L"combase.dll"), "WindowsDeleteString")); + if (p) + return p(string); + return E_NOTIMPL; +} + +STDAPI_(PCWSTR) WindowsGetStringRawBuffer(HSTRING string, UINT32* length) +{ + static auto p = static_cast((void*)GetProcAddress(GetModuleHandle(L"combase.dll"), "WindowsGetStringRawBuffer")); + if (p) + return p(string, length); + return nullptr; +} diff --git a/Src/StartMenu/StartMenuHelper/ExplorerTap.h b/Src/StartMenu/StartMenuHelper/ExplorerTap.h new file mode 100644 index 000000000..0b774702c --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ExplorerTap.h @@ -0,0 +1,10 @@ +#pragma once + +struct ExplorerTapSettings +{ + BOOL startButtonEnabled; + BOOL startButtonAllTaskbars; +}; + +extern "C" void UpdateExplorerTap(const ExplorerTapSettings* settings); +extern "C" void StopExplorerTap(); diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj index 3e7790656..6086f1841 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..69b95d9b6 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..89b717161 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper32.def +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper32.def @@ -8,3 +8,5 @@ EXPORTS DllRegisterServer PRIVATE DllUnregisterServer PRIVATE DllInstall PRIVATE + UpdateExplorerTap + StopExplorerTap diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper64.def b/Src/StartMenu/StartMenuHelper/StartMenuHelper64.def index dd824295a..4338b512a 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper64.def +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper64.def @@ -8,3 +8,5 @@ EXPORTS DllRegisterServer PRIVATE DllUnregisterServer PRIVATE DllInstall PRIVATE + UpdateExplorerTap + StopExplorerTap From 10877cb87d8a4b8ce9f4253576cc6dd5e84ab0fd Mon Sep 17 00:00:00 2001 From: YellowNest <59575587+YellowNest@users.noreply.github.com> Date: Sat, 17 Oct 2026 08:44:19 +0200 Subject: [PATCH 2/2] Reset visibility override state when Start glyph changes --- Src/StartMenu/StartMenuHelper/ExplorerTap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Src/StartMenu/StartMenuHelper/ExplorerTap.cpp b/Src/StartMenu/StartMenuHelper/ExplorerTap.cpp index 87d309d78..54e8bb329 100644 --- a/Src/StartMenu/StartMenuHelper/ExplorerTap.cpp +++ b/Src/StartMenu/StartMenuHelper/ExplorerTap.cpp @@ -172,6 +172,7 @@ class ATL_NO_VTABLE ExplorerTap : if (it != m_elements.end()) { it->glyph = element.Handle; + it->visibilityOverride = false; // A newly added glyph has no override yet. interesting = true; } }