Skip to content

Commit bc607b6

Browse files
authored
Make MPO tweak a three-state control (#4897)
* refactor: make MPO tweak a three-state control * docs: explain MPO tweak states * docs: address code review comments * refactor: make Multiplane Overlay config-driven
1 parent 7f18b4f commit bc607b6

12 files changed

Lines changed: 457 additions & 17 deletions

config/tweaks.json

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,28 +1459,44 @@
14591459
],
14601460
"link": "https://winutil.christitus.com/code-reference/tweaks/customize-preferences/scrollbars"
14611461
},
1462-
"WPFToggleMultiplaneOverlay": {
1462+
"WPFMultiplaneOverlay": {
14631463
"Content": "Multiplane Overlay",
1464-
"Description": "Multiplane Overlay compose multiple image layers, which can sometimes cause issues with graphics cards.",
1464+
"Description": "Multiplane Overlay composes multiple image layers, which can sometimes cause issues with graphics cards. Changes to this preference are applied immediately.",
14651465
"category": "Customize Preferences",
14661466
"panel": "2",
1467-
"Type": "Toggle",
1467+
"Type": "Combobox",
1468+
"ComboItems": [
1469+
"Enabled",
1470+
"Disabled (Compatibility)",
1471+
"Fully Disabled"
1472+
],
1473+
"ComboDescriptions": {
1474+
"Enabled": "Uses Windows' default overlay behavior.",
1475+
"Disabled (Compatibility)": "Disables MPO using OverlayTestMode=5, the less aggressive compatibility method.",
1476+
"Fully Disabled": "Disables MPO using OverlayTestMode=5 and DisableOverlays=1, the more aggressive method."
1477+
},
14681478
"registry": [
14691479
{
14701480
"Path": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\Dwm",
14711481
"Name": "OverlayTestMode",
1472-
"Value": "0",
14731482
"Type": "DWord",
1474-
"OriginalValue": "5",
1475-
"DefaultState": "true"
1483+
"DefaultValue": "0",
1484+
"Values": {
1485+
"Enabled": "<RemoveEntry>",
1486+
"Disabled (Compatibility)": "5",
1487+
"Fully Disabled": "5"
1488+
}
14761489
},
14771490
{
14781491
"Path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\GraphicsDrivers",
14791492
"Name": "DisableOverlays",
1480-
"Value": "0",
14811493
"Type": "DWord",
1482-
"OriginalValue": "1",
1483-
"DefaultState": "true"
1494+
"DefaultValue": "0",
1495+
"Values": {
1496+
"Enabled": "<RemoveEntry>",
1497+
"Disabled (Compatibility)": "<RemoveEntry>",
1498+
"Fully Disabled": "1"
1499+
}
14841500
}
14851501
],
14861502
"link": "https://winutil.christitus.com/code-reference/tweaks/customize-preferences/multiplaneoverlay"

docs/src/content/docs/code-reference/architecture.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,8 @@ Update UI
385385
- `Description`: What it does
386386
- `category`: Essential/Advanced/Customize
387387
- `registry`: Registry changes to make
388+
- `registry[].Values`: Per-state values for a registry-backed combobox
389+
- `registry[].DefaultValue`: Effective value when the registry entry is absent
388390
- `service`: Services to change
389391
- `OriginalValue/State`: For undo functionality
390392

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
function Get-WinUtilRegistryComboState {
2+
<#
3+
.SYNOPSIS
4+
Finds the configured combo-box state matching the current registry values.
5+
6+
.PARAMETER Registry
7+
Registry settings containing a value mapping for each supported state.
8+
9+
.OUTPUTS
10+
The name of the matching state.
11+
#>
12+
param(
13+
[Parameter(Mandatory)]
14+
$Registry
15+
)
16+
17+
foreach ($state in $Registry[0].Values.PSObject.Properties) {
18+
$stateMatches = $true
19+
foreach ($setting in @($Registry)) {
20+
$currentValue = Get-WinUtilRegistryComboValue -Setting $setting
21+
$actualValue = if ($currentValue.Exists -and $null -ne $currentValue.Value) { $currentValue.Value } else { $setting.DefaultValue }
22+
$configuredValue = $setting.Values.PSObject.Properties[$state.Name].Value
23+
# Removal represents the effective Windows default when matching the current state.
24+
$expectedValue = if ($configuredValue -eq "<RemoveEntry>") { $setting.DefaultValue } else { $configuredValue }
25+
if ([string]$actualValue -ne [string]$expectedValue) {
26+
$stateMatches = $false
27+
break
28+
}
29+
}
30+
if ($stateMatches) {
31+
return $state.Name
32+
}
33+
}
34+
35+
throw "Registry values do not match a supported state."
36+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
function Get-WinUtilRegistryComboValue {
2+
<#
3+
.SYNOPSIS
4+
Reads one registry value for a registry-backed combo-box state.
5+
6+
.PARAMETER Setting
7+
The registry setting from the combo-box configuration.
8+
#>
9+
param(
10+
[Parameter(Mandatory)]
11+
$Setting
12+
)
13+
14+
try {
15+
$item = Get-ItemProperty -Path $Setting.Path -Name $Setting.Name -ErrorAction Stop
16+
$property = $item.PSObject.Properties[$Setting.Name]
17+
return [pscustomobject]@{ Exists = $null -ne $property; Value = $property.Value }
18+
} catch [System.Management.Automation.PSArgumentException] {
19+
# The registry provider uses PSArgumentException when a named value is absent.
20+
return [pscustomobject]@{ Exists = $false; Value = $null }
21+
} catch [System.Management.Automation.ItemNotFoundException] {
22+
return [pscustomobject]@{ Exists = $false; Value = $null }
23+
}
24+
}

functions/private/Invoke-WinUtilCurrentSystem.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Function Invoke-WinUtilCurrentSystem {
6161
$serviceKeys = $entry.service
6262
$entryType = $entry.Type
6363

64-
if ($registryKeys -or $serviceKeys) {
64+
if (($registryKeys -or $serviceKeys) -and $entryType -ne "Combobox") {
6565
$Values = @()
6666

6767
if ($entryType -eq "Toggle") {

functions/private/Invoke-WinUtilTweaks.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function Invoke-WinUtilTweaks {
6262
}
6363
}
6464
if ($sync.configs.tweaks.$CheckBox.registry) {
65-
$sync.configs.tweaks.$CheckBox.registry | ForEach-Object {
65+
$sync.configs.tweaks.$CheckBox.registry | Where-Object { -not $psitem.Values } | ForEach-Object {
6666
Set-WinUtilRegistry -Name $psitem.Name -Path $psitem.Path -Type $psitem.Type -Value $psitem.$($values.registry)
6767
}
6868
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
function Set-WinUtilRegistryComboState {
2+
<#
3+
.SYNOPSIS
4+
Applies and verifies a config-defined registry combo-box state.
5+
6+
.PARAMETER Registry
7+
Registry settings containing a value mapping for each supported state.
8+
9+
.PARAMETER State
10+
The state name to apply.
11+
#>
12+
param(
13+
[Parameter(Mandatory)]
14+
$Registry,
15+
16+
[Parameter(Mandatory)]
17+
[string]$State
18+
)
19+
20+
if ($Registry[0].Values.PSObject.Properties.Name -notcontains $State) {
21+
throw "Unknown registry state '$State'."
22+
}
23+
24+
# Preserve exact prior values so a partial update can be rolled back.
25+
$previousValues = foreach ($setting in @($Registry)) {
26+
$currentValue = Get-WinUtilRegistryComboValue -Setting $setting
27+
[pscustomobject]@{ Setting = $setting; Exists = $currentValue.Exists; Value = $currentValue.Value }
28+
}
29+
30+
try {
31+
foreach ($setting in @($Registry)) {
32+
$configuredValue = $setting.Values.PSObject.Properties[$State].Value
33+
$previousValue = $previousValues | Where-Object Setting -EQ $setting
34+
if ($configuredValue -ne "<RemoveEntry>" -or $previousValue.Exists) {
35+
Set-WinUtilRegistry -Name $setting.Name -Path $setting.Path -Type $setting.Type -Value $configuredValue
36+
}
37+
}
38+
39+
# Set-WinUtilRegistry reports write errors without throwing, so verify each result explicitly.
40+
foreach ($setting in @($Registry)) {
41+
$configuredValue = $setting.Values.PSObject.Properties[$State].Value
42+
$currentValue = Get-WinUtilRegistryComboValue -Setting $setting
43+
$writeMatches = if ($configuredValue -eq "<RemoveEntry>") {
44+
-not $currentValue.Exists
45+
} else {
46+
$currentValue.Exists -and [string]$currentValue.Value -eq [string]$configuredValue
47+
}
48+
if (-not $writeMatches) {
49+
throw "The registry values did not match the requested state."
50+
}
51+
}
52+
} catch {
53+
$applyError = $_.Exception.Message
54+
if ([string]::IsNullOrWhiteSpace($applyError)) {
55+
$applyError = "The registry values did not match the requested state."
56+
}
57+
$rollbackFailed = $false
58+
foreach ($previousValue in $previousValues) {
59+
try {
60+
$currentValue = Get-WinUtilRegistryComboValue -Setting $previousValue.Setting
61+
if ($previousValue.Exists -or $currentValue.Exists) {
62+
$rollbackValue = if ($previousValue.Exists) { $previousValue.Value } else { "<RemoveEntry>" }
63+
Set-WinUtilRegistry -Name $previousValue.Setting.Name -Path $previousValue.Setting.Path -Type $previousValue.Setting.Type -Value $rollbackValue
64+
}
65+
$restoredValue = Get-WinUtilRegistryComboValue -Setting $previousValue.Setting
66+
if ($restoredValue.Exists -ne $previousValue.Exists -or ($restoredValue.Exists -and [string]$restoredValue.Value -ne [string]$previousValue.Value)) {
67+
$rollbackFailed = $true
68+
}
69+
} catch {
70+
$rollbackFailed = $true
71+
}
72+
}
73+
if ($rollbackFailed) {
74+
throw "Unable to apply registry state '$State': $applyError. The previous registry state could not be restored."
75+
}
76+
throw "Unable to apply registry state '$State': $applyError"
77+
}
78+
}

functions/public/Invoke-WPFUIElements.ps1

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ function Invoke-WPFUIElements {
7676
Description = $entryInfo.description
7777
Type = $entryInfo.type
7878
ComboItems = $entryInfo.ComboItems
79+
ComboDescriptions = $entryInfo.ComboDescriptions
80+
Registry = $entryInfo.registry
7981
Checked = $entryInfo.Checked
8082
ButtonWidth = $entryInfo.ButtonWidth
8183
GroupName = $entryInfo.GroupName # Added for RadioButton groupings
@@ -247,6 +249,7 @@ function Invoke-WPFUIElements {
247249
$label = New-Object Windows.Controls.Label
248250
$label.Content = $entryInfo.Content
249251
$label.HorizontalAlignment = "Left"
252+
$label.ToolTip = $entryInfo.Description
250253
$label.VerticalAlignment = "Center"
251254
$label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize")
252255
$label.UseLayoutRounding = $true
@@ -261,11 +264,27 @@ function Invoke-WPFUIElements {
261264
$comboBox.SetResourceReference([Windows.Controls.Control]::MarginProperty, "ButtonMargin")
262265
$comboBox.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize")
263266
$comboBox.UseLayoutRounding = $true
267+
$comboBox.Tag = [pscustomobject]@{
268+
Registry = $entryInfo.Registry
269+
State = $null
270+
}
264271
[System.Windows.Automation.AutomationProperties]::SetName($comboBox, $entryInfo.Content)
265272

266-
foreach ($comboitem in ($entryInfo.ComboItems -split " ")) {
273+
$comboItems = if ($entryInfo.ComboItems -is [string]) {
274+
$entryInfo.ComboItems -split " "
275+
} else {
276+
@($entryInfo.ComboItems)
277+
}
278+
279+
foreach ($comboitem in $comboItems) {
267280
$comboBoxItem = New-Object Windows.Controls.ComboBoxItem
268281
$comboBoxItem.Content = $comboitem
282+
if ($entryInfo.ComboDescriptions) {
283+
$comboDescription = $entryInfo.ComboDescriptions.PSObject.Properties[$comboitem].Value
284+
if ($comboDescription) {
285+
$comboBoxItem.ToolTip = $comboDescription
286+
}
287+
}
269288
$comboBoxItem.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize")
270289
$comboBoxItem.UseLayoutRounding = $true
271290
$comboBox.Items.Add($comboBoxItem) | Out-Null
@@ -274,22 +293,82 @@ function Invoke-WPFUIElements {
274293
$horizontalStackPanel.Children.Add($comboBox) | Out-Null
275294
$itemsControl.Items.Add($horizontalStackPanel) | Out-Null
276295

277-
$comboBox.SelectedIndex = 0
296+
if ($entryInfo.Registry -and @($entryInfo.Registry)[0].Values) {
297+
try {
298+
$comboBox.Tag.State = Get-WinUtilRegistryComboState -Registry $entryInfo.Registry
299+
$comboBox.SelectedIndex = @($comboBox.Items.Content).IndexOf([string]$comboBox.Tag.State)
300+
} catch {
301+
$unknownStateItem = New-Object Windows.Controls.ComboBoxItem
302+
$unknownStateItem.Content = "Custom / Unknown - select a state"
303+
$unknownStateItem.IsEnabled = $false
304+
$unknownStateItem.ToolTip = "$($_.Exception.Message) Select one of the supported states to replace these values."
305+
$comboBox.Items.Add($unknownStateItem) | Out-Null
306+
$comboBox.SelectedItem = $unknownStateItem
307+
$comboBox.ToolTip = $unknownStateItem.ToolTip
308+
}
309+
} else {
310+
$comboBox.SelectedIndex = 0
311+
}
278312

279313
# Set initial text
280314
if ($comboBox.Items.Count -gt 0) {
281-
$comboBox.Text = $comboBox.Items[0].Content
315+
$comboBox.Text = $comboBox.SelectedItem.Content
282316
}
283317

318+
$sync[$entryInfo.Name] = $comboBox
319+
284320
# Add SelectionChanged event handler to update the text property
285321
$comboBox.Add_SelectionChanged({
286322
$selectedItem = $this.SelectedItem
287323
if ($selectedItem) {
288324
$this.Text = $selectedItem.Content
325+
$registry = $this.Tag.Registry
326+
if ($registry -and $selectedItem.IsEnabled -and $selectedItem.Content -ne $this.Tag.State) {
327+
try {
328+
Set-WinUtilRegistryComboState -Registry $registry -State $selectedItem.Content
329+
$this.Tag.State = $selectedItem.Content
330+
$this.ToolTip = $null
331+
$unknownStateItem = @($this.Items) | Where-Object Content -EQ "Custom / Unknown - select a state" | Select-Object -First 1
332+
if ($unknownStateItem) {
333+
$this.Items.Remove($unknownStateItem)
334+
}
335+
} catch {
336+
$applyError = $_.Exception.Message
337+
if ([string]::IsNullOrWhiteSpace($applyError)) {
338+
$applyError = "Unable to apply registry state '$($selectedItem.Content)'."
339+
}
340+
$previousState = if ($this.Tag.State) { $this.Tag.State } else { "Custom / Unknown - select a state" }
341+
$this.SelectedItem = @($this.Items) | Where-Object Content -EQ $previousState | Select-Object -First 1
342+
[System.Windows.MessageBox]::Show(
343+
$applyError,
344+
"WinUtil",
345+
[System.Windows.MessageBoxButton]::OK,
346+
[System.Windows.MessageBoxImage]::Warning
347+
) | Out-Null
348+
}
349+
}
289350
}
290351
})
291352

292-
$sync[$entryInfo.Name] = $comboBox
353+
if ($entryInfo.Registry -and @($entryInfo.Registry)[0].Values -and $entryInfo.Link) {
354+
$textBlock = New-Object Windows.Controls.TextBlock
355+
$textBlock.Name = $comboBox.Name + "Link"
356+
$textBlock.Text = "(?)"
357+
$textBlock.ToolTip = $entryInfo.Link
358+
$textBlock.Style = $HoverTextBlockStyle
359+
$textBlock.UseLayoutRounding = $true
360+
$textBlock.VerticalAlignment = "Center"
361+
$textBlock.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize")
362+
$textBlock.Tag = $comboBox
363+
364+
$textBlock.Add_MouseUp({
365+
[System.Object]$Sender = $args[0]
366+
Start-Process $Sender.ToolTip -ErrorAction Stop
367+
})
368+
369+
$horizontalStackPanel.Children.Add($textBlock) | Out-Null
370+
$sync[$textBlock.Name] = $textBlock
371+
}
293372
}
294373

295374
"Button" {

0 commit comments

Comments
 (0)