diff --git a/mods/taskbar-start-button-position.wh.cpp b/mods/taskbar-start-button-position.wh.cpp index 2859a2267..8890cbd09 100644 --- a/mods/taskbar-start-button-position.wh.cpp +++ b/mods/taskbar-start-button-position.wh.cpp @@ -2,7 +2,7 @@ // @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.1.2 +// @version 1.2 // @author m417z // @github https://github.com/m417z // @twitter https://twitter.com/m417z @@ -31,9 +31,6 @@ icons are centered. Only Windows 11 is supported. -Known limitations: -* There's a jumpy animation when a centered taskbar is animated. - ![Screenshot](https://i.imgur.com/bEqvfOE.png) */ // ==/WindhawkModReadme== @@ -82,17 +79,6 @@ Target g_target; std::atomic g_unloading; -UINT_PTR g_updateStartButtonPositionTimer; -int g_updateStartButtonPositionTimerCounter; - -struct TaskbarData { - winrt::weak_ref xamlRoot; - winrt::weak_ref startButtonElement; - winrt::weak_ref widgetElement; -}; - -std::vector g_taskbarData; - typedef enum MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, @@ -154,135 +140,7 @@ FrameworkElement FindChildByClassName(FrameworkElement element, }); } -void UpdateStartButtonPosition(TaskbarData& taskbarData) { - Wh_Log(L">"); - - auto xamlRoot = taskbarData.xamlRoot.get(); - if (!xamlRoot) { - return; - } - - auto startButton = taskbarData.startButtonElement.get(); - if (!startButton) { - return; - } - - if (startButton.XamlRoot() != xamlRoot) { - Wh_Log(L"XamlRoot mismatch"); - return; - } - - auto widgetElement = taskbarData.widgetElement.get(); - if (!widgetElement) { - auto itemsRepeater = Media::VisualTreeHelper::GetParent(startButton) - .try_as(); - if (itemsRepeater) { - widgetElement = - EnumChildElements(itemsRepeater, [](FrameworkElement child) { - if (child.Name() != L"AugmentedEntryPointButton") { - return false; - } - - auto offset = child.ActualOffset(); - if (offset.x != 0 || offset.y != 0) { - return false; - } - - return true; - }); - - if (widgetElement) { - taskbarData.widgetElement = winrt::make_weak(widgetElement); - - auto margin = widgetElement.Margin(); - margin.Left += 34; - widgetElement.Margin(margin); - } - } - } - - FrameworkElement xamlRootContent = - xamlRoot.Content().try_as(); - - auto pt = startButton.TransformToVisual(xamlRootContent) - .TransformPoint(winrt::Windows::Foundation::Point{0, 0}); - Wh_Log(L"%f, %f", pt.X, pt.Y); - - if (pt.X == 0) { - return; - } - - Media::TranslateTransform transform; - if (auto prevTransform = - startButton.RenderTransform().try_as()) { - transform = prevTransform; - } - - transform.X(transform.X() - pt.X); - startButton.RenderTransform(transform); -} - -void ResetStartButtonStyles(const TaskbarData& taskbarData) { - Wh_Log(L">"); - - auto startButton = taskbarData.startButtonElement.get(); - if (!startButton) { - return; - } - - Media::TranslateTransform transform; - if (auto prevTransform = - startButton.RenderTransform().try_as()) { - transform = prevTransform; - } - - transform.X(0); - startButton.RenderTransform(transform); - - Thickness startButtonMargin = startButton.Margin(); - startButtonMargin.Right = 0; - startButton.Margin(startButtonMargin); - - if (auto widgetElement = taskbarData.widgetElement.get()) { - auto margin = widgetElement.Margin(); - margin.Left -= 34; - if (margin.Left >= 0) { - widgetElement.Margin(margin); - } - } -} - -void ScheduleUpdateStartButtonPosition() { - g_updateStartButtonPositionTimerCounter = 0; - g_updateStartButtonPositionTimer = - SetTimer(nullptr, g_updateStartButtonPositionTimer, 100, - [](HWND hwnd, // handle of window for timer messages - UINT uMsg, // WM_TIMER message - UINT_PTR idEvent, // timer identifier - DWORD dwTime // current system time - ) WINAPI { - g_updateStartButtonPositionTimerCounter++; - if (g_updateStartButtonPositionTimerCounter >= 30) { - KillTimer(nullptr, g_updateStartButtonPositionTimer); - g_updateStartButtonPositionTimer = 0; - } - - for (auto& item : g_taskbarData) { - UpdateStartButtonPosition(item); - } - }); -} - bool ApplyStyle(XamlRoot xamlRoot) { - auto dataItem = std::find_if( - g_taskbarData.begin(), g_taskbarData.end(), [&xamlRoot](auto x) { - auto xamlRootIter = x.xamlRoot.get(); - return xamlRootIter && xamlRootIter == xamlRoot; - }); - if (dataItem != g_taskbarData.end()) { - return true; - } - FrameworkElement xamlRootContent = xamlRoot.Content().try_as(); @@ -311,20 +169,39 @@ bool ApplyStyle(XamlRoot xamlRoot) { Automation::AutomationProperties::GetAutomationId(child); return automationId == L"StartButton"; }); - if (!startButton) { - return false; + if (startButton) { + double startButtonWidth = startButton.ActualWidth(); + + Thickness startButtonMargin = startButton.Margin(); + startButtonMargin.Right = g_unloading ? 0 : -startButtonWidth; + startButton.Margin(startButtonMargin); } - double startButtonWidth = startButton.ActualWidth(); + auto widgetElement = + EnumChildElements(taskbarFrameRepeater, [](FrameworkElement child) { + auto childClassName = winrt::get_class_name(child); + if (childClassName != L"Taskbar.AugmentedEntryPointButton") { + return false; + } - Thickness startButtonMargin = startButton.Margin(); - startButtonMargin.Right = -startButtonWidth; - startButton.Margin(startButtonMargin); + if (child.Name() != L"AugmentedEntryPointButton") { + return false; + } - g_taskbarData.push_back({ - .xamlRoot = xamlRoot, - .startButtonElement = startButton, - }); + 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 : 34; + widgetElement.Margin(margin); + } return true; } @@ -497,35 +374,135 @@ void ApplySettingsFromTaskbarThread() { void ApplySettings(HWND hTaskbarWnd) { RunFromWindowThread( hTaskbarWnd, - [](PVOID pParam) WINAPI { - if (!g_unloading) { - ApplySettingsFromTaskbarThread(); - ScheduleUpdateStartButtonPosition(); - } else { - for (const auto& item : g_taskbarData) { - ResetStartButtonStyles(item); - } + [](PVOID pParam) WINAPI { ApplySettingsFromTaskbarThread(); }, 0); +} - if (g_updateStartButtonPositionTimer) { - KillTimer(nullptr, g_updateStartButtonPositionTimer); - g_updateStartButtonPositionTimer = 0; - } +using IUIElement_Arrange_t = HRESULT( + WINAPI*)(UIElement pThis, const winrt::Windows::Foundation::Rect* rect); +IUIElement_Arrange_t IUIElement_Arrange_Original; +HRESULT WINAPI +IUIElement_Arrange_Hook(UIElement pThis, + const winrt::Windows::Foundation::Rect* rect) { + Wh_Log(L">"); + + auto original = [&] { return IUIElement_Arrange_Original(pThis, rect); }; + + if (g_unloading) { + return original(); + } + + FrameworkElement element = pThis.try_as(); + 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; } - }, - 0); + + 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 CPearl_SetBounds_t = HRESULT(WINAPI*)(void* pThis, void* param1); -CPearl_SetBounds_t CPearl_SetBounds_Original; -HRESULT WINAPI CPearl_SetBounds_Hook(void* pThis, void* param1) { +using AugmentedEntryPointButton_UpdateButtonPadding_t = + void(WINAPI*)(void* pThis); +AugmentedEntryPointButton_UpdateButtonPadding_t + AugmentedEntryPointButton_UpdateButtonPadding_Original; +void WINAPI AugmentedEntryPointButton_UpdateButtonPadding_Hook(void* pThis) { Wh_Log(L">"); - if (!g_unloading) { - ApplySettingsFromTaskbarThread(); - ScheduleUpdateStartButtonPosition(); + AugmentedEntryPointButton_UpdateButtonPadding_Original(pThis); + + if (g_unloading) { + return; } - return CPearl_SetBounds_Original(pThis, param1); + 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 = 34; + button.Margin(margin); + }); } bool HookTaskbarDllSymbols() { @@ -536,36 +513,66 @@ bool HookTaskbarDllSymbols() { } WindhawkUtils::SYMBOL_HOOK taskbarDllHooks[] = { - { - {LR"(public: long __cdecl CPearl::SetBounds(struct tagRECT const &))"}, - (void**)&CPearl_SetBounds_Original, - (void*)CPearl_SetBounds_Hook, - }, { {LR"(const CTaskBand::`vftable'{for `ITaskListWndSite'})"}, - (void**)&CTaskBand_ITaskListWndSite_vftable, + &CTaskBand_ITaskListWndSite_vftable, }, { {LR"(const CSecondaryTaskBand::`vftable'{for `ITaskListWndSite'})"}, - (void**)&CSecondaryTaskBand_ITaskListWndSite_vftable, + &CSecondaryTaskBand_ITaskListWndSite_vftable, }, { {LR"(public: virtual class std::shared_ptr __cdecl CTaskBand::GetTaskbarHost(void)const )"}, - (void**)&CTaskBand_GetTaskbarHost_Original, + &CTaskBand_GetTaskbarHost_Original, }, { {LR"(public: virtual class std::shared_ptr __cdecl CSecondaryTaskBand::GetTaskbarHost(void)const )"}, - (void**)&CSecondaryTaskBand_GetTaskbarHost_Original, + &CSecondaryTaskBand_GetTaskbarHost_Original, }, { {LR"(public: void __cdecl std::_Ref_count_base::_Decref(void))"}, - (void**)&std__Ref_count_base__Decref_Original, + &std__Ref_count_base__Decref_Original, }, }; return HookSymbols(module, taskbarDllHooks, ARRAYSIZE(taskbarDllHooks)); } +bool HookTaskbarViewDllSymbols() { + WCHAR dllPath[MAX_PATH]; + if (!GetWindowsDirectory(dllPath, ARRAYSIZE(dllPath))) { + Wh_Log(L"GetWindowsDirectory failed"); + return false; + } + + wcscat_s( + dllPath, MAX_PATH, + LR"(\SystemApps\MicrosoftWindows.Client.Core_cw5n1h2txyewy\Taskbar.View.dll)"); + + HMODULE module = + LoadLibraryEx(dllPath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + if (!module) { + Wh_Log(L"Taskbar view module couldn't be loaded"); + return false; + } + + // Taskbar.View.dll + WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { + { + {LR"(public: __cdecl winrt::impl::consume_Windows_UI_Xaml_IUIElement::Arrange(struct winrt::Windows::Foundation::Rect const &)const )"}, + &IUIElement_Arrange_Original, + IUIElement_Arrange_Hook, + }, + { + {LR"(protected: virtual void __cdecl winrt::Taskbar::implementation::AugmentedEntryPointButton::UpdateButtonPadding(void))"}, + &AugmentedEntryPointButton_UpdateButtonPadding_Original, + AugmentedEntryPointButton_UpdateButtonPadding_Hook, + }, + }; + + return HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)); +} + namespace CoreWindowUI { using SetWindowPos_t = decltype(&SetWindowPos); @@ -903,6 +910,10 @@ BOOL Wh_ModInit() { return FALSE; } + if (!HookTaskbarViewDllSymbols()) { + return FALSE; + } + return TRUE; }