// ==WindhawkMod== // @id taskbar-start-button-position // @name Start button always on the left // @description Forces the start button to be on the left of the taskbar, even when taskbar icons are centered (Windows 11 only) // @version 1.2.4 // @author m417z // @github https://github.com/m417z // @twitter https://twitter.com/m417z // @homepage https://m417z.com/ // @include StartMenuExperienceHost.exe // @include explorer.exe // @architecture x86-64 // @compilerOptions -ldwmapi -lole32 -loleaut32 -lruntimeobject -lshcore // ==/WindhawkMod== // Source code is published under The GNU General Public License v3.0. // // For bug reports and feature requests, please open an issue here: // https://github.com/ramensoftware/windhawk-mods/issues // // For pull requests, development takes place here: // https://github.com/m417z/my-windhawk-mods // ==WindhawkModReadme== /* # Start button always on the left Forces the start button to be on the left of the taskbar, even when taskbar icons are centered. Only Windows 11 is supported. ![Screenshot](https://i.imgur.com/MSKYKbE.png) */ // ==/WindhawkModReadme== // ==WindhawkModSettings== /* - startMenuOnTheLeft: true $name: Start menu on the left $description: >- Make the start menu open on the left even if taskbar icons are centered */ // ==/WindhawkModSettings== #include #include #include #include #include #include #include #undef GetCurrentTime #include #include #include #include #include #include #include #include using namespace winrt::Windows::UI::Xaml; struct { bool startMenuOnTheLeft; } g_settings; enum class Target { Explorer, StartMenuExperienceHost, }; Target g_target; std::atomic g_taskbarViewDllLoaded; std::atomic g_unloading; thread_local bool g_TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride; HWND g_searchMenuWnd; int g_searchMenuOriginalX; typedef enum MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI } MONITOR_DPI_TYPE; STDAPI GetDpiForMonitor(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY); HWND FindCurrentProcessTaskbarWnd() { HWND hTaskbarWnd = nullptr; EnumWindows( [](HWND hWnd, LPARAM lParam) -> BOOL { DWORD dwProcessId; WCHAR className[32]; if (GetWindowThreadProcessId(hWnd, &dwProcessId) && dwProcessId == GetCurrentProcessId() && GetClassName(hWnd, className, ARRAYSIZE(className)) && _wcsicmp(className, L"Shell_TrayWnd") == 0) { *reinterpret_cast(lParam) = hWnd; return FALSE; } return TRUE; }, reinterpret_cast(&hTaskbarWnd)); return hTaskbarWnd; } FrameworkElement EnumChildElements( FrameworkElement element, std::function enumCallback) { int childrenCount = Media::VisualTreeHelper::GetChildrenCount(element); for (int i = 0; i < childrenCount; i++) { auto child = Media::VisualTreeHelper::GetChild(element, i) .try_as(); if (!child) { Wh_Log(L"Failed to get child %d of %d", i + 1, childrenCount); continue; } if (enumCallback(child)) { return child; } } return nullptr; } FrameworkElement FindChildByName(FrameworkElement element, PCWSTR name) { return EnumChildElements(element, [name](FrameworkElement child) { return child.Name() == name; }); } FrameworkElement FindChildByClassName(FrameworkElement element, PCWSTR className) { return EnumChildElements(element, [className](FrameworkElement child) { return winrt::get_class_name(child) == className; }); } bool ApplyStyle(XamlRoot xamlRoot) { FrameworkElement xamlRootContent = xamlRoot.Content().try_as(); FrameworkElement taskbarFrameRepeater = nullptr; FrameworkElement child = xamlRootContent; if (child && (child = FindChildByClassName(child, L"Taskbar.TaskbarFrame")) && (child = FindChildByName(child, L"RootGrid")) && (child = FindChildByName(child, L"TaskbarFrameRepeater"))) { taskbarFrameRepeater = child; } if (!taskbarFrameRepeater) { return false; } auto startButton = EnumChildElements(taskbarFrameRepeater, [](FrameworkElement child) { auto childClassName = winrt::get_class_name(child); if (childClassName != L"Taskbar.ExperienceToggleButton") { return false; } auto automationId = Automation::AutomationProperties::GetAutomationId(child); return automationId == L"StartButton"; }); if (startButton) { double startButtonWidth = startButton.ActualWidth(); Thickness startButtonMargin = startButton.Margin(); startButtonMargin.Right = g_unloading ? 0 : -startButtonWidth; startButton.Margin(startButtonMargin); } auto widgetElement = EnumChildElements(taskbarFrameRepeater, [](FrameworkElement child) { auto childClassName = winrt::get_class_name(child); if (childClassName != L"Taskbar.AugmentedEntryPointButton") { return false; } if (child.Name() != L"AugmentedEntryPointButton") { return false; } auto margin = child.Margin(); auto offset = child.ActualOffset(); if (offset.x != margin.Left || offset.y != 0) { return false; } return true; }); if (widgetElement) { auto margin = widgetElement.Margin(); margin.Left = g_unloading ? 0 : 44; widgetElement.Margin(margin); } return true; } void* CTaskBand_ITaskListWndSite_vftable; void* CSecondaryTaskBand_ITaskListWndSite_vftable; using CTaskBand_GetTaskbarHost_t = void*(WINAPI*)(void* pThis, void** result); CTaskBand_GetTaskbarHost_t CTaskBand_GetTaskbarHost_Original; void* TaskbarHost_FrameHeight_Original; using CSecondaryTaskBand_GetTaskbarHost_t = void*(WINAPI*)(void* pThis, void** result); CSecondaryTaskBand_GetTaskbarHost_t CSecondaryTaskBand_GetTaskbarHost_Original; using std__Ref_count_base__Decref_t = void(WINAPI*)(void* pThis); std__Ref_count_base__Decref_t std__Ref_count_base__Decref_Original; XamlRoot XamlRootFromTaskbarHostSharedPtr(void* taskbarHostSharedPtr[2]) { if (!taskbarHostSharedPtr[0] && !taskbarHostSharedPtr[1]) { return nullptr; } size_t taskbarElementIUnknownOffset = 0x48; #if defined(_M_X64) { // 48:83EC 28 | sub rsp,28 // 48:83C1 48 | add rcx,48 const BYTE* b = (const BYTE*)TaskbarHost_FrameHeight_Original; if (b[0] == 0x48 && b[1] == 0x83 && b[2] == 0xEC && b[4] == 0x48 && b[5] == 0x83 && b[6] == 0xC1 && b[7] <= 0x7F) { taskbarElementIUnknownOffset = b[7]; } else { Wh_Log(L"Unsupported TaskbarHost::FrameHeight"); } } #elif defined(_M_ARM64) // Just use the default offset which will hopefully work in most cases. #else #error "Unsupported architecture" #endif auto* taskbarElementIUnknown = *(IUnknown**)((BYTE*)taskbarHostSharedPtr[0] + taskbarElementIUnknownOffset); FrameworkElement taskbarElement = nullptr; taskbarElementIUnknown->QueryInterface(winrt::guid_of(), winrt::put_abi(taskbarElement)); auto result = taskbarElement ? taskbarElement.XamlRoot() : nullptr; std__Ref_count_base__Decref_Original(taskbarHostSharedPtr[1]); return result; } XamlRoot GetTaskbarXamlRoot(HWND hTaskbarWnd) { HWND hTaskSwWnd = (HWND)GetProp(hTaskbarWnd, L"TaskbandHWND"); if (!hTaskSwWnd) { return nullptr; } void* taskBand = (void*)GetWindowLongPtr(hTaskSwWnd, 0); void* taskBandForTaskListWndSite = taskBand; for (int i = 0; *(void**)taskBandForTaskListWndSite != CTaskBand_ITaskListWndSite_vftable; i++) { if (i == 20) { return nullptr; } taskBandForTaskListWndSite = (void**)taskBandForTaskListWndSite + 1; } void* taskbarHostSharedPtr[2]{}; CTaskBand_GetTaskbarHost_Original(taskBandForTaskListWndSite, taskbarHostSharedPtr); return XamlRootFromTaskbarHostSharedPtr(taskbarHostSharedPtr); } XamlRoot GetSecondaryTaskbarXamlRoot(HWND hSecondaryTaskbarWnd) { HWND hTaskSwWnd = (HWND)FindWindowEx(hSecondaryTaskbarWnd, nullptr, L"WorkerW", nullptr); if (!hTaskSwWnd) { return nullptr; } void* taskBand = (void*)GetWindowLongPtr(hTaskSwWnd, 0); void* taskBandForTaskListWndSite = taskBand; for (int i = 0; *(void**)taskBandForTaskListWndSite != CSecondaryTaskBand_ITaskListWndSite_vftable; i++) { if (i == 20) { return nullptr; } taskBandForTaskListWndSite = (void**)taskBandForTaskListWndSite + 1; } void* taskbarHostSharedPtr[2]{}; CSecondaryTaskBand_GetTaskbarHost_Original(taskBandForTaskListWndSite, taskbarHostSharedPtr); return XamlRootFromTaskbarHostSharedPtr(taskbarHostSharedPtr); } using RunFromWindowThreadProc_t = void(WINAPI*)(void* parameter); bool RunFromWindowThread(HWND hWnd, RunFromWindowThreadProc_t proc, void* procParam) { static const UINT runFromWindowThreadRegisteredMsg = RegisterWindowMessage(L"Windhawk_RunFromWindowThread_" WH_MOD_ID); struct RUN_FROM_WINDOW_THREAD_PARAM { RunFromWindowThreadProc_t proc; void* procParam; }; DWORD dwThreadId = GetWindowThreadProcessId(hWnd, nullptr); if (dwThreadId == 0) { return false; } if (dwThreadId == GetCurrentThreadId()) { proc(procParam); return true; } HHOOK hook = SetWindowsHookEx( WH_CALLWNDPROC, [](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT { if (nCode == HC_ACTION) { const CWPSTRUCT* cwp = (const CWPSTRUCT*)lParam; if (cwp->message == runFromWindowThreadRegisteredMsg) { RUN_FROM_WINDOW_THREAD_PARAM* param = (RUN_FROM_WINDOW_THREAD_PARAM*)cwp->lParam; param->proc(param->procParam); } } return CallNextHookEx(nullptr, nCode, wParam, lParam); }, nullptr, dwThreadId); if (!hook) { return false; } RUN_FROM_WINDOW_THREAD_PARAM param; param.proc = proc; param.procParam = procParam; SendMessage(hWnd, runFromWindowThreadRegisteredMsg, 0, (LPARAM)¶m); UnhookWindowsHookEx(hook); return true; } void ApplySettingsFromTaskbarThread() { Wh_Log(L"Applying settings"); EnumThreadWindows( GetCurrentThreadId(), [](HWND hWnd, LPARAM lParam) -> BOOL { WCHAR szClassName[32]; if (GetClassName(hWnd, szClassName, ARRAYSIZE(szClassName)) == 0) { return TRUE; } XamlRoot xamlRoot = nullptr; if (_wcsicmp(szClassName, L"Shell_TrayWnd") == 0) { xamlRoot = GetTaskbarXamlRoot(hWnd); } else if (_wcsicmp(szClassName, L"Shell_SecondaryTrayWnd") == 0) { xamlRoot = GetSecondaryTaskbarXamlRoot(hWnd); } else { return TRUE; } if (!xamlRoot) { Wh_Log(L"Getting XamlRoot failed"); return TRUE; } if (!ApplyStyle(xamlRoot)) { Wh_Log(L"ApplyStyles failed"); return TRUE; } return TRUE; }, 0); } void ApplySettings(HWND hTaskbarWnd) { RunFromWindowThread( hTaskbarWnd, [](void* pParam) { ApplySettingsFromTaskbarThread(); }, 0); } using IUIElement_Arrange_t = HRESULT(WINAPI*)(void* pThis, winrt::Windows::Foundation::Rect rect); IUIElement_Arrange_t IUIElement_Arrange_Original; HRESULT WINAPI IUIElement_Arrange_Hook(void* pThis, winrt::Windows::Foundation::Rect rect) { Wh_Log(L">"); auto original = [=] { return IUIElement_Arrange_Original(pThis, rect); }; if (!g_TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride || g_unloading) { return original(); } FrameworkElement element = nullptr; ((IUnknown*)pThis) ->QueryInterface(winrt::guid_of(), winrt::put_abi(element)); if (!element) { return original(); } auto className = winrt::get_class_name(element); if (className != L"Taskbar.ExperienceToggleButton") { return original(); } auto automationId = Automation::AutomationProperties::GetAutomationId(element); if (automationId != L"StartButton") { return original(); } auto taskbarFrameRepeater = Media::VisualTreeHelper::GetParent(element).as(); auto widgetElement = EnumChildElements(taskbarFrameRepeater, [](FrameworkElement child) { auto childClassName = winrt::get_class_name(child); if (childClassName != L"Taskbar.AugmentedEntryPointButton") { return false; } if (child.Name() != L"AugmentedEntryPointButton") { return false; } auto margin = child.Margin(); auto offset = child.ActualOffset(); if (offset.x != margin.Left || offset.y != 0) { return false; } return true; }); if (!widgetElement) { element.Dispatcher().TryRunAsync( winrt::Windows::UI::Core::CoreDispatcherPriority::High, [element]() { double width = element.ActualWidth(); double minX = std::numeric_limits::infinity(); auto taskbarFrameRepeater = Media::VisualTreeHelper::GetParent(element) .as(); EnumChildElements(taskbarFrameRepeater, [&element, &minX](FrameworkElement child) { if (child == element) { return false; } auto offset = child.ActualOffset(); if (offset.x >= 0 && offset.x < minX) { minX = offset.x; } return false; }); if (minX < width) { Thickness margin = element.Margin(); margin.Right = 0; element.Margin(margin); } else if (minX > width * 2) { Thickness margin = element.Margin(); margin.Right = -width; element.Margin(margin); } }); } // Force the start button to have X = 0. winrt::Windows::Foundation::Rect newRect = rect; newRect.X = 0; return IUIElement_Arrange_Original(pThis, newRect); } using TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride_t = HRESULT(WINAPI*)(void* pThis, void* context, winrt::Windows::Foundation::Size size, winrt::Windows::Foundation::Size* resultSize); TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride_t TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride_Original; HRESULT WINAPI TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride_Hook( void* pThis, void* context, winrt::Windows::Foundation::Size size, winrt::Windows::Foundation::Size* resultSize) { Wh_Log(L">"); [[maybe_unused]] static bool hooked = [] { Shapes::Rectangle rectangle; IUIElement element = rectangle; void** vtable = *(void***)winrt::get_abi(element); auto arrange = (IUIElement_Arrange_t)vtable[92]; WindhawkUtils::SetFunctionHook(arrange, IUIElement_Arrange_Hook, &IUIElement_Arrange_Original); Wh_ApplyHookOperations(); return true; }(); g_TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride = true; HRESULT ret = TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride_Original( pThis, context, size, resultSize); g_TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride = false; return ret; } using ExperienceToggleButton_UpdateButtonPadding_t = void(WINAPI*)(void* pThis); ExperienceToggleButton_UpdateButtonPadding_t ExperienceToggleButton_UpdateButtonPadding_Original; void WINAPI ExperienceToggleButton_UpdateButtonPadding_Hook(void* pThis) { Wh_Log(L">"); ExperienceToggleButton_UpdateButtonPadding_Original(pThis); if (g_unloading) { return; } FrameworkElement toggleButtonElement = nullptr; ((IUnknown**)pThis)[1]->QueryInterface(winrt::guid_of(), winrt::put_abi(toggleButtonElement)); if (!toggleButtonElement) { return; } auto panelElement = FindChildByName(toggleButtonElement, L"ExperienceToggleButtonRootPanel") .try_as(); if (!panelElement) { return; } auto className = winrt::get_class_name(toggleButtonElement); if (className == L"Taskbar.ExperienceToggleButton") { auto automationId = Automation::AutomationProperties::GetAutomationId( toggleButtonElement); if (automationId == L"StartButton") { // Start button properties differ depending on whether Explorer is // started with centered icons or left-aligned icons. This seems to // be a bug in Explorer. Compare the start button in these two // cases: // 1. Left-align in settings, restart Explorer. // 2. Center-align in settings, restart Explorer, left-align. // // You can see that in the second case, the start button lacks the // padding on the left. // // This workaround adds this padding. if (panelElement.Width() == 45) { panelElement.Width(55); } if (panelElement.Padding() == Thickness{2, 4, 2, 4}) { panelElement.Padding(Thickness{12, 4, 2, 4}); } } } } using AugmentedEntryPointButton_UpdateButtonPadding_t = void(WINAPI*)(void* pThis); AugmentedEntryPointButton_UpdateButtonPadding_t AugmentedEntryPointButton_UpdateButtonPadding_Original; void WINAPI AugmentedEntryPointButton_UpdateButtonPadding_Hook(void* pThis) { Wh_Log(L">"); AugmentedEntryPointButton_UpdateButtonPadding_Original(pThis); if (g_unloading) { return; } FrameworkElement button = nullptr; ((IUnknown**)pThis)[1]->QueryInterface(winrt::guid_of(), winrt::put_abi(button)); if (!button) { return; } button.Dispatcher().TryRunAsync( winrt::Windows::UI::Core::CoreDispatcherPriority::High, [button]() { auto offset = button.ActualOffset(); if (offset.x != 0 || offset.y != 0) { return; } auto margin = button.Margin(); margin.Left = 44; button.Margin(margin); }); } bool HookTaskbarDllSymbols() { HMODULE module = LoadLibraryEx(L"taskbar.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!module) { Wh_Log(L"Failed to load taskbar.dll"); return false; } WindhawkUtils::SYMBOL_HOOK taskbarDllHooks[] = { { {LR"(const CTaskBand::`vftable'{for `ITaskListWndSite'})"}, &CTaskBand_ITaskListWndSite_vftable, }, { {LR"(const CSecondaryTaskBand::`vftable'{for `ITaskListWndSite'})"}, &CSecondaryTaskBand_ITaskListWndSite_vftable, }, { {LR"(public: virtual class std::shared_ptr __cdecl CTaskBand::GetTaskbarHost(void)const )"}, &CTaskBand_GetTaskbarHost_Original, }, { {LR"(public: int __cdecl TaskbarHost::FrameHeight(void)const )"}, &TaskbarHost_FrameHeight_Original, }, { {LR"(public: virtual class std::shared_ptr __cdecl CSecondaryTaskBand::GetTaskbarHost(void)const )"}, &CSecondaryTaskBand_GetTaskbarHost_Original, }, { {LR"(public: void __cdecl std::_Ref_count_base::_Decref(void))"}, &std__Ref_count_base__Decref_Original, }, }; return HookSymbols(module, taskbarDllHooks, ARRAYSIZE(taskbarDllHooks)); } bool HookTaskbarViewDllSymbols(HMODULE module) { // Taskbar.View.dll WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { { {LR"(public: virtual int __cdecl winrt::impl::produce::ArrangeOverride(void *,struct winrt::Windows::Foundation::Size,struct winrt::Windows::Foundation::Size *))"}, &TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride_Original, TaskbarCollapsibleLayoutXamlTraits_ArrangeOverride_Hook, }, { {LR"(protected: virtual void __cdecl winrt::Taskbar::implementation::ExperienceToggleButton::UpdateButtonPadding(void))"}, &ExperienceToggleButton_UpdateButtonPadding_Original, ExperienceToggleButton_UpdateButtonPadding_Hook, }, { {LR"(protected: virtual void __cdecl winrt::Taskbar::implementation::AugmentedEntryPointButton::UpdateButtonPadding(void))"}, &AugmentedEntryPointButton_UpdateButtonPadding_Original, AugmentedEntryPointButton_UpdateButtonPadding_Hook, }, }; return HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)); } HMODULE GetTaskbarViewModuleHandle() { HMODULE module = GetModuleHandle(L"Taskbar.View.dll"); if (!module) { module = GetModuleHandle(L"ExplorerExtensions.dll"); } return module; } void HandleLoadedModuleIfTaskbarView(HMODULE module, LPCWSTR lpLibFileName) { if (!g_taskbarViewDllLoaded && GetTaskbarViewModuleHandle() == module && !g_taskbarViewDllLoaded.exchange(true)) { Wh_Log(L"Loaded %s", lpLibFileName); if (HookTaskbarViewDllSymbols(module)) { Wh_ApplyHookOperations(); } } } using LoadLibraryExW_t = decltype(&LoadLibraryExW); LoadLibraryExW_t LoadLibraryExW_Original; HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { HMODULE module = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags); if (module) { HandleLoadedModuleIfTaskbarView(module, lpLibFileName); } return module; } std::wstring GetProcessFileName(DWORD dwProcessId) { HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcessId); if (!hProcess) { return std::wstring{}; } WCHAR processPath[MAX_PATH]; DWORD dwSize = ARRAYSIZE(processPath); if (!QueryFullProcessImageName(hProcess, 0, processPath, &dwSize)) { CloseHandle(hProcess); return std::wstring{}; } CloseHandle(hProcess); PCWSTR processFileNameUpper = wcsrchr(processPath, L'\\'); if (!processFileNameUpper) { return std::wstring{}; } processFileNameUpper++; return processFileNameUpper; } using DwmSetWindowAttribute_t = decltype(&DwmSetWindowAttribute); DwmSetWindowAttribute_t DwmSetWindowAttribute_Original; HRESULT WINAPI DwmSetWindowAttribute_Hook(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute) { auto original = [=]() { return DwmSetWindowAttribute_Original(hwnd, dwAttribute, pvAttribute, cbAttribute); }; if (dwAttribute != DWMWA_CLOAK || cbAttribute != sizeof(BOOL)) { return original(); } BOOL cloak = *(BOOL*)pvAttribute; if (cloak) { return original(); } Wh_Log(L"> %08X", (DWORD)(DWORD_PTR)hwnd); DWORD processId = 0; if (!hwnd || !GetWindowThreadProcessId(hwnd, &processId)) { return original(); } std::wstring processFileName = GetProcessFileName(processId); enum class DwmTarget { SearchHost, }; DwmTarget target; if (_wcsicmp(processFileName.c_str(), L"SearchHost.exe") == 0) { target = DwmTarget::SearchHost; } else { return original(); } HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); UINT monitorDpiX = 96; UINT monitorDpiY = 96; GetDpiForMonitor(monitor, MDT_DEFAULT, &monitorDpiX, &monitorDpiY); MONITORINFO monitorInfo{ .cbSize = sizeof(MONITORINFO), }; GetMonitorInfo(monitor, &monitorInfo); RECT targetRect; if (!GetWindowRect(hwnd, &targetRect)) { return original(); } int x = targetRect.left; int y = targetRect.top; int cx = targetRect.right - targetRect.left; int cy = targetRect.bottom - targetRect.top; if (target == DwmTarget::SearchHost) { // Only change x. int xNew; if (g_settings.startMenuOnTheLeft) { // Not centered or already changed. if (x == monitorInfo.rcWork.left) { return original(); } xNew = monitorInfo.rcWork.left; g_searchMenuWnd = hwnd; g_searchMenuOriginalX = x; } else { if (!g_searchMenuOriginalX) { return original(); } xNew = g_searchMenuOriginalX; g_searchMenuWnd = nullptr; g_searchMenuOriginalX = 0; } if (xNew == x) { return original(); } x = xNew; } SetWindowPos(hwnd, nullptr, x, y, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE); return original(); } namespace StartMenuUI { bool g_applyStylePending; bool g_inApplyStyle; std::optional g_previousCanvasLeft; winrt::weak_ref g_startSizingFrameWeakRef; int64_t g_canvasTopPropertyChangedToken; int64_t g_canvasLeftPropertyChangedToken; std::optional g_previousHorizontalAlignment; winrt::event_token g_layoutUpdatedToken; winrt::event_token g_visibilityChangedToken; HWND GetCoreWnd() { struct ENUM_WINDOWS_PARAM { HWND* hWnd; }; HWND hWnd = nullptr; ENUM_WINDOWS_PARAM param = {&hWnd}; EnumWindows( [](HWND hWnd, LPARAM lParam) -> BOOL { ENUM_WINDOWS_PARAM& param = *(ENUM_WINDOWS_PARAM*)lParam; DWORD dwProcessId = 0; if (!GetWindowThreadProcessId(hWnd, &dwProcessId) || dwProcessId != GetCurrentProcessId()) { return TRUE; } WCHAR szClassName[32]; if (GetClassName(hWnd, szClassName, ARRAYSIZE(szClassName)) == 0) { return TRUE; } if (_wcsicmp(szClassName, L"Windows.UI.Core.CoreWindow") == 0) { *param.hWnd = hWnd; return FALSE; } return TRUE; }, (LPARAM)¶m); return hWnd; } void ApplyStyle(); void ApplyStyleClassicStartMenu(FrameworkElement content, HMONITOR monitor) { FrameworkElement startSizingFrame = FindChildByClassName(content, L"StartDocked.StartSizingFrame"); if (!startSizingFrame) { Wh_Log(L"Failed to find StartDocked.StartSizingFrame"); return; } if (g_unloading) { if (g_previousCanvasLeft.has_value()) { Wh_Log(L"Restoring Canvas.Left to %f", g_previousCanvasLeft.value()); Controls::Canvas::SetLeft(startSizingFrame, g_previousCanvasLeft.value()); } } else { if (!g_previousCanvasLeft.has_value()) { double canvasLeft = Controls::Canvas::GetLeft(startSizingFrame); // The value might be zero when not yet initialized. if (canvasLeft) { g_previousCanvasLeft = canvasLeft; } } constexpr int kStartMenuMargin = 12; double newLeft = kStartMenuMargin; Wh_Log(L"Setting Canvas.Left to %f", newLeft); Controls::Canvas::SetLeft(startSizingFrame, newLeft); // Subscribe to Canvas.Top and Canvas.Left property changes to apply // custom styles right when that happens. Without it, the start menu may // end up truncated. A simple reproduction is to open the start menu on // different monitors, each with a different resolution/DPI/taskbar // side. if (!g_startSizingFrameWeakRef.get()) { auto startSizingFrameDo = startSizingFrame.as(); g_startSizingFrameWeakRef = startSizingFrameDo; g_canvasTopPropertyChangedToken = startSizingFrameDo.RegisterPropertyChangedCallback( Controls::Canvas::TopProperty(), [](DependencyObject sender, DependencyProperty property) { double top = Controls::Canvas::GetTop( sender.as()); Wh_Log(L"Canvas.Top changed to %f", top); if (!g_inApplyStyle) { ApplyStyle(); } }); g_canvasLeftPropertyChangedToken = startSizingFrameDo.RegisterPropertyChangedCallback( Controls::Canvas::LeftProperty(), [](DependencyObject sender, DependencyProperty property) { double left = Controls::Canvas::GetLeft( sender.as()); Wh_Log(L"Canvas.Left changed to %f", left); if (!g_inApplyStyle) { ApplyStyle(); } }); } } } void ApplyStyleRedesignedStartMenu(FrameworkElement content) { FrameworkElement frameRoot = FindChildByName(content, L"FrameRoot"); if (!frameRoot) { Wh_Log(L"Failed to find Start menu frame root"); return; } if (g_unloading) { frameRoot.HorizontalAlignment(g_previousHorizontalAlignment.value_or( HorizontalAlignment::Center)); } else { if (!g_previousHorizontalAlignment) { g_previousHorizontalAlignment = frameRoot.HorizontalAlignment(); } frameRoot.HorizontalAlignment(HorizontalAlignment::Left); } } void ApplyStyle() { g_inApplyStyle = true; HWND coreWnd = GetCoreWnd(); HMONITOR monitor = MonitorFromWindow(coreWnd, MONITOR_DEFAULTTONEAREST); Wh_Log(L"Applying Start menu style for monitor %p", monitor); auto window = Window::Current(); FrameworkElement content = window.Content().as(); winrt::hstring contentClassName = winrt::get_class_name(content); Wh_Log(L"Start menu content class name: %s", contentClassName.c_str()); if (contentClassName == L"Windows.UI.Xaml.Controls.Canvas") { ApplyStyleClassicStartMenu(content, monitor); } else if (contentClassName == L"StartMenu.StartBlendedFlexFrame") { ApplyStyleRedesignedStartMenu(content); } else { Wh_Log(L"Error: Unsupported Start menu content class name"); } g_inApplyStyle = false; } void Init() { if (g_layoutUpdatedToken) { return; } auto window = Window::Current(); if (!window) { return; } if (!g_visibilityChangedToken) { g_visibilityChangedToken = window.VisibilityChanged( [](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args) { Wh_Log(L"Window visibility changed: %d", args.Visible()); if (args.Visible()) { g_applyStylePending = true; } }); } auto contentUI = window.Content(); if (!contentUI) { return; } auto content = contentUI.as(); g_layoutUpdatedToken = content.LayoutUpdated( [](winrt::Windows::Foundation::IInspectable const&, winrt::Windows::Foundation::IInspectable const&) { if (g_applyStylePending) { g_applyStylePending = false; ApplyStyle(); } }); ApplyStyle(); } void Uninit() { if (!g_layoutUpdatedToken) { return; } auto window = Window::Current(); if (!window) { return; } if (g_visibilityChangedToken) { window.VisibilityChanged(g_visibilityChangedToken); g_visibilityChangedToken = {}; } auto contentUI = window.Content(); if (!contentUI) { return; } auto content = contentUI.as(); content.LayoutUpdated(g_layoutUpdatedToken); g_layoutUpdatedToken = {}; auto startSizingFrameDo = g_startSizingFrameWeakRef.get(); if (startSizingFrameDo) { if (g_canvasTopPropertyChangedToken) { startSizingFrameDo.UnregisterPropertyChangedCallback( Controls::Canvas::TopProperty(), g_canvasTopPropertyChangedToken); g_canvasTopPropertyChangedToken = 0; } if (g_canvasLeftPropertyChangedToken) { startSizingFrameDo.UnregisterPropertyChangedCallback( Controls::Canvas::LeftProperty(), g_canvasLeftPropertyChangedToken); g_canvasLeftPropertyChangedToken = 0; } } g_startSizingFrameWeakRef = nullptr; ApplyStyle(); } void SettingsChanged() { ApplyStyle(); } using RoGetActivationFactory_t = decltype(&RoGetActivationFactory); RoGetActivationFactory_t RoGetActivationFactory_Original; HRESULT WINAPI RoGetActivationFactory_Hook(HSTRING activatableClassId, REFIID iid, void** factory) { thread_local static bool isInHook; if (isInHook) { return RoGetActivationFactory_Original(activatableClassId, iid, factory); } isInHook = true; if (wcscmp(WindowsGetStringRawBuffer(activatableClassId, nullptr), L"Windows.UI.Xaml.Hosting.XamlIsland") == 0) { try { Init(); } catch (...) { HRESULT hr = winrt::to_hresult(); Wh_Log(L"Error %08X", hr); } } HRESULT ret = RoGetActivationFactory_Original(activatableClassId, iid, factory); isInHook = false; return ret; } } // namespace StartMenuUI void RestoreMenuPositions() { if (g_searchMenuWnd && g_searchMenuOriginalX) { RECT rect; if (GetWindowRect(g_searchMenuWnd, &rect)) { int x = rect.left; int y = rect.top; int cx = rect.right - rect.left; int cy = rect.bottom - rect.top; if (g_searchMenuOriginalX != x) { x = g_searchMenuOriginalX; SetWindowPos(g_searchMenuWnd, nullptr, x, y, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE); } } g_searchMenuWnd = nullptr; g_searchMenuOriginalX = 0; } } void LoadSettings() { g_settings.startMenuOnTheLeft = Wh_GetIntSetting(L"startMenuOnTheLeft"); } BOOL Wh_ModInit() { Wh_Log(L">"); LoadSettings(); g_target = Target::Explorer; WCHAR moduleFilePath[MAX_PATH]; switch ( GetModuleFileName(nullptr, moduleFilePath, ARRAYSIZE(moduleFilePath))) { case 0: case ARRAYSIZE(moduleFilePath): Wh_Log(L"GetModuleFileName failed"); break; default: if (PCWSTR moduleFileName = wcsrchr(moduleFilePath, L'\\')) { moduleFileName++; if (_wcsicmp(moduleFileName, L"StartMenuExperienceHost.exe") == 0) { g_target = Target::StartMenuExperienceHost; } } else { Wh_Log(L"GetModuleFileName returned an unsupported path"); } break; } if (g_target == Target::StartMenuExperienceHost) { if (!g_settings.startMenuOnTheLeft) { return FALSE; } HMODULE winrtModule = GetModuleHandle(L"api-ms-win-core-winrt-l1-1-0.dll"); auto pRoGetActivationFactory = (decltype(&RoGetActivationFactory))GetProcAddress( winrtModule, "RoGetActivationFactory"); WindhawkUtils::SetFunctionHook( pRoGetActivationFactory, StartMenuUI::RoGetActivationFactory_Hook, &StartMenuUI::RoGetActivationFactory_Original); return TRUE; } if (!HookTaskbarDllSymbols()) { return FALSE; } if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { g_taskbarViewDllLoaded = true; if (!HookTaskbarViewDllSymbols(taskbarViewModule)) { return FALSE; } } else { Wh_Log(L"Taskbar view module not loaded yet"); HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); auto pKernelBaseLoadLibraryExW = (decltype(&LoadLibraryExW))GetProcAddress(kernelBaseModule, "LoadLibraryExW"); WindhawkUtils::SetFunctionHook(pKernelBaseLoadLibraryExW, LoadLibraryExW_Hook, &LoadLibraryExW_Original); } HMODULE dwmapiModule = LoadLibraryEx(L"dwmapi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (dwmapiModule) { auto pDwmSetWindowAttribute = (decltype(&DwmSetWindowAttribute))GetProcAddress( dwmapiModule, "DwmSetWindowAttribute"); if (pDwmSetWindowAttribute) { WindhawkUtils::SetFunctionHook(pDwmSetWindowAttribute, DwmSetWindowAttribute_Hook, &DwmSetWindowAttribute_Original); } } return TRUE; } void Wh_ModAfterInit() { Wh_Log(L">"); if (g_target == Target::Explorer) { if (!g_taskbarViewDllLoaded) { if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { if (!g_taskbarViewDllLoaded.exchange(true)) { Wh_Log(L"Got Taskbar.View.dll"); if (HookTaskbarViewDllSymbols(taskbarViewModule)) { Wh_ApplyHookOperations(); } } } } HWND hTaskbarWnd = FindCurrentProcessTaskbarWnd(); if (hTaskbarWnd) { ApplySettings(hTaskbarWnd); } } else if (g_target == Target::StartMenuExperienceHost) { HWND hCoreWnd = StartMenuUI::GetCoreWnd(); if (hCoreWnd) { Wh_Log(L"Initializing - Found core window"); RunFromWindowThread( hCoreWnd, [](PVOID) { StartMenuUI::Init(); }, nullptr); } } } void Wh_ModBeforeUninit() { Wh_Log(L">"); g_unloading = true; if (g_target == Target::Explorer) { HWND hTaskbarWnd = FindCurrentProcessTaskbarWnd(); if (hTaskbarWnd) { ApplySettings(hTaskbarWnd); } } else if (g_target == Target::StartMenuExperienceHost) { HWND hCoreWnd = StartMenuUI::GetCoreWnd(); if (hCoreWnd) { Wh_Log(L"Uninitializing - Found core window"); RunFromWindowThread( hCoreWnd, [](PVOID) { StartMenuUI::Uninit(); }, nullptr); } } } void Wh_ModUninit() { Wh_Log(L">"); if (g_target == Target::Explorer) { RestoreMenuPositions(); } } BOOL Wh_ModSettingsChanged(BOOL* bReload) { Wh_Log(L">"); if (g_target == Target::Explorer) { RestoreMenuPositions(); } LoadSettings(); if (g_target == Target::StartMenuExperienceHost) { if (!g_settings.startMenuOnTheLeft) { return FALSE; } HWND hCoreWnd = StartMenuUI::GetCoreWnd(); if (hCoreWnd) { Wh_Log(L"Applying settings - Found core window"); RunFromWindowThread( hCoreWnd, [](PVOID) { StartMenuUI::SettingsChanged(); }, nullptr); } } return TRUE; }