From 55cf8e1768150ba51f8b822a44f19de3cf0cc9fa Mon Sep 17 00:00:00 2001 From: Michael Maltsev <4129781+m417z@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:04:53 +0200 Subject: [PATCH] Taskbar Labels for Windows 11 v1.2 (#399) * A new, more robust implementation for newer Windows 11 versions with a native taskbar labels implementation. --- mods/taskbar-labels.wh.cpp | 399 +++++++++++++++++++++++++------------ 1 file changed, 271 insertions(+), 128 deletions(-) diff --git a/mods/taskbar-labels.wh.cpp b/mods/taskbar-labels.wh.cpp index d2f0099d0..00d4f168f 100644 --- a/mods/taskbar-labels.wh.cpp +++ b/mods/taskbar-labels.wh.cpp @@ -1,8 +1,8 @@ // ==WindhawkMod== // @id taskbar-labels // @name Taskbar Labels for Windows 11 -// @description Show text labels for running programs on the taskbar (Windows 11 only) -// @version 1.1.5 +// @description Show and customize text labels for running programs on the taskbar (Windows 11 only) +// @version 1.2 // @author m417z // @github https://github.com/m417z // @twitter https://twitter.com/m417z @@ -24,11 +24,19 @@ /* # Taskbar Labels for Windows 11 -Show text labels for running programs on the taskbar (Windows 11 only). +Show and customize text labels for running programs on the taskbar (Windows 11 +only). -By default, the Windows 11 taskbar only shows icons for taskbar items, without -any text labels. This mod adds text labels, similarly to the way it was possible -to configure in older Windows versions. +**Older Windows 11 versions:** By default, the original Windows 11 taskbar only +shows icons for taskbar items, without any text labels. This mod adds text +labels, similarly to the way it was possible to configure in older Windows +versions. + +**Newer Windows 11 versions:** A native taskbar labels implementation was added +in newer Windows 11 versions. For these versions, the mod improves it by making +all taskbar items have the same width (optional), adding ellipsis for long +labels, and providing other customization options. Note that you can still use +the taskbar properties to configure taskbar combining. Before: @@ -50,7 +58,8 @@ choose one of the following running indicator styles: - taskbarItemWidth: 160 $name: Taskbar item width $description: >- - Windows 11 default: 44 + Set to 0 to use the Windows adaptive width, only for newer Windows versions + with the built-in taskbar labels implementation - runningIndicatorStyle: centerFixed $name: Running indicator style $options: @@ -66,7 +75,7 @@ choose one of the following running indicator styles: - fullWidth: Full width - fontSize: 12 $name: Font size -- leftAndRightPaddingSize: 10 +- leftAndRightPaddingSize: 8 $name: Left and right padding size - spaceBetweenIconAndLabel: 8 $name: Space between icon and label @@ -74,8 +83,14 @@ choose one of the following running indicator styles: $name: Label for a single item $description: >- The following variables can be used: %name%, %amount% + + Ignored in newer Windows versions with the built-in taskbar labels + implementation - labelForMultipleItems: "[%amount%] %name%" $name: Label for multiple items + $description: >- + Ignored in newer Windows versions with the built-in taskbar labels + implementation */ // ==/WindhawkModSettings== @@ -114,8 +129,6 @@ struct deleter_from_fn { using string_setting_unique_ptr = std::unique_ptr>; -// #define EXTRA_DBG_LOG - enum class IndicatorStyle { centerFixed, centerDynamic, @@ -137,8 +150,11 @@ struct { WCHAR g_taskbarViewDllPath[MAX_PATH]; std::atomic g_taskbarViewDllLoaded = false; std::atomic g_applyingSettings = false; +std::atomic g_overrideGroupingMode = false; std::atomic g_unloading = false; +bool g_hasNativeLabelsImplementation; + double g_initialTaskbarItemWidth; UINT_PTR g_invalidateTaskListButtonTimer; @@ -225,70 +241,12 @@ void RecalculateLabels() { g_applyingSettings = true; - // Trigger TrayUI::_HandleSettingChange. - // SendMessage(hTaskbarWnd, WM_SETTINGCHANGE, SPI_SETLOGICALDPIOVERRIDE, 0); - // Trigger CTaskBand::_HandleSyncDisplayChange. SendMessage(hMSTaskSwWClass, 0x452, 3, 0); g_applyingSettings = false; } -#if defined(EXTRA_DBG_LOG) -void LogAllElementsAux(FrameworkElement element, int nesting = 0) { - std::string padding(nesting * 2, ' '); - - 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"%SFailed to get child %d of %d", padding.c_str(), i + 1, - childrenCount); - continue; - } - - auto className = winrt::get_class_name(child); - Wh_Log(L"%SClass: %s", padding.c_str(), className.c_str()); - Wh_Log(L"%SName: %s", padding.c_str(), child.Name().c_str()); - - auto offset = child.ActualOffset(); - Wh_Log(L"%SPosition: %f, %f", padding.c_str(), offset.x, offset.y); - Wh_Log(L"%SSize: %f x %f", padding.c_str(), child.ActualWidth(), - child.ActualHeight()); - - if (child.Name() == L"WindhawkText") { - auto windhawkTextControl = child.as(); - Wh_Log(L"%SText: %s", padding.c_str(), - windhawkTextControl.Text().c_str()); - } - - LogAllElementsAux(child, nesting + 1); - } -} - -void LogAllElements(FrameworkElement element) { - try { - auto rootElement = element; - while (true) { - auto parent = Media::VisualTreeHelper::GetParent(rootElement) - .as(); - if (!parent) { - break; - } - rootElement = parent; - } - - Wh_Log(L">>> LogAllElements"); - LogAllElementsAux(rootElement); - Wh_Log(L"<<< LogAllElements"); - } catch (winrt::hresult_error const& ex) { - Wh_Log(L"LogAllElements failed: %08X", ex.code()); - } -} -#endif // defined(EXTRA_DBG_LOG) - using TaskListButton_get_IsRunning_t = HRESULT(WINAPI*)(void* pThis, bool* running); TaskListButton_get_IsRunning_t TaskListButton_get_IsRunning_Original; @@ -348,10 +306,6 @@ FrameworkElement ItemsRepeater_TryGetElement( double CalculateTaskbarItemWidth(FrameworkElement taskbarFrameRepeaterElement, double minWidth, double maxWidth) { -#if defined(EXTRA_DBG_LOG) - LogAllElements(taskbarFrameRepeaterElement); -#endif // defined(EXTRA_DBG_LOG) - double taskbarFrameRepeaterEndOffset = 0; auto rootGridElement = @@ -679,21 +633,6 @@ void UpdateTaskListButtonWidth(FrameworkElement taskListButtonElement, iconPanelElement.Width(widthToSet); - // Hide second column if running with the native labels implementation of - // Windows. - auto columnDefinitions = - iconPanelElement.as().ColumnDefinitions(); - if (columnDefinitions.Size() == 2) { - columnDefinitions.GetAt(0).Width(GridLength({ - .Value = 1, - .GridUnitType = GridUnitType::Star, - })); - columnDefinitions.GetAt(1).Width(GridLength({ - .Value = 0.0, - .GridUnitType = GridUnitType::Pixel, - })); - } - iconElement.HorizontalAlignment(showLabels ? HorizontalAlignment::Left : HorizontalAlignment::Stretch); @@ -753,7 +692,7 @@ void UpdateTaskListButtonWidth(FrameworkElement taskListButtonElement, indicatorElement.MinWidth(minWidth); - if (wcscmp(indicatorClassName, L"ProgressIndicator") == 0) { + if (isProgressIndicator) { auto element = indicatorElement; if ((element = FindChildByName(element, L"LayoutRoot")) && (element = FindChildByName(element, L"ProgressBarRoot")) && @@ -803,6 +742,156 @@ void UpdateTaskListButtonWidth(FrameworkElement taskListButtonElement, } } +void UpdateTaskListButtonWithLabelStyle( + FrameworkElement taskListButtonElement) { + auto iconPanelElement = + FindChildByName(taskListButtonElement, L"IconPanel"); + if (!iconPanelElement) { + return; + } + + auto iconElement = FindChildByName(iconPanelElement, L"Icon"); + if (!iconElement) { + return; + } + + double taskListButtonWidth = taskListButtonElement.ActualWidth(); + + double iconPanelWidth = iconPanelElement.ActualWidth(); + + auto columnDefinitions = + iconPanelElement.as().ColumnDefinitions(); + + auto firstColumnWidth = columnDefinitions.GetAt(0).Width(); + auto firstColumnWidthPixels = + firstColumnWidth.GridUnitType == GridUnitType::Pixel + ? firstColumnWidth.Value + : 0.0; + + double secondColumnWidthPixels = + g_unloading ? 0 : g_settings.taskbarItemWidth; + if (secondColumnWidthPixels > 0) { + secondColumnWidthPixels -= firstColumnWidthPixels; + if (secondColumnWidthPixels < 1) { + secondColumnWidthPixels = 1; + } + } + + if (secondColumnWidthPixels > 0) { + columnDefinitions.GetAt(1).Width(GridLength({ + .Value = secondColumnWidthPixels, + .GridUnitType = GridUnitType::Pixel, + })); + } else { + columnDefinitions.GetAt(1).Width(GridLength({ + .Value = 1, + .GridUnitType = GridUnitType::Auto, + })); + } + + auto labelControlElement = + FindChildByName(iconPanelElement, L"LabelControl") + .as(); + if (labelControlElement) { + labelControlElement.HorizontalAlignment( + g_unloading ? HorizontalAlignment::Center + : HorizontalAlignment::Left); + + labelControlElement.TextTrimming( + g_unloading ? TextTrimming::Clip : TextTrimming::CharacterEllipsis); + + auto labelControlMargin = labelControlElement.Margin(); + labelControlMargin.Left = + g_unloading ? 0 + : (iconElement.ActualWidth() - 24 + + g_settings.spaceBetweenIconAndLabel - 8); + labelControlMargin.Right = + g_unloading ? 0 : (g_settings.leftAndRightPaddingSize - 10); + labelControlElement.Margin(labelControlMargin); + + double fontSize = g_unloading ? 12 : g_settings.fontSize; + if (labelControlElement.FontSize() != fontSize) { + labelControlElement.FontSize(fontSize); + } + } + + iconElement.HorizontalAlignment((g_unloading || !labelControlElement) + ? HorizontalAlignment::Stretch + : HorizontalAlignment::Left); + + auto iconMargin = iconElement.Margin(); + iconMargin.Left = (g_unloading || !labelControlElement) + ? 0 + : g_settings.leftAndRightPaddingSize; + iconMargin.Right = 0; + iconElement.Margin(iconMargin); + + auto iconPanelMargin = iconPanelElement.Margin(); + double overflowWidth = + iconPanelMargin.Left + iconPanelWidth - taskListButtonWidth; + + PCWSTR indicatorClassNames[] = { + L"RunningIndicator", + L"ProgressIndicator", + }; + for (auto indicatorClassName : indicatorClassNames) { + auto indicatorElement = + FindChildByName(iconPanelElement, indicatorClassName); + if (!indicatorElement) { + continue; + } + + bool isProgressIndicator = + wcscmp(indicatorClassName, L"ProgressIndicator") == 0; + + IndicatorStyle indicatorStyle = + g_unloading + ? IndicatorStyle::left + : (isProgressIndicator ? g_settings.progressIndicatorStyle + : g_settings.runningIndicatorStyle); + + if (indicatorStyle == IndicatorStyle::left) { + indicatorElement.SetValue(Controls::Grid::ColumnSpanProperty(), + winrt::box_value(1)); + } else { + indicatorElement.SetValue(Controls::Grid::ColumnSpanProperty(), + winrt::box_value(2)); + } + + double minWidth = 0; + + if (indicatorStyle == IndicatorStyle::centerDynamic) { + if (firstColumnWidthPixels > 0) { + minWidth = indicatorElement.Width() * taskListButtonWidth / + firstColumnWidthPixels; + } + } else if (indicatorStyle == IndicatorStyle::fullWidth) { + minWidth = taskListButtonWidth - 6; + } + + indicatorElement.MinWidth(minWidth); + + auto indicatorMargin = indicatorElement.Margin(); + indicatorMargin.Left = 0; + indicatorMargin.Right = + indicatorStyle == IndicatorStyle::left ? 0 : overflowWidth; + indicatorElement.Margin(indicatorMargin); + + if (isProgressIndicator) { + auto element = indicatorElement; + if ((element = FindChildByName(element, L"LayoutRoot")) && + (element = FindChildByName(element, L"ProgressBarRoot")) && + (element = FindChildByClassName( + element, L"Windows.UI.Xaml.Controls.Border")) && + (element = FindChildByClassName( + element, L"Windows.UI.Xaml.Controls.Grid")) && + (element = FindChildByName(element, L"ProgressBarTrack"))) { + element.MinWidth(minWidth); + } + } + } +} + void UpdateTaskListButtonCustomizations( FrameworkElement taskListButtonElement) { auto iconPanelElement = @@ -835,6 +924,20 @@ void UpdateTaskListButtonCustomizations( return; } + // Only true with the native labels implementation of Windows. + auto columnDefinitions = + iconPanelElement.as().ColumnDefinitions(); + if (columnDefinitions.Size() == 2) { + UpdateTaskListButtonWithLabelStyle(taskListButtonElement); + return; + } + + if (g_hasNativeLabelsImplementation) { + // Should never happen. + Wh_Log(L"Unexpected button properties"); + return; + } + if (!g_initialTaskbarItemWidth) { g_initialTaskbarItemWidth = iconPanelWidth; } @@ -956,6 +1059,10 @@ void WINAPI TaskListButton_UpdateButtonPadding_Hook(void* pThis) { TaskListButton_UpdateButtonPadding_Original(pThis); + if (g_hasNativeLabelsImplementation) { + return; + } + void* taskListButtonIUnknownPtr = (void**)pThis + 3; winrt::Windows::Foundation::IUnknown taskListButtonIUnknown; winrt::copy_from_abi(taskListButtonIUnknown, taskListButtonIUnknownPtr); @@ -972,6 +1079,10 @@ void WINAPI TaskListButton_UpdateBadgeSize_Hook(void* pThis) { TaskListButton_UpdateBadgeSize_Original(pThis); + if (g_hasNativeLabelsImplementation) { + return; + } + void* taskListButtonIUnknownPtr = (void**)pThis + 3; winrt::Windows::Foundation::IUnknown taskListButtonIUnknown; winrt::copy_from_abi(taskListButtonIUnknown, taskListButtonIUnknownPtr); @@ -990,6 +1101,10 @@ void WINAPI TaskbarFrame_OnTaskbarLayoutChildBoundsChanged_Hook(void* pThis) { TaskbarFrame_OnTaskbarLayoutChildBoundsChanged_Original(pThis); + if (g_hasNativeLabelsImplementation) { + return; + } + void* taskbarFrameIUnknownPtr = (void**)pThis + 3; winrt::Windows::Foundation::IUnknown taskbarFrameIUnknown; winrt::copy_from_abi(taskbarFrameIUnknown, taskbarFrameIUnknownPtr); @@ -1035,6 +1150,10 @@ void WINAPI TaskListButton_Icon_Hook(void* pThis, LONG_PTR randomAccessStream) { TaskListButton_Icon_Original(pThis, randomAccessStream); + if (g_hasNativeLabelsImplementation) { + return; + } + if (!g_inGroupChanged) { return; } @@ -1060,45 +1179,39 @@ void WINAPI TaskListButton_Icon_Hook(void* pThis, LONG_PTR randomAccessStream) { } } -using ISizeChangedEventArgs_PreviousSize_t = winrt::Windows::Foundation::Size*( - WINAPI*)(void* pThis, winrt::Windows::Foundation::Size* size); -ISizeChangedEventArgs_PreviousSize_t - ISizeChangedEventArgs_PreviousSize_Original; -winrt::Windows::Foundation::Size* WINAPI -ISizeChangedEventArgs_PreviousSize_Hook( - void* pThis, - winrt::Windows::Foundation::Size* size) { +using ITaskbarAppItemViewModel_get_HasLabel_t = + HRESULT(WINAPI*)(void* pThis, bool* hasLabel); +ITaskbarAppItemViewModel_get_HasLabel_t + ITaskbarAppItemViewModel_get_HasLabel_Original; +HRESULT WINAPI ITaskbarAppItemViewModel_get_HasLabel_Hook(void* pThis, + bool* hasLabel) { Wh_Log(L">"); - ISizeChangedEventArgs_PreviousSize_Original(pThis, size); - - // Return initial item width to prevent auto collapse if running with the - // native labels implementation of Windows. - winrt::Windows::Foundation::IInspectable obj = nullptr; - winrt::copy_from_abi(obj, *(void**)pThis); - if (winrt::get_class_name(obj) == L"Taskbar.TaskListButton" && - g_initialTaskbarItemWidth) { - static std::optional windowsLabelsImplementation; - if (!windowsLabelsImplementation) { - auto taskListButtonElement = obj.try_as(); - if (taskListButtonElement) { - auto iconPanelElement = - FindChildByName(taskListButtonElement, L"IconPanel"); - if (iconPanelElement) { - auto columnDefinitions = - iconPanelElement.as() - .ColumnDefinitions(); - windowsLabelsImplementation = columnDefinitions.Size() == 2; - } - } - } + HRESULT ret = + ITaskbarAppItemViewModel_get_HasLabel_Original(pThis, hasLabel); + if (SUCCEEDED(ret) && !g_unloading) { + *hasLabel = true; + } + + return ret; +} + +using TaskbarSettings_GroupingMode_t = DWORD(WINAPI*)(void* pThis); +TaskbarSettings_GroupingMode_t TaskbarSettings_GroupingMode_Original; +DWORD WINAPI TaskbarSettings_GroupingMode_Hook(void* pThis) { + Wh_Log(L">"); + + DWORD ret = TaskbarSettings_GroupingMode_Original(pThis); - if (windowsLabelsImplementation.value_or(false)) { - size->Width = g_initialTaskbarItemWidth; + if (g_overrideGroupingMode) { + if (ret == 2) { + ret = 0; + } else { + ret = 2; } } - return size; + return ret; } void LoadSettings() { @@ -1138,7 +1251,23 @@ void LoadSettings() { } void ApplySettings() { - RecalculateLabels(); + if (!g_hasNativeLabelsImplementation) { + RecalculateLabels(); + } else { + HWND hTaskbarWnd = GetTaskbarWnd(); + if (!hTaskbarWnd) { + return; + } + + // Trigger TrayUI::_HandleSettingChange. + g_overrideGroupingMode = true; + SendMessage(hTaskbarWnd, WM_SETTINGCHANGE, 0, 0); + g_overrideGroupingMode = false; + + Sleep(400); + + SendMessage(hTaskbarWnd, WM_SETTINGCHANGE, 0, 0); + } } struct SYMBOL_HOOK { @@ -1425,11 +1554,20 @@ bool HookTaskbarViewDllSymbols(HMODULE module) { }, { { - LR"(public: __cdecl winrt::impl::consume_Windows_UI_Xaml_ISizeChangedEventArgs::PreviousSize(void)const )", - LR"(public: __cdecl winrt::impl::consume_Windows_UI_Xaml_ISizeChangedEventArgs::PreviousSize(void)const __ptr64)", + LR"(public: virtual int __cdecl winrt::impl::produce::get_HasLabel(bool *))", + LR"(public: virtual int __cdecl winrt::impl::produce::get_HasLabel(bool * __ptr64) __ptr64)", + }, + (void**)&ITaskbarAppItemViewModel_get_HasLabel_Original, + (void*)ITaskbarAppItemViewModel_get_HasLabel_Hook, + true, + }, + { + { + LR"(public: __cdecl winrt::impl::consume_WindowsUdk_UI_Shell_ITaskbarSettings5::GroupingMode(void)const )", + LR"(public: __cdecl winrt::impl::consume_WindowsUdk_UI_Shell_ITaskbarSettings5::GroupingMode(void)const __ptr64)", }, - (void**)&ISizeChangedEventArgs_PreviousSize_Original, - (void*)ISizeChangedEventArgs_PreviousSize_Hook, + (void**)&TaskbarSettings_GroupingMode_Original, + (void*)TaskbarSettings_GroupingMode_Hook, true, }, }; @@ -1519,8 +1657,13 @@ BOOL ModInitWithTaskbarView(HMODULE taskbarViewModule) { return FALSE; } - if (!HookTaskbarDllSymbols()) { - return FALSE; + g_hasNativeLabelsImplementation = + !!ITaskbarAppItemViewModel_get_HasLabel_Original; + + if (!g_hasNativeLabelsImplementation) { + if (!HookTaskbarDllSymbols()) { + return FALSE; + } } return TRUE;