Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,31 @@ public void EdidIdFromMonitorId_NewFormat_ReturnsMiddleSegment()
Assert.AreEqual("DELD1A8", MonitorIdentity.EdidIdFromMonitorId(input));
}

[TestMethod]
public void EdidIdFromMonitorId_RawDevicePathWithTrailingGuid_ReturnsMiddleSegment()
{
// The Phase 0 classification log calls EdidIdFromMonitorId with a raw
// QueryDisplayConfig DevicePath (still carrying the trailing "#{guid}" segment).
// The helper must extract the EdidId correctly for that form too.
var input = @"\\?\DISPLAY#DELD1A8#5&abc123&0&UID12345#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}";

Assert.AreEqual("DELD1A8", MonitorIdentity.EdidIdFromMonitorId(input));
}

[TestMethod]
public void EdidIdFromMonitorId_SameModelMonitorsProduceSameId()
{
// Two Dell U2723QE on different ports share an EdidId but have different UIDs.
// Logging the EdidId identifies the model for crash correlation without leaking
// per-unit identifiers.
var portA = @"\\?\DISPLAY#DELD1A8#5&abc&0&UID111#{guid}";
var portB = @"\\?\DISPLAY#DELD1A8#5&xyz&0&UID222#{guid}";

Assert.AreEqual(
MonitorIdentity.EdidIdFromMonitorId(portA),
MonitorIdentity.EdidIdFromMonitorId(portB));
}

[TestMethod]
public void EdidIdFromMonitorId_NullEmptyOrMalformed_ReturnsEmpty()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@ public static string PnpHardwareKeyFromInstanceName(string? instanceName)
}

/// <summary>
/// Extract the EDID PnP identifier from a new-format <c>Monitor.Id</c>. The new format
/// is the DevicePath returned by <c>QueryDisplayConfig</c> with the trailing device-class
/// GUID suffix stripped; the EdidId sits between the leading <c>"\\?\DISPLAY#"</c> and
/// the next <c>#</c>.
/// Extract the EDID PnP identifier (3-letter PNP manufacturer + 4-hex product code,
/// e.g. <c>"DELD1A8"</c>) from either a new-format <c>Monitor.Id</c> or a raw
/// <c>QueryDisplayConfig</c> DevicePath. The EdidId sits between the leading
/// <c>"\\?\DISPLAY#"</c> and the next <c>#</c>, and is identical for every physical
/// monitor of the same model — use it for "which model" crash correlation without
/// leaking per-unit identifiers.
/// </summary>
/// <param name="monitorId">A Monitor.Id in the new DevicePath-based format.</param>
/// <returns>EdidId segment (e.g. <c>"DELD1A8"</c>), or empty string if the input is not a new-format Id.</returns>
/// <param name="monitorId">A Monitor.Id (no trailing <c>#{guid}</c>) or a raw DevicePath (with trailing <c>#{guid}</c>).</param>
/// <returns>EdidId segment (e.g. <c>"DELD1A8"</c>), or empty string if the input is not a recognized form.</returns>
public static string EdidIdFromMonitorId(string? monitorId)
{
if (string.IsNullOrEmpty(monitorId))
Expand Down
10 changes: 9 additions & 1 deletion src/modules/powerdisplay/PowerDisplay/Helpers/MonitorManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,16 @@ private static void LogClassificationSummary(
: info.OutputTechnology.ToString(CultureInfo.InvariantCulture);
var classification = info.IsInternal ? "Internal" : "External";

// Log EdidId (manufacturer+product code from EDID) up front, before any
// DDC/CI capability fetch runs. QueryDisplayConfig reads OS-cached EDID and
// cannot BSOD, so this line is guaranteed on disk before the crash-prone
// Phase 2 fetch starts — recovered logs identify every attached model
// (and same-model duplicates) for crash correlation.
var edidId = MonitorIdentity.EdidIdFromMonitorId(info.DevicePath);
var edidIdField = string.IsNullOrEmpty(edidId) ? "?" : edidId;

Logger.LogInfo(
$" [Path {info.MonitorNumber}] {info.GdiDeviceName} / \"{info.FriendlyName}\": " +
$" [Path {info.MonitorNumber}] EdidId={edidIdField} {info.GdiDeviceName} / \"{info.FriendlyName}\": " +
$"OutputTechnology={techValue} → {classification}");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
TextWrapping="Wrap" />
<TextBlock x:Name="WarningDescription" TextWrapping="Wrap" />
<TextBlock
x:Name="WarningList"
Margin="20,0,0,0"
TextWrapping="Wrap" />
<ItemsControl x:Name="WarningList" Margin="20,0,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{x:Bind}" TextWrapping="Wrap" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock
x:Name="WarningConfirm"
FontWeight="SemiBold"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,62 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.ApplicationModel.Resources;

namespace Microsoft.PowerToys.Settings.UI.Views
{
/// <summary>
/// Confirmation dialog shown when the user enables a feature that can damage the
/// hardware or otherwise leave it in a non-recoverable state. The caller supplies a
/// resource key prefix; the dialog loads "{prefix}_WarningTitle/Header/Description/List/Confirm".
/// resource key prefix; the dialog loads
/// "{prefix}_WarningTitle/Header/Description/WarningList_Item{N}/Confirm".
/// Bullets are prepended in code so translators only see the body text; the
/// item loop probes <c>_WarningList_Item1</c>, <c>_Item2</c>, ... until a missing
/// key is hit, so adding a 4th bullet only requires a new resw entry.
/// </summary>
public sealed partial class DangerousFeatureWarningDialog : ContentDialog
{
// Visual decorations are applied in code so translators only see body text.
private const string WarningHeaderPrefix = "⚠️ ";
private const string BulletPrefix = "• ";

// Hard cap on bullet probes; a real dialog never approaches this.
private const int MaxBulletItems = 10;

// Direct ResourceMap handle so the bullet loop can probe for missing keys with
// TryGetValue (returns null) instead of ResourceLoader.GetString (throws
// "NamedResource Not Found").
private static readonly ResourceMap ResourceMap =
new ResourceManager("PowerToys.Settings.pri").MainResourceMap.GetSubtree("Resources");

public DangerousFeatureWarningDialog(string resourceKeyPrefix)
{
InitializeComponent();

var loader = ResourceLoaderInstance.ResourceLoader;
Title = loader.GetString($"{resourceKeyPrefix}_WarningTitle");
WarningHeader.Text = loader.GetString($"{resourceKeyPrefix}_WarningHeader");
WarningHeader.Text = WarningHeaderPrefix + loader.GetString($"{resourceKeyPrefix}_WarningHeader");
WarningDescription.Text = loader.GetString($"{resourceKeyPrefix}_WarningDescription");
WarningList.Text = loader.GetString($"{resourceKeyPrefix}_WarningList");
WarningConfirm.Text = loader.GetString($"{resourceKeyPrefix}_WarningConfirm");
PrimaryButtonText = loader.GetString("PowerDisplay_Dialog_Enable");
CloseButtonText = loader.GetString("PowerDisplay_Dialog_Cancel");

var items = new List<string>();
for (int i = 1; i <= MaxBulletItems; i++)
{
var candidate = ResourceMap.TryGetValue($"{resourceKeyPrefix}_WarningList_Item{i}");
if (candidate == null || string.IsNullOrEmpty(candidate.ValueAsString))
{
break;
}

items.Add(BulletPrefix + candidate.ValueAsString);
}

WarningList.ItemsSource = items;
}
}
}
68 changes: 48 additions & 20 deletions src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -5579,15 +5579,16 @@ The break timer font matches the text font.</value>
<value>Confirm Color Temperature Change</value>
</data>
<data name="PowerDisplay_ColorTemperature_WarningHeader" xml:space="preserve">
<value>⚠️ Warning: This is a potentially dangerous operation!</value>
<value>Warning: This is a potentially dangerous operation!</value>
</data>
<data name="PowerDisplay_ColorTemperature_WarningDescription" xml:space="preserve">
<value>Changing the color temperature setting may cause unpredictable results including:</value>
</data>
<data name="PowerDisplay_ColorTemperature_WarningList" xml:space="preserve">
<value>• Incorrect display colors
• Display malfunction
• Settings that cannot be reverted</value>
<data name="PowerDisplay_ColorTemperature_WarningList_Item1" xml:space="preserve">
<value>Incorrect display colors or other display malfunction</value>
</data>
<data name="PowerDisplay_ColorTemperature_WarningList_Item2" xml:space="preserve">
<value>Settings that cannot be reverted</value>
</data>
<data name="PowerDisplay_ColorTemperature_WarningConfirm" xml:space="preserve">
<value>Do you want to enable color temperature control for this monitor?</value>
Expand All @@ -5608,15 +5609,16 @@ The break timer font matches the text font.</value>
<value>Confirm power state control</value>
</data>
<data name="PowerDisplay_PowerState_WarningHeader" xml:space="preserve">
<value>⚠️ Warning: This action may be unsafe.</value>
<value>Warning: This action may be unsafe.</value>
</data>
<data name="PowerDisplay_PowerState_WarningDescription" xml:space="preserve">
<value>Enabling power state control may lead to unexpected behavior, including:</value>
</data>
<data name="PowerDisplay_PowerState_WarningList" xml:space="preserve">
<value>• Monitor may enter standby and not wake via software.
• You may need to press the monitor’s power button or reconnect the cable to recover.
• Some monitors may not restore the previous state correctly.</value>
<data name="PowerDisplay_PowerState_WarningList_Item1" xml:space="preserve">
<value>Monitor may enter standby and not wake via software — recovery may require the physical power button or reseating the cable.</value>
</data>
<data name="PowerDisplay_PowerState_WarningList_Item2" xml:space="preserve">
<value>Some monitors may not restore the previous state correctly.</value>
</data>
<data name="PowerDisplay_PowerState_WarningConfirm" xml:space="preserve">
<value>Enable power state control for this monitor?</value>
Expand All @@ -5625,15 +5627,19 @@ The break timer font matches the text font.</value>
<value>Confirm input source control</value>
</data>
<data name="PowerDisplay_InputSource_WarningHeader" xml:space="preserve">
<value>⚠️ Warning: This action may be unsafe.</value>
<value>Warning: This action may be unsafe.</value>
</data>
<data name="PowerDisplay_InputSource_WarningDescription" xml:space="preserve">
<value>Switching input sources may cause unintended results, including:</value>
</data>
<data name="PowerDisplay_InputSource_WarningList" xml:space="preserve">
<value>• Screen may go black if the selected input has no signal.
• Software control may be lost until you switch back using the monitor’s physical buttons.
• Some monitors may not reliably expose all input sources.</value>
<data name="PowerDisplay_InputSource_WarningList_Item1" xml:space="preserve">
<value>Screen may go black if the selected input has no signal.</value>
</data>
<data name="PowerDisplay_InputSource_WarningList_Item2" xml:space="preserve">
<value>Software control may be lost until you switch back using the monitor’s physical buttons.</value>
</data>
<data name="PowerDisplay_InputSource_WarningList_Item3" xml:space="preserve">
<value>Some monitors may not reliably expose all input sources.</value>
</data>
<data name="PowerDisplay_InputSource_WarningConfirm" xml:space="preserve">
<value>Enable input source control for this monitor?</value>
Expand All @@ -5642,19 +5648,41 @@ The break timer font matches the text font.</value>
<value>Confirm maximum compatibility mode</value>
</data>
<data name="PowerDisplay_MaxCompatibility_WarningHeader" xml:space="preserve">
<value>⚠️ Warning: This may cause unstable monitor behavior.</value>
<value>Warning: This may cause unstable monitor behavior.</value>
</data>
<data name="PowerDisplay_MaxCompatibility_WarningDescription" xml:space="preserve">
<value>Maximum compatibility mode bypasses your monitor's capabilities string and sends a probe for every supported VCP feature. Some monitors do not handle this well, which may cause:</value>
</data>
<data name="PowerDisplay_MaxCompatibility_WarningList" xml:space="preserve">
<value>• The screen may go black or briefly lose signal during discovery.
• The monitor may respond unpredictably, including unwanted changes to brightness, contrast, or input source.
• The monitor may stop responding to DDC/CI until it is power-cycled.</value>
<data name="PowerDisplay_MaxCompatibility_WarningList_Item1" xml:space="preserve">
<value>The screen may go black or briefly lose signal during discovery.</value>
</data>
<data name="PowerDisplay_MaxCompatibility_WarningList_Item2" xml:space="preserve">
<value>The monitor may respond unpredictably, including unwanted changes to brightness, contrast, or input source.</value>
</data>
<data name="PowerDisplay_MaxCompatibility_WarningList_Item3" xml:space="preserve">
<value>The monitor may stop responding to DDC/CI until it is power-cycled.</value>
</data>
<data name="PowerDisplay_MaxCompatibility_WarningConfirm" xml:space="preserve">
<value>Only enable this if a monitor is missing from Power Display and you fully understand the side effects listed above. Continue?</value>
</data>
<data name="PowerDisplay_EnableModule_WarningTitle" xml:space="preserve">
<value>Enable Power Display?</value>
</data>
<data name="PowerDisplay_EnableModule_WarningHeader" xml:space="preserve">
<value>Warning: This may cause a system crash on a small number of monitors.</value>
</data>
<data name="PowerDisplay_EnableModule_WarningDescription" xml:space="preserve">
<value>Power Display reads each connected monitor's DDC/CI capabilities at startup. On a small number of monitors with malformed capability strings, this read has been observed to trigger a kernel-side bug that causes Windows to bug-check (BSOD). The underlying issue is in Windows, not in Power Display, but the read is what surfaces it. Possible effects:</value>
</data>
<data name="PowerDisplay_EnableModule_WarningList_Item1" xml:space="preserve">
<value>On affected monitors, the system may crash and restart.</value>
</data>
<data name="PowerDisplay_EnableModule_WarningList_Item2" xml:space="preserve">
<value>If a crash is detected, Power Display will auto-disable itself on next launch; you'll need to re-enable the module and dismiss the warning on the settings page each time.</value>
</data>
<data name="PowerDisplay_EnableModule_WarningConfirm" xml:space="preserve">
<value>Only enable Power Display if you accept the risk above. Continue?</value>
</data>
<data name="PowerDisplay_Dialog_Enable" xml:space="preserve">
<value>Enable</value>
</data>
Expand Down
Loading
Loading