From 23129dc99634ad8037cd80cd3f01a7e79b63bcfc Mon Sep 17 00:00:00 2001
From: Michael Maltsev <4129781+m417z@users.noreply.github.com>
Date: Mon, 11 Dec 2023 16:00:58 +0200
Subject: [PATCH] Windows 11 Taskbar Styler v1.2 (#420)
* Index, properties and a visual state can now be specified for a target. Refer to the mod description for more information and examples.
* Styles can now be specified per visual state.
* Styles can now be specified in XAML form.
* Customized styles are now restored on mod unload.
* Fixed a possible crash on mod unload.
---
mods/windows-11-taskbar-styler.wh.cpp | 940 +++++++++++++++++++-------
1 file changed, 687 insertions(+), 253 deletions(-)
diff --git a/mods/windows-11-taskbar-styler.wh.cpp b/mods/windows-11-taskbar-styler.wh.cpp
index 75c797a7f..a766b075d 100644
--- a/mods/windows-11-taskbar-styler.wh.cpp
+++ b/mods/windows-11-taskbar-styler.wh.cpp
@@ -2,7 +2,7 @@
// @id windows-11-taskbar-styler
// @name Windows 11 Taskbar Styler
// @description An advanced mod to override style attributes of the taskbar control elements
-// @version 1.1.3
+// @version 1.2
// @author m417z
// @github https://github.com/m417z
// @twitter https://twitter.com/m417z
@@ -43,15 +43,32 @@ taskbar's control elements in real time, and experiment with various styles.
## Control styles
-Each entry has a target control and list of styles.
+Each entry has a target control and a list of styles.
The target control is written as `Control` or `Control#Name`, i.e. the target
control tag name, such as `taskbar:TaskListButton` or `Rectangle`, optionally
followed by `#` and the target control's `x:Name` attribute. The target control
-can also include parent elements, separated by `>`, for example:
-`ParentControl#ParentName > Control#Name`.
-
-Each style is written as `Style=Value`, for example: `Height=5`.
+can also include:
+* Child control index, for example: `Control#Name[2]` will only match the
+ relevant control that's also the second child among all of its parent's child
+ controls.
+* Control properties, for example:
+ `Control#Name[Property1=Value1][Property2=Value2]`.
+* Parent controls, separated by `>`, for example: `ParentControl#ParentName >
+ Control#Name`.
+* Visual state group name, for example: `Control#Name@VisualStateGroupName`. It
+ can be specified for the target control or for a parent control, but can be
+ specified only once per target. The visual state group can be used in styles
+ as specified below.
+
+**Note**: The target is evaluated only once. If, for example, the index or the
+properties of a control change, the target conditions aren't evaluated again.
+
+Each style is written as `Style=Value`, for example: `Height=5`. The `:=` syntax
+can be used to use XAML syntax, for example: `Fill:=`. In addition, a visual state can be specified as following:
+`Style@VisualState=Value`, in which case the style will only apply when the
+visual state group specified in the target matches the specified visual state.
A couple of practical examples:
@@ -62,37 +79,44 @@ A couple of practical examples:
* Target: `taskbar:TaskListButton`
* Style: `CornerRadius=0`
-### Task list button background color
-
-![Screenshot](https://i.imgur.com/eP13uBu.png)
-
-* Target: `taskbar:TaskListButtonPanel > Border#BackgroundElement`
-* Style: `Background=gray`
-
### Running indicator size and color
-![Screenshot](https://i.imgur.com/enttdYJ.png)
+![Screenshot](https://i.imgur.com/mR5c3F5.png)
+
+* Target: `taskbar:TaskListLabeledButtonPanel@RunningIndicatorStates >
+ Rectangle#RunningIndicator`
+* Styles:
+ * `Fill=#FFED7014`
+ * `Height=2`
+ * `Width=12`
+ * `Fill@ActiveRunningIndicator=Red`
+ * `Width@ActiveRunningIndicator=20`
-* Target: `Rectangle#RunningIndicator`
-* Styles: `Fill=#FFED7014`, `Height=2`, `MinWidth=12`
+### Task list button background gradient
-**Note:** Currently, it's not possible to set styles only for a specific visual
-state. In this example, the color and width apply for all states, including
-active and inactive buttons.
+![Screenshot](https://i.imgur.com/LNPcw0G.png)
-### Time font and color
+* Targets:
+ * `taskbar:TaskListButtonPanel > Border#BackgroundElement`
+ * `taskbar:TaskListLabeledButtonPanel > Border#BackgroundElement`
+* Style: `Background:=`
-![Screenshot](https://i.imgur.com/ZRM0drT.png)
+### Hide the start button
-* Target: `TextBlock#TimeInnerTextBlock`
-* Styles: `FontFamily=Comic Sans MS`, `FontSize=14`, `Foreground=Red`
+* Target:
+ `taskbar:ExperienceToggleButton#LaunchListButton[AutomationProperties.AutomationId=StartButton]`
+* Style: `Visibility=Collapsed`
-### Hide date
+### Hide the network notification icon
-![Screenshot](https://i.imgur.com/9fGyL9W.png)
+* Target: `systemtray:OmniButton#ControlCenterButton > Grid > ContentPresenter >
+ ItemsPresenter > StackPanel > ContentPresenter[1]`
+* Style: `Visibility=Collapsed`
-* Target: `TextBlock#DateInnerTextBlock`
-* Styles: `Visibility=Collapsed`
+**Note**: To hide the volume notification icon instead, use `[2]` instead of
+`[1]`.
## Resource variables
@@ -132,34 +156,9 @@ relevant `#pragma region` regions in the code editor.
- value: "0"
$name: Value
$name: Resource variables
-- promptForExplorerRestart: true
- $name: Prompt for Explorer restart
- $description: >-
- Show a message prompting to restart Explorer on each style change
*/
// ==/WindhawkModSettings==
-#include
-
-#undef GetCurrentTime
-
-#include
-
-std::atomic g_targetThreadId = 0;
-
-void ApplyCustomizations(winrt::Windows::UI::Xaml::FrameworkElement element);
-
-HMODULE GetCurrentModuleHandle() {
- HMODULE module;
- if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
- GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
- L"", &module)) {
- return nullptr;
- }
-
- return module;
-}
-
////////////////////////////////////////////////////////////////////////////////
// clang-format off
@@ -299,11 +298,11 @@ extern "C"{
#pragma region Application Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
-#pragma warning(push)
-#pragma warning(disable:4668)
-#pragma warning(disable:4001)
+// #pragma warning(push)
+// #pragma warning(disable:4668)
+// #pragma warning(disable:4001)
// #pragma once
-#pragma warning(pop)
+// #pragma warning(pop)
// Win32 API definitions
#define E_NOTFOUND HRESULT_FROM_WIN32(ERROR_NOT_FOUND)
#define E_UNKNOWNTYPE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_XAML, 40L)
@@ -1044,6 +1043,35 @@ extern RPC_IF_HANDLE __MIDL_itf_windows2Eui2Examl2Ehosting2Edesktopwindowxamlsou
#pragma endregion // windows_ui_xaml_hosting_desktopwindowxamlsource_h
+// clang-format on
+////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#undef GetCurrentTime
+
+#include
+
+std::atomic g_targetThreadId = 0;
+
+void ApplyCustomizations(InstanceHandle handle,
+ winrt::Windows::UI::Xaml::FrameworkElement element);
+void CleanupCustomizations(InstanceHandle handle);
+
+HMODULE GetCurrentModuleHandle() {
+ HMODULE module;
+ if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ L"", &module)) {
+ return nullptr;
+ }
+
+ return module;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// clang-format off
+
#pragma region visualtreewatcher_hpp
#include
@@ -1129,23 +1157,26 @@ HRESULT VisualTreeWatcher::OnVisualTreeChange(ParentChildRelation, VisualElement
Wh_Log(L"Element type: %s", element.Type);
- const auto inspectable = FromHandle(element.Handle);
-
- auto frameworkElement = inspectable.try_as();
- if (!frameworkElement)
+ if (mutationType == Add)
{
- const auto desktopXamlSource = FromHandle(element.Handle);
- frameworkElement = desktopXamlSource.Content().try_as();
- }
+ const auto inspectable = FromHandle(element.Handle);
- if (frameworkElement)
- {
- Wh_Log(L"FrameworkElement name: %s", frameworkElement.Name().c_str());
- }
+ auto frameworkElement = inspectable.try_as();
+ if (!frameworkElement)
+ {
+ const auto desktopXamlSource = FromHandle(element.Handle);
+ frameworkElement = desktopXamlSource.Content().try_as();
+ }
- if (frameworkElement && mutationType == Add)
+ if (frameworkElement)
+ {
+ Wh_Log(L"FrameworkElement name: %s", frameworkElement.Name().c_str());
+ ApplyCustomizations(element.Handle, frameworkElement);
+ }
+ }
+ else if (mutationType == Remove)
{
- ApplyCustomizations(frameworkElement);
+ CleanupCustomizations(element.Handle);
}
return S_OK;
@@ -1345,9 +1376,11 @@ HRESULT InjectWindhawkTAP() noexcept
// clang-format on
////////////////////////////////////////////////////////////////////////////////
+#include
#include
#include
#include
+#include
#include
#include
@@ -1373,29 +1406,75 @@ struct deleter_from_fn {
using string_setting_unique_ptr =
std::unique_ptr>;
-HANDLE g_restartExplorerPromptThread;
-std::atomic g_restartExplorerPromptWindow;
-
using PropertyKeyValue =
std::pair;
struct ElementMatcher {
std::wstring type;
std::wstring name;
+ std::wstring visualStateGroup;
+ int oneBasedIndex = 0;
+ std::vector> propertyValuesStr;
+ std::vector propertyValues;
+};
+
+struct StyleRule {
+ std::wstring name;
+ std::wstring visualState;
+ std::wstring value;
+ bool isXamlValue = false;
};
-struct ElementPropertyOverrides {
+// Property -> visual state -> value.
+using PropertyOverrides = std::unordered_map<
+ DependencyProperty,
+ std::unordered_map>;
+
+struct ElementCustomizationRules {
ElementMatcher elementMatcher;
std::vector parentElementMatchers;
- std::vector propertyValues;
+ PropertyOverrides propertyOverrides;
};
-bool g_propertyOverridesLoaded;
-std::vector g_propertyOverrides;
-DWORD g_propertyOverridesUpdateCount = 0;
+bool g_elementsCustomizationRulesLoaded;
+std::vector g_elementsCustomizationRules;
+
+struct ElementPropertyCustomizationState {
+ std::optional originalValue;
+ std::optional customValue;
+ int64_t propertyChangedToken = 0;
+};
+
+struct ElementCustomizationState {
+ winrt::weak_ref element;
+ std::unordered_map
+ propertyCustomizationStates;
+ winrt::weak_ref visualStateGroup;
+ winrt::event_token visualStateGroupCurrentStateChangedToken;
+};
+
+std::unordered_map
+ g_elementsCustomizationState;
+
+bool g_elementPropertyModifying;
+
+// https://stackoverflow.com/a/12835139
+VisualStateGroup GetVisualStateGroup(FrameworkElement element,
+ std::wstring_view stateGroupName) {
+ auto list = VisualStateManager::GetVisualStateGroups(element);
+
+ for (const auto& v : list) {
+ if (v.Name() == stateGroupName) {
+ return v;
+ }
+ }
+
+ return nullptr;
+}
bool TestElementMatcher(FrameworkElement element,
- const ElementMatcher& matcher) {
+ const ElementMatcher& matcher,
+ VisualStateGroup* visualStateGroup) {
if (!matcher.type.empty() &&
matcher.type != winrt::get_class_name(element)) {
return false;
@@ -1405,13 +1484,78 @@ bool TestElementMatcher(FrameworkElement element,
return false;
}
+ if (matcher.oneBasedIndex) {
+ auto parent = Media::VisualTreeHelper::GetParent(element);
+ if (!parent) {
+ return false;
+ }
+
+ int index = matcher.oneBasedIndex - 1;
+ if (index < 0 ||
+ index >= Media::VisualTreeHelper::GetChildrenCount(parent) ||
+ Media::VisualTreeHelper::GetChild(parent, index) != element) {
+ return false;
+ }
+ }
+
+ auto elementDo = element.as();
+
+ for (const auto& propertyValue : matcher.propertyValues) {
+ const auto value = elementDo.ReadLocalValue(propertyValue.first);
+ const auto className = winrt::get_class_name(value);
+ const auto expectedClassName =
+ winrt::get_class_name(propertyValue.second);
+ if (className != expectedClassName) {
+ Wh_Log(L"Different property class: %s vs. %s", className.c_str(),
+ expectedClassName.c_str());
+ return false;
+ }
+
+ if (className == L"Windows.Foundation.IReference`1") {
+ if (winrt::unbox_value(propertyValue.second) ==
+ winrt::unbox_value(value)) {
+ continue;
+ }
+
+ return false;
+ }
+
+ if (className == L"Windows.Foundation.IReference`1") {
+ if (winrt::unbox_value(propertyValue.second) ==
+ winrt::unbox_value(value)) {
+ continue;
+ }
+
+ return false;
+ }
+
+ if (className == L"Windows.Foundation.IReference`1") {
+ if (winrt::unbox_value(propertyValue.second) ==
+ winrt::unbox_value(value)) {
+ continue;
+ }
+
+ return false;
+ }
+
+ Wh_Log(L"Unsupported property class: %s", className.c_str());
+ return false;
+ }
+
+ if (!matcher.visualStateGroup.empty() && visualStateGroup) {
+ *visualStateGroup =
+ GetVisualStateGroup(element, matcher.visualStateGroup);
+ }
+
return true;
}
-const std::vector* FindElementPropertyOverrides(
- FrameworkElement element) {
- for (const auto& override : g_propertyOverrides) {
- if (!TestElementMatcher(element, override.elementMatcher)) {
+const ElementCustomizationRules* FindElementCustomizationRules(
+ FrameworkElement element,
+ VisualStateGroup* visualStateGroup) {
+ for (const auto& override : g_elementsCustomizationRules) {
+ if (!TestElementMatcher(element, override.elementMatcher,
+ visualStateGroup)) {
continue;
}
@@ -1428,14 +1572,15 @@ const std::vector* FindElementPropertyOverrides(
break;
}
- if (!TestElementMatcher(parentElementIter, matcher)) {
+ if (!TestElementMatcher(parentElementIter, matcher,
+ visualStateGroup)) {
parentElementMatchFailed = true;
break;
}
}
if (!parentElementMatchFailed) {
- return &override.propertyValues;
+ return &override;
}
}
@@ -1445,42 +1590,213 @@ const std::vector* FindElementPropertyOverrides(
void ProcessAllStylesFromSettings();
void ProcessResourceVariablesFromSettings();
-void ApplyCustomizations(FrameworkElement element) {
- if (!g_propertyOverridesLoaded) {
+void ApplyCustomizations(InstanceHandle handle, FrameworkElement element) {
+ if (!g_elementsCustomizationRulesLoaded) {
ProcessAllStylesFromSettings();
ProcessResourceVariablesFromSettings();
- g_propertyOverridesLoaded = true;
+ g_elementsCustomizationRulesLoaded = true;
}
- auto propertyValues = FindElementPropertyOverrides(element);
- if (!propertyValues) {
+ VisualStateGroup visualStateGroup;
+ auto rules = FindElementCustomizationRules(element, &visualStateGroup);
+ if (!rules) {
return;
}
- Wh_Log(L"Applying %zu styles", propertyValues->size());
+ Wh_Log(L"Applying styles");
- auto elementDo = element.as();
+ auto& elementCustomizationState = g_elementsCustomizationState[handle];
- for (const auto& [property, value] : *propertyValues) {
- element.SetValue(property, value);
- elementDo.RegisterPropertyChangedCallback(
- property,
- [value = value,
- propertyOverridesUpdateCount = g_propertyOverridesUpdateCount](
- DependencyObject sender, DependencyProperty property) {
- if (propertyOverridesUpdateCount !=
- g_propertyOverridesUpdateCount) {
- return;
+ {
+ auto oldElement = elementCustomizationState.element.get();
+ if (oldElement) {
+ auto oldElementDo = oldElement.as();
+ for (const auto& [property, state] :
+ elementCustomizationState.propertyCustomizationStates) {
+ oldElementDo.UnregisterPropertyChangedCallback(
+ property, state.propertyChangedToken);
+
+ if (state.originalValue) {
+ oldElement.SetValue(property, *state.originalValue);
}
+ }
+ }
+
+ auto oldVisualStateGroup =
+ elementCustomizationState.visualStateGroup.get();
+ if (oldVisualStateGroup) {
+ oldVisualStateGroup.CurrentStateChanged(
+ elementCustomizationState
+ .visualStateGroupCurrentStateChangedToken);
+ }
+ }
+
+ elementCustomizationState = {
+ .element = element,
+ };
+
+ auto elementDo = element.as();
+
+ VisualState currentVisualState(
+ visualStateGroup ? visualStateGroup.CurrentState() : nullptr);
+
+ std::wstring currentVisualStateName(
+ currentVisualState ? currentVisualState.Name() : L"");
+
+ for (auto& [property, valuesPerVisualState] : rules->propertyOverrides) {
+ const auto [propertyCustomizationStatesIt, inserted] =
+ elementCustomizationState.propertyCustomizationStates.insert(
+ {property, {}});
+ if (!inserted) {
+ continue;
+ }
+
+ auto& propertyCustomizationState =
+ propertyCustomizationStatesIt->second;
+
+ auto it = valuesPerVisualState.find(currentVisualStateName);
+ if (it == valuesPerVisualState.end() &&
+ !currentVisualStateName.empty()) {
+ it = valuesPerVisualState.find(L"");
+ }
+
+ if (it != valuesPerVisualState.end()) {
+ propertyCustomizationState.originalValue =
+ element.ReadLocalValue(property);
+ propertyCustomizationState.customValue = it->second;
+ element.SetValue(property, it->second);
+ }
+
+ propertyCustomizationState.propertyChangedToken =
+ elementDo.RegisterPropertyChangedCallback(
+ property,
+ [&propertyCustomizationState](DependencyObject sender,
+ DependencyProperty property) {
+ if (g_elementPropertyModifying) {
+ return;
+ }
+
+ auto element = sender.try_as();
+ if (!element) {
+ return;
+ }
+
+ if (!propertyCustomizationState.customValue) {
+ return;
+ }
- auto element = sender.try_as();
- if (element) {
Wh_Log(L"Re-applying style for %s",
winrt::get_class_name(element).c_str());
- element.SetValue(property, value);
- }
- });
+
+ auto localValue = element.ReadLocalValue(property);
+
+ if (*propertyCustomizationState.customValue != localValue) {
+ propertyCustomizationState.originalValue = localValue;
+ }
+
+ g_elementPropertyModifying = true;
+ element.SetValue(property,
+ *propertyCustomizationState.customValue);
+ g_elementPropertyModifying = false;
+ });
+ }
+
+ if (visualStateGroup) {
+ elementCustomizationState.visualStateGroup = visualStateGroup;
+
+ elementCustomizationState.visualStateGroupCurrentStateChangedToken =
+ visualStateGroup.CurrentStateChanged(
+ [rules, &elementCustomizationState](
+ winrt::Windows::Foundation::IInspectable const& sender,
+ VisualStateChangedEventArgs const& e) {
+ auto element = elementCustomizationState.element.get();
+
+ Wh_Log(L"Re-applying all styles for %s",
+ winrt::get_class_name(element).c_str());
+
+ g_elementPropertyModifying = true;
+
+ auto& propertyCustomizationStates =
+ elementCustomizationState.propertyCustomizationStates;
+
+ for (auto& [property, valuesPerVisualState] :
+ rules->propertyOverrides) {
+ auto& propertyCustomizationState =
+ propertyCustomizationStates.at(property);
+
+ auto newState = e.NewState();
+ auto newStateName =
+ std::wstring{newState ? newState.Name() : L""};
+ auto it = valuesPerVisualState.find(newStateName);
+ if (it == valuesPerVisualState.end()) {
+ it = valuesPerVisualState.find(L"");
+ if (it != valuesPerVisualState.end()) {
+ auto oldState = e.OldState();
+ auto oldStateName = std::wstring{
+ oldState ? oldState.Name() : L""};
+ if (!valuesPerVisualState.contains(
+ oldStateName)) {
+ continue;
+ }
+ }
+ }
+
+ if (it != valuesPerVisualState.end()) {
+ if (!propertyCustomizationState.originalValue) {
+ propertyCustomizationState.originalValue =
+ element.ReadLocalValue(property);
+ }
+
+ propertyCustomizationState.customValue = it->second;
+ element.SetValue(property, it->second);
+ } else {
+ if (propertyCustomizationState.originalValue) {
+ if (*propertyCustomizationState.originalValue ==
+ DependencyProperty::UnsetValue()) {
+ element.ClearValue(property);
+ } else {
+ element.SetValue(property,
+ *propertyCustomizationState
+ .originalValue);
+ }
+
+ propertyCustomizationState.originalValue
+ .reset();
+ }
+
+ propertyCustomizationState.customValue.reset();
+ }
+ }
+
+ g_elementPropertyModifying = false;
+ });
+ }
+}
+
+void CleanupCustomizations(InstanceHandle handle) {
+ auto it = g_elementsCustomizationState.find(handle);
+ if (it == g_elementsCustomizationState.end()) {
+ return;
+ }
+
+ auto& [k, v] = *it;
+
+ auto oldElement = v.element.get();
+ if (oldElement) {
+ auto oldElementDo = oldElement.as();
+ for (const auto& [property, state] : v.propertyCustomizationStates) {
+ oldElementDo.UnregisterPropertyChangedCallback(
+ property, state.propertyChangedToken);
+ }
}
+
+ auto oldVisualStateGroup = v.visualStateGroup.get();
+ if (oldVisualStateGroup) {
+ oldVisualStateGroup.CurrentStateChanged(
+ v.visualStateGroupCurrentStateChangedToken);
+ }
+
+ g_elementsCustomizationState.erase(it);
}
// https://stackoverflow.com/a/5665377
@@ -1539,14 +1855,28 @@ std::vector SplitStringView(std::wstring_view s,
return res;
}
-struct ResolveTypeAndStylesResult {
- std::wstring type;
- std::vector propertyValues;
-};
+std::wstring AdjustTypeName(std::wstring_view type) {
+ static const std::vector>
+ adjustments = {
+ {L"Taskbar.", L"taskbar:"},
+ {L"SystemTray.", L"systemtray:"},
+ {L"Microsoft.UI.Xaml.Control.", L"muxc:"},
+ };
+
+ for (const auto& adjustment : adjustments) {
+ if (type.starts_with(adjustment.first)) {
+ auto result = std::wstring{adjustment.second};
+ result += type.substr(adjustment.first.size());
+ return result;
+ }
+ }
-ResolveTypeAndStylesResult ResolveTypeAndStyles(
- std::wstring_view targetType,
- std::vector> styles) {
+ return std::wstring{type};
+}
+
+void ResolveTypeAndStyles(ElementMatcher* elementMatcher,
+ std::vector styleRules = {},
+ PropertyOverrides* propertyOverrides = nullptr) {
std::wstring xaml =
LR"(type));
xaml += L"\">\n";
- for (const auto& [property, value] : styles) {
+ for (const auto& [property, value] : elementMatcher->propertyValuesStr) {
xaml += L" \n";
}
+ for (const auto& rule : styleRules) {
+ xaml += L" \n";
+ } else {
+ xaml +=
+ L">\n"
+ L" \n";
+ xaml += rule.value;
+ xaml +=
+ L"\n"
+ L" \n"
+ L" \n";
+ }
+ }
+
xaml +=
LR"(
)";
@@ -1588,75 +1938,205 @@ ResolveTypeAndStylesResult ResolveTypeAndStyles(
auto [styleKey, styleInspectable] = resourceDictionary.First().Current();
auto style = styleInspectable.as