diff --git a/src/modules/powerdisplay/PowerDisplay/Core/Models/Monitor.cs b/src/modules/powerdisplay/PowerDisplay/Core/Models/Monitor.cs
index e8892cf60d..b4d989a127 100644
--- a/src/modules/powerdisplay/PowerDisplay/Core/Models/Monitor.cs
+++ b/src/modules/powerdisplay/PowerDisplay/Core/Models/Monitor.cs
@@ -187,7 +187,7 @@ namespace PowerDisplay.Core.Models
public IntPtr Handle { get; set; } = IntPtr.Zero;
///
- /// Device key - unique identifier part of device path (like Twinkle Tray's deviceKey)
+ /// Device key - unique identifier part of device path
///
public string DeviceKey { get; set; } = string.Empty;
@@ -216,6 +216,16 @@ namespace PowerDisplay.Core.Models
///
public MonitorCapabilities Capabilities { get; set; } = MonitorCapabilities.None;
+ ///
+ /// Raw DDC/CI capabilities string (MCCS format)
+ ///
+ public string? CapabilitiesRaw { get; set; }
+
+ ///
+ /// Parsed VCP capabilities information
+ ///
+ public VcpCapabilities? VcpCapabilitiesInfo { get; set; }
+
///
/// Last update time
///
diff --git a/src/modules/powerdisplay/PowerDisplay/Core/Models/VcpCapabilities.cs b/src/modules/powerdisplay/PowerDisplay/Core/Models/VcpCapabilities.cs
new file mode 100644
index 0000000000..dd57995905
--- /dev/null
+++ b/src/modules/powerdisplay/PowerDisplay/Core/Models/VcpCapabilities.cs
@@ -0,0 +1,134 @@
+// Copyright (c) Microsoft Corporation
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PowerDisplay.Core.Models
+{
+ ///
+ /// DDC/CI VCP capabilities information
+ ///
+ public class VcpCapabilities
+ {
+ ///
+ /// Raw capabilities string (MCCS format)
+ ///
+ public string Raw { get; set; } = string.Empty;
+
+ ///
+ /// Monitor model name from capabilities
+ ///
+ public string? Model { get; set; }
+
+ ///
+ /// Monitor type from capabilities (e.g., "LCD")
+ ///
+ public string? Type { get; set; }
+
+ ///
+ /// MCCS protocol version
+ ///
+ public string? Protocol { get; set; }
+
+ ///
+ /// Supported command codes
+ ///
+ public List SupportedCommands { get; set; } = new();
+
+ ///
+ /// Supported VCP codes with their information
+ ///
+ public Dictionary SupportedVcpCodes { get; set; } = new();
+
+ ///
+ /// Check if a specific VCP code is supported
+ ///
+ public bool SupportsVcpCode(byte code) => SupportedVcpCodes.ContainsKey(code);
+
+ ///
+ /// Get VCP code information
+ ///
+ public VcpCodeInfo? GetVcpCodeInfo(byte code)
+ {
+ return SupportedVcpCodes.TryGetValue(code, out var info) ? info : null;
+ }
+
+ ///
+ /// Check if a VCP code supports discrete values
+ ///
+ public bool HasDiscreteValues(byte code)
+ {
+ var info = GetVcpCodeInfo(code);
+ return info?.HasDiscreteValues ?? false;
+ }
+
+ ///
+ /// Get supported values for a VCP code
+ ///
+ public IReadOnlyList? GetSupportedValues(byte code)
+ {
+ return GetVcpCodeInfo(code)?.SupportedValues;
+ }
+
+ ///
+ /// Creates an empty capabilities object
+ ///
+ public static VcpCapabilities Empty => new();
+
+ public override string ToString()
+ {
+ return $"Model: {Model}, VCP Codes: {SupportedVcpCodes.Count}";
+ }
+ }
+
+ ///
+ /// Information about a single VCP code
+ ///
+ public readonly struct VcpCodeInfo
+ {
+ ///
+ /// VCP code (e.g., 0x10 for brightness)
+ ///
+ public byte Code { get; }
+
+ ///
+ /// Human-readable name of the VCP code
+ ///
+ public string Name { get; }
+
+ ///
+ /// Supported discrete values (empty if continuous range)
+ ///
+ public IReadOnlyList SupportedValues { get; }
+
+ ///
+ /// Whether this VCP code has discrete values
+ ///
+ public bool HasDiscreteValues => SupportedValues.Count > 0;
+
+ ///
+ /// Whether this VCP code supports a continuous range
+ ///
+ public bool IsContinuous => SupportedValues.Count == 0;
+
+ public VcpCodeInfo(byte code, string name, IReadOnlyList? supportedValues = null)
+ {
+ Code = code;
+ Name = name;
+ SupportedValues = supportedValues ?? Array.Empty();
+ }
+
+ public override string ToString()
+ {
+ if (HasDiscreteValues)
+ {
+ return $"0x{Code:X2} ({Name}): {string.Join(", ", SupportedValues)}";
+ }
+
+ return $"0x{Code:X2} ({Name}): Continuous";
+ }
+ }
+}
diff --git a/src/modules/powerdisplay/PowerDisplay/Core/MonitorManager.cs b/src/modules/powerdisplay/PowerDisplay/Core/MonitorManager.cs
index ec731055f4..1a3e1226c4 100644
--- a/src/modules/powerdisplay/PowerDisplay/Core/MonitorManager.cs
+++ b/src/modules/powerdisplay/PowerDisplay/Core/MonitorManager.cs
@@ -127,6 +127,36 @@ namespace PowerDisplay.Core
Logger.LogWarning($"Failed to get brightness for monitor {monitor.Id}: {ex.Message}");
}
+ // Get capabilities for DDC/CI monitors (External type)
+ if (monitor.Type == MonitorType.External && controller.SupportedType == MonitorType.External)
+ {
+ try
+ {
+ Logger.LogInfo($"Getting capabilities for monitor {monitor.Id}");
+ var capsString = await controller.GetCapabilitiesStringAsync(monitor, cancellationToken);
+
+ if (!string.IsNullOrEmpty(capsString))
+ {
+ monitor.CapabilitiesRaw = capsString;
+
+ // Parse capabilities
+ monitor.VcpCapabilitiesInfo = Utils.VcpCapabilitiesParser.Parse(capsString);
+
+ Logger.LogInfo($"Successfully parsed capabilities for {monitor.Id}: {monitor.VcpCapabilitiesInfo.SupportedVcpCodes.Count} VCP codes");
+ }
+ else
+ {
+ Logger.LogWarning($"Got empty capabilities string for monitor {monitor.Id}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"Failed to get capabilities for monitor {monitor.Id}: {ex.Message}");
+
+ // Continue without capabilities - not critical
+ }
+ }
+
newMonitors.Add(monitor);
}
}
diff --git a/src/modules/powerdisplay/PowerDisplay/Core/Utils/VcpCapabilitiesParser.cs b/src/modules/powerdisplay/PowerDisplay/Core/Utils/VcpCapabilitiesParser.cs
new file mode 100644
index 0000000000..0789454087
--- /dev/null
+++ b/src/modules/powerdisplay/PowerDisplay/Core/Utils/VcpCapabilitiesParser.cs
@@ -0,0 +1,315 @@
+// Copyright (c) Microsoft Corporation
+// 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;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+using ManagedCommon;
+using PowerDisplay.Core.Models;
+
+namespace PowerDisplay.Core.Utils
+{
+ ///
+ /// Parser for DDC/CI MCCS capabilities strings
+ ///
+ public static class VcpCapabilitiesParser
+ {
+ private static readonly char[] SpaceSeparator = new[] { ' ' };
+ private static readonly char[] ValueSeparators = new[] { ' ', '(', ')' };
+
+ ///
+ /// Parse a capabilities string into structured VcpCapabilities
+ ///
+ /// Raw MCCS capabilities string
+ /// Parsed capabilities object, or Empty if parsing fails
+ public static VcpCapabilities Parse(string? capabilitiesString)
+ {
+ if (string.IsNullOrWhiteSpace(capabilitiesString))
+ {
+ return VcpCapabilities.Empty;
+ }
+
+ try
+ {
+ var capabilities = new VcpCapabilities
+ {
+ Raw = capabilitiesString,
+ };
+
+ // Extract model, type, protocol
+ capabilities.Model = ExtractValue(capabilitiesString, "model");
+ capabilities.Type = ExtractValue(capabilitiesString, "type");
+ capabilities.Protocol = ExtractValue(capabilitiesString, "prot");
+
+ // Extract supported commands
+ capabilities.SupportedCommands = ParseCommandList(capabilitiesString);
+
+ // Extract and parse VCP codes
+ capabilities.SupportedVcpCodes = ParseVcpCodes(capabilitiesString);
+
+ Logger.LogInfo($"Parsed capabilities: Model={capabilities.Model}, VCP Codes={capabilities.SupportedVcpCodes.Count}");
+
+ return capabilities;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"Failed to parse capabilities string: {ex.Message}");
+ return VcpCapabilities.Empty;
+ }
+ }
+
+ ///
+ /// Extract a simple value from capabilities string
+ /// Example: "model(PD3220U)" -> "PD3220U"
+ ///
+ private static string? ExtractValue(string capabilities, string key)
+ {
+ try
+ {
+ var pattern = $@"{key}\(([^)]+)\)";
+ var match = Regex.Match(capabilities, pattern, RegexOptions.IgnoreCase);
+ return match.Success ? match.Groups[1].Value : null;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Parse command list from capabilities string
+ /// Example: "cmds(01 02 03 07 0C)" -> [0x01, 0x02, 0x03, 0x07, 0x0C]
+ ///
+ private static List ParseCommandList(string capabilities)
+ {
+ var commands = new List();
+
+ try
+ {
+ var match = Regex.Match(capabilities, @"cmds\(([^)]+)\)", RegexOptions.IgnoreCase);
+ if (match.Success)
+ {
+ var cmdString = match.Groups[1].Value;
+ var cmdTokens = cmdString.Split(SpaceSeparator, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var token in cmdTokens)
+ {
+ if (byte.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var cmd))
+ {
+ commands.Add(cmd);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"Failed to parse command list: {ex.Message}");
+ }
+
+ return commands;
+ }
+
+ ///
+ /// Parse VCP codes section from capabilities string
+ ///
+ private static Dictionary ParseVcpCodes(string capabilities)
+ {
+ var vcpCodes = new Dictionary();
+
+ try
+ {
+ // Find the "vcp(" section
+ var vcpStart = capabilities.IndexOf("vcp(", StringComparison.OrdinalIgnoreCase);
+ if (vcpStart < 0)
+ {
+ Logger.LogWarning("No 'vcp(' section found in capabilities string");
+ return vcpCodes;
+ }
+
+ // Extract the complete VCP section by matching parentheses
+ var vcpSection = ExtractVcpSection(capabilities, vcpStart + 4); // Skip "vcp("
+ if (string.IsNullOrEmpty(vcpSection))
+ {
+ return vcpCodes;
+ }
+
+ Logger.LogDebug($"Extracted VCP section: {vcpSection.Substring(0, Math.Min(100, vcpSection.Length))}...");
+
+ // Parse VCP codes from the section
+ ParseVcpCodesFromSection(vcpSection, vcpCodes);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"Failed to parse VCP codes: {ex.Message}");
+ }
+
+ return vcpCodes;
+ }
+
+ ///
+ /// Extract VCP section by matching parentheses
+ ///
+ private static string ExtractVcpSection(string capabilities, int startIndex)
+ {
+ var depth = 1;
+ var result = string.Empty;
+
+ for (int i = startIndex; i < capabilities.Length && depth > 0; i++)
+ {
+ var ch = capabilities[i];
+
+ if (ch == '(')
+ {
+ depth++;
+ }
+ else if (ch == ')')
+ {
+ depth--;
+ if (depth == 0)
+ {
+ break;
+ }
+ }
+
+ result += ch;
+ }
+
+ return result;
+ }
+
+ ///
+ /// Parse VCP codes from the extracted VCP section
+ ///
+ private static void ParseVcpCodesFromSection(string vcpSection, Dictionary vcpCodes)
+ {
+ var i = 0;
+
+ while (i < vcpSection.Length)
+ {
+ // Skip whitespace
+ while (i < vcpSection.Length && char.IsWhiteSpace(vcpSection[i]))
+ {
+ i++;
+ }
+
+ if (i >= vcpSection.Length)
+ {
+ break;
+ }
+
+ // Read VCP code (2 hex digits)
+ if (i + 1 < vcpSection.Length &&
+ IsHexDigit(vcpSection[i]) &&
+ IsHexDigit(vcpSection[i + 1]))
+ {
+ var codeStr = vcpSection.Substring(i, 2);
+ if (byte.TryParse(codeStr, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var code))
+ {
+ i += 2;
+
+ // Check if there are supported values (followed by '(')
+ while (i < vcpSection.Length && char.IsWhiteSpace(vcpSection[i]))
+ {
+ i++;
+ }
+
+ var supportedValues = new List();
+
+ if (i < vcpSection.Length && vcpSection[i] == '(')
+ {
+ // Extract supported values
+ i++; // Skip '('
+ var valuesSection = ExtractVcpValuesSection(vcpSection, i);
+ i += valuesSection.Length + 1; // +1 for closing ')'
+
+ // Parse values
+ ParseVcpValues(valuesSection, supportedValues);
+ }
+
+ // Get VCP code name
+ var name = VcpCodeNames.GetName(code);
+
+ // Store VCP code info
+ vcpCodes[code] = new VcpCodeInfo(code, name, supportedValues);
+
+ Logger.LogDebug($"Parsed VCP code: 0x{code:X2} ({name}), Values: {supportedValues.Count}");
+ }
+ else
+ {
+ i++;
+ }
+ }
+ else
+ {
+ i++;
+ }
+ }
+ }
+
+ ///
+ /// Extract VCP values section by matching parentheses
+ ///
+ private static string ExtractVcpValuesSection(string section, int startIndex)
+ {
+ var depth = 1;
+ var result = string.Empty;
+
+ for (int i = startIndex; i < section.Length && depth > 0; i++)
+ {
+ var ch = section[i];
+
+ if (ch == '(')
+ {
+ depth++;
+ result += ch;
+ }
+ else if (ch == ')')
+ {
+ depth--;
+ if (depth == 0)
+ {
+ break;
+ }
+
+ result += ch;
+ }
+ else
+ {
+ result += ch;
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Parse VCP values from the values section
+ ///
+ private static void ParseVcpValues(string valuesSection, List supportedValues)
+ {
+ var tokens = valuesSection.Split(ValueSeparators, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var token in tokens)
+ {
+ // Try to parse as hex
+ if (int.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var value))
+ {
+ supportedValues.Add(value);
+ }
+ }
+ }
+
+ ///
+ /// Check if a character is a hex digit
+ ///
+ private static bool IsHexDigit(char c)
+ {
+ return (c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'F') ||
+ (c >= 'a' && c <= 'f');
+ }
+ }
+}
diff --git a/src/modules/powerdisplay/PowerDisplay/Core/Utils/VcpCodeNames.cs b/src/modules/powerdisplay/PowerDisplay/Core/Utils/VcpCodeNames.cs
new file mode 100644
index 0000000000..3175603685
--- /dev/null
+++ b/src/modules/powerdisplay/PowerDisplay/Core/Utils/VcpCodeNames.cs
@@ -0,0 +1,239 @@
+// Copyright (c) Microsoft Corporation
+// 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;
+
+namespace PowerDisplay.Core.Utils
+{
+ ///
+ /// VCP code to friendly name mapping based on MCCS v2.2a specification
+ ///
+ public static class VcpCodeNames
+ {
+ ///
+ /// VCP code to name mapping
+ ///
+ private static readonly Dictionary CodeNames = new()
+ {
+ // Control codes
+ { 0x01, "Degauss" },
+ { 0x02, "New Control Value" },
+ { 0x03, "Soft Controls" },
+
+ // Geometry codes
+ { 0x04, "Restore Factory Defaults" },
+ { 0x05, "Restore Brightness and Contrast" },
+ { 0x06, "Restore Factory Geometry" },
+ { 0x08, "Restore Color Defaults" },
+ { 0x0A, "Restore Factory TV Defaults" },
+
+ // Color temperature codes
+ { 0x0B, "Color Temperature Increment" },
+ { 0x0C, "Color Temperature Request" },
+ { 0x0E, "Clock" },
+ { 0x0F, "Color Saturation" },
+
+ // Image adjustment codes
+ { 0x10, "Brightness" },
+ { 0x11, "Flesh Tone Enhancement" },
+ { 0x12, "Contrast" },
+ { 0x13, "Backlight Control" },
+ { 0x14, "Select Color Preset" },
+ { 0x16, "Video Gain: Red" },
+ { 0x17, "User Color Vision Compensation" },
+ { 0x18, "Video Gain: Green" },
+ { 0x1A, "Video Gain: Blue" },
+ { 0x1C, "Focus" },
+ { 0x1E, "Auto Setup" },
+ { 0x1F, "Auto Color Setup" },
+
+ // Geometry codes
+ { 0x20, "Horizontal Position" },
+ { 0x22, "Horizontal Size" },
+ { 0x24, "Horizontal Pincushion" },
+ { 0x26, "Horizontal Pincushion Balance" },
+ { 0x28, "Horizontal Convergence R/B" },
+ { 0x29, "Horizontal Convergence M/G" },
+ { 0x2A, "Horizontal Linearity" },
+ { 0x2C, "Horizontal Linearity Balance" },
+ { 0x30, "Vertical Position" },
+ { 0x32, "Vertical Size" },
+ { 0x34, "Vertical Pincushion" },
+ { 0x36, "Vertical Pincushion Balance" },
+ { 0x38, "Vertical Convergence R/B" },
+ { 0x39, "Vertical Convergence M/G" },
+ { 0x3A, "Vertical Linearity" },
+ { 0x3C, "Vertical Linearity Balance" },
+ { 0x3E, "Clock Phase" },
+
+ // Miscellaneous codes
+ { 0x40, "Horizontal Parallelogram" },
+ { 0x41, "Vertical Parallelogram" },
+ { 0x42, "Horizontal Keystone" },
+ { 0x43, "Vertical Keystone" },
+ { 0x44, "Rotation" },
+ { 0x46, "Top Corner Flare" },
+ { 0x48, "Top Corner Hook" },
+ { 0x4A, "Bottom Corner Flare" },
+ { 0x4C, "Bottom Corner Hook" },
+
+ // Advanced codes
+ { 0x52, "Active Control" },
+ { 0x54, "Performance Preservation" },
+ { 0x56, "Horizontal Moire" },
+ { 0x58, "Vertical Moire" },
+ { 0x59, "6 Axis Saturation: Red" },
+ { 0x5A, "6 Axis Saturation: Yellow" },
+ { 0x5B, "6 Axis Saturation: Green" },
+ { 0x5C, "6 Axis Saturation: Cyan" },
+ { 0x5D, "6 Axis Saturation: Blue" },
+ { 0x5E, "6 Axis Saturation: Magenta" },
+
+ // Input source codes
+ { 0x60, "Input Source" },
+ { 0x62, "Audio Speaker Volume" },
+ { 0x63, "Speaker Select" },
+ { 0x64, "Audio: Microphone Volume" },
+ { 0x66, "Ambient Light Sensor" },
+ { 0x6B, "Backlight Level: White" },
+ { 0x6C, "Video Black Level: Red" },
+ { 0x6D, "Backlight Level: Red" },
+ { 0x6E, "Video Black Level: Green" },
+ { 0x6F, "Backlight Level: Green" },
+ { 0x70, "Video Black Level: Blue" },
+ { 0x71, "Backlight Level: Blue" },
+ { 0x72, "Gamma" },
+ { 0x73, "LUT Size" },
+ { 0x74, "Single Point LUT Operation" },
+ { 0x75, "Block LUT Operation" },
+
+ // Color calibration codes
+ { 0x86, "Display Scaling" },
+ { 0x87, "Sharpness" },
+ { 0x88, "Velocity Scan Modulation" },
+ { 0x8A, "Color Saturation" },
+ { 0x8C, "TV Sharpness" },
+ { 0x8D, "Audio Mute/Screen Blank" },
+ { 0x8E, "TV Contrast" },
+ { 0x8F, "Audio Treble" },
+ { 0x90, "Hue" },
+ { 0x91, "Audio Bass" },
+ { 0x92, "TV Black Level/Luminance" },
+ { 0x93, "Audio Balance L/R" },
+ { 0x94, "Audio Processor Mode" },
+ { 0x95, "Window Position(TL_X)" },
+ { 0x96, "Window Position(TL_Y)" },
+ { 0x97, "Window Position(BR_X)" },
+ { 0x98, "Window Position(BR_Y)" },
+ { 0x99, "Window Background" },
+ { 0x9A, "6 Axis Hue Control: Red" },
+ { 0x9B, "6 Axis Hue Control: Yellow" },
+ { 0x9C, "6 Axis Hue Control: Green" },
+ { 0x9D, "6 Axis Hue Control: Cyan" },
+ { 0x9E, "6 Axis Hue Control: Blue" },
+ { 0x9F, "6 Axis Hue Control: Magenta" },
+
+ // Window control codes
+ { 0xA0, "Auto Setup On/Off" },
+ { 0xA2, "Auto Color Setup On/Off" },
+ { 0xA4, "Window Mask Control" },
+ { 0xA5, "Window Select" },
+ { 0xA6, "Window Size" },
+ { 0xA7, "Window Transparency" },
+ { 0xAA, "Screen Orientation" },
+ { 0xAC, "Horizontal Frequency" },
+ { 0xAE, "Vertical Frequency" },
+
+ // Misc advanced codes
+ { 0xB0, "Settings" },
+ { 0xB2, "Flat Panel Sub-Pixel Layout" },
+ { 0xB4, "Source Timing Mode" },
+ { 0xB6, "Display Technology Type" },
+ { 0xB7, "Monitor Status" },
+ { 0xB8, "Packet Count" },
+ { 0xB9, "Monitor X Origin" },
+ { 0xBA, "Monitor Y Origin" },
+ { 0xBB, "Header Error Count" },
+ { 0xBC, "Body CRC Error Count" },
+ { 0xBD, "Client ID" },
+ { 0xBE, "Link Control" },
+
+ // Display controller codes
+ { 0xC0, "Display Usage Time" },
+ { 0xC2, "Display Firmware Level" },
+ { 0xC4, "Display Descriptor Length" },
+ { 0xC5, "Transmit Display Descriptor" },
+ { 0xC6, "Enable Display of 'Display Descriptor'" },
+ { 0xC8, "Display Controller Type" },
+ { 0xC9, "Display Firmware Level" },
+ { 0xCA, "OSD" },
+ { 0xCC, "OSD Language" },
+ { 0xD0, "Output Select" },
+ { 0xD2, "Asset Tag" },
+ { 0xD4, "Stereo Video Mode" },
+ { 0xD6, "Power Mode" },
+ { 0xD7, "Auxiliary Power Output" },
+ { 0xD8, "Scan Mode" },
+ { 0xD9, "Image Mode" },
+ { 0xDA, "On Screen Display" },
+ { 0xDC, "Display Application" },
+ { 0xDE, "Scratch Pad" },
+
+ // Information codes
+ { 0xDF, "VCP Version" },
+ { 0xE0, "Manufacturer Specific" },
+ { 0xE1, "Manufacturer Specific" },
+ { 0xE2, "Manufacturer Specific" },
+ { 0xE3, "Manufacturer Specific" },
+ { 0xE4, "Manufacturer Specific" },
+ { 0xE5, "Manufacturer Specific" },
+ { 0xE6, "Manufacturer Specific" },
+ { 0xE7, "Manufacturer Specific" },
+ { 0xE8, "Manufacturer Specific" },
+ { 0xE9, "Manufacturer Specific" },
+ { 0xEA, "Manufacturer Specific" },
+ { 0xEB, "Manufacturer Specific" },
+ { 0xEC, "Manufacturer Specific" },
+ { 0xED, "Manufacturer Specific" },
+ { 0xEE, "Manufacturer Specific" },
+ { 0xEF, "Manufacturer Specific" },
+ { 0xF0, "Manufacturer Specific" },
+ { 0xF1, "Manufacturer Specific" },
+ { 0xF2, "Manufacturer Specific" },
+ { 0xF3, "Manufacturer Specific" },
+ { 0xF4, "Manufacturer Specific" },
+ { 0xF5, "Manufacturer Specific" },
+ { 0xF6, "Manufacturer Specific" },
+ { 0xF7, "Manufacturer Specific" },
+ { 0xF8, "Manufacturer Specific" },
+ { 0xF9, "Manufacturer Specific" },
+ { 0xFA, "Manufacturer Specific" },
+ { 0xFB, "Manufacturer Specific" },
+ { 0xFC, "Manufacturer Specific" },
+ { 0xFD, "Manufacturer Specific" },
+ { 0xFE, "Manufacturer Specific" },
+ { 0xFF, "Manufacturer Specific" },
+ };
+
+ ///
+ /// Get the friendly name for a VCP code
+ ///
+ /// VCP code (e.g., 0x10)
+ /// Friendly name, or hex representation if unknown
+ public static string GetName(byte code)
+ {
+ return CodeNames.TryGetValue(code, out var name) ? name : $"Unknown (0x{code:X2})";
+ }
+
+ ///
+ /// Check if a VCP code has a known name
+ ///
+ public static bool HasName(byte code) => CodeNames.ContainsKey(code);
+
+ ///
+ /// Get all known VCP codes
+ ///
+ public static IEnumerable GetAllKnownCodes() => CodeNames.Keys;
+ }
+}
diff --git a/src/modules/powerdisplay/PowerDisplay/Core/Utils/VcpValueNames.cs b/src/modules/powerdisplay/PowerDisplay/Core/Utils/VcpValueNames.cs
new file mode 100644
index 0000000000..772497177e
--- /dev/null
+++ b/src/modules/powerdisplay/PowerDisplay/Core/Utils/VcpValueNames.cs
@@ -0,0 +1,182 @@
+// Copyright (c) Microsoft Corporation
+// 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;
+
+namespace PowerDisplay.Core.Utils
+{
+ ///
+ /// Provides human-readable names for VCP code values based on MCCS standard
+ ///
+ public static class VcpValueNames
+ {
+ // Dictionary>
+ private static readonly Dictionary> ValueNames = new()
+ {
+ // 0x14: Select Color Preset
+ [0x14] = new Dictionary
+ {
+ [0x01] = "sRGB",
+ [0x02] = "Display Native",
+ [0x03] = "4000K",
+ [0x04] = "5000K",
+ [0x05] = "6500K",
+ [0x06] = "7500K",
+ [0x08] = "9300K",
+ [0x09] = "10000K",
+ [0x0A] = "11500K",
+ [0x0B] = "User 1",
+ [0x0C] = "User 2",
+ [0x0D] = "User 3",
+ },
+
+ // 0x60: Input Source
+ [0x60] = new Dictionary
+ {
+ [0x01] = "VGA-1",
+ [0x02] = "VGA-2",
+ [0x03] = "DVI-1",
+ [0x04] = "DVI-2",
+ [0x05] = "Composite Video 1",
+ [0x06] = "Composite Video 2",
+ [0x07] = "S-Video-1",
+ [0x08] = "S-Video-2",
+ [0x09] = "Tuner-1",
+ [0x0A] = "Tuner-2",
+ [0x0B] = "Tuner-3",
+ [0x0C] = "Component Video 1",
+ [0x0D] = "Component Video 2",
+ [0x0E] = "Component Video 3",
+ [0x0F] = "DisplayPort-1",
+ [0x10] = "DisplayPort-2",
+ [0x11] = "HDMI-1",
+ [0x12] = "HDMI-2",
+ [0x1B] = "USB-C",
+ },
+
+ // 0xD6: Power Mode
+ [0xD6] = new Dictionary
+ {
+ [0x01] = "On",
+ [0x02] = "Standby",
+ [0x03] = "Suspend",
+ [0x04] = "Off (DPM)",
+ [0x05] = "Off (Hard)",
+ },
+
+ // 0x8D: Audio Mute
+ [0x8D] = new Dictionary
+ {
+ [0x01] = "Muted",
+ [0x02] = "Unmuted",
+ },
+
+ // 0xDC: Display Application
+ [0xDC] = new Dictionary
+ {
+ [0x00] = "Standard/Default",
+ [0x01] = "Productivity",
+ [0x02] = "Mixed",
+ [0x03] = "Movie",
+ [0x04] = "User Defined",
+ [0x05] = "Games",
+ [0x06] = "Sports",
+ [0x07] = "Professional (calibration)",
+ [0x08] = "Standard/Default with intermediate power consumption",
+ [0x09] = "Standard/Default with low power consumption",
+ [0x0A] = "Demonstration",
+ [0xF0] = "Dynamic Contrast",
+ },
+
+ // 0xCC: OSD Language
+ [0xCC] = new Dictionary
+ {
+ [0x01] = "Chinese (traditional, Hantai)",
+ [0x02] = "English",
+ [0x03] = "French",
+ [0x04] = "German",
+ [0x05] = "Italian",
+ [0x06] = "Japanese",
+ [0x07] = "Korean",
+ [0x08] = "Portuguese (Portugal)",
+ [0x09] = "Russian",
+ [0x0A] = "Spanish",
+ [0x0B] = "Swedish",
+ [0x0C] = "Turkish",
+ [0x0D] = "Chinese (simplified, Kantai)",
+ [0x0E] = "Portuguese (Brazil)",
+ [0x0F] = "Arabic",
+ [0x10] = "Bulgarian",
+ [0x11] = "Croatian",
+ [0x12] = "Czech",
+ [0x13] = "Danish",
+ [0x14] = "Dutch",
+ [0x15] = "Estonian",
+ [0x16] = "Finnish",
+ [0x17] = "Greek",
+ [0x18] = "Hebrew",
+ [0x19] = "Hindi",
+ [0x1A] = "Hungarian",
+ [0x1B] = "Latvian",
+ [0x1C] = "Lithuanian",
+ [0x1D] = "Norwegian",
+ [0x1E] = "Polish",
+ [0x1F] = "Romanian",
+ [0x20] = "Serbian",
+ [0x21] = "Slovak",
+ [0x22] = "Slovenian",
+ [0x23] = "Thai",
+ [0x24] = "Ukrainian",
+ [0x25] = "Vietnamese",
+ },
+
+ // 0x62: Audio Speaker Volume
+ [0x62] = new Dictionary
+ {
+ [0x00] = "Mute",
+
+ // Other values are continuous
+ },
+
+ // 0xDB: Image Mode (Dell monitors)
+ [0xDB] = new Dictionary
+ {
+ [0x00] = "Standard",
+ [0x01] = "Multimedia",
+ [0x02] = "Movie",
+ [0x03] = "Game",
+ [0x04] = "Sports",
+ [0x05] = "Color Temperature",
+ [0x06] = "Custom Color",
+ [0x07] = "ComfortView",
+ },
+ };
+
+ ///
+ /// Get human-readable name for a VCP value
+ ///
+ /// VCP code (e.g., 0x14)
+ /// Value to translate
+ /// Formatted string like "sRGB (0x01)" or "0x01" if unknown
+ public static string GetName(byte vcpCode, int value)
+ {
+ if (ValueNames.TryGetValue(vcpCode, out var codeValues))
+ {
+ if (codeValues.TryGetValue(value, out var name))
+ {
+ return $"{name} (0x{value:X2})";
+ }
+ }
+
+ return $"0x{value:X2}";
+ }
+
+ ///
+ /// Check if a VCP code has value name mappings
+ ///
+ /// VCP code to check
+ /// True if value names are available
+ public static bool HasValueNames(byte vcpCode) => ValueNames.ContainsKey(vcpCode);
+ }
+}
diff --git a/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorStateManager.cs b/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorStateManager.cs
index 2e6fed6326..e9cb72969f 100644
--- a/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorStateManager.cs
+++ b/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorStateManager.cs
@@ -43,6 +43,8 @@ namespace PowerDisplay.Helpers
public int Contrast { get; set; }
public int Volume { get; set; }
+
+ public string? CapabilitiesRaw { get; set; }
}
public MonitorStateManager()
@@ -147,6 +149,47 @@ namespace PowerDisplay.Helpers
}
}
+ ///
+ /// Update monitor capabilities and schedule save.
+ /// Capabilities are saved separately to avoid frequent writes.
+ ///
+ public void UpdateMonitorCapabilities(string hardwareId, string? capabilitiesRaw)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(hardwareId))
+ {
+ Logger.LogWarning($"Cannot update capabilities: HardwareId is empty");
+ return;
+ }
+
+ lock (_lock)
+ {
+ // Get or create state entry
+ if (!_states.TryGetValue(hardwareId, out var state))
+ {
+ state = new MonitorState();
+ _states[hardwareId] = state;
+ }
+
+ // Update capabilities
+ state.CapabilitiesRaw = capabilitiesRaw;
+
+ // Mark dirty and schedule save
+ _isDirty = true;
+ }
+
+ // Schedule save
+ _saveTimer.Change(SaveDebounceMs, Timeout.Infinite);
+
+ Logger.LogInfo($"[State] Updated capabilities for monitor HardwareId='{hardwareId}' (length: {capabilitiesRaw?.Length ?? 0})");
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"Failed to update monitor capabilities: {ex.Message}");
+ }
+ }
+
///
/// Get saved parameters for a monitor using HardwareId
///
@@ -168,6 +211,27 @@ namespace PowerDisplay.Helpers
return null;
}
+ ///
+ /// Get saved capabilities for a monitor using HardwareId
+ ///
+ public string? GetMonitorCapabilities(string hardwareId)
+ {
+ if (string.IsNullOrEmpty(hardwareId))
+ {
+ return null;
+ }
+
+ lock (_lock)
+ {
+ if (_states.TryGetValue(hardwareId, out var state))
+ {
+ return state.CapabilitiesRaw;
+ }
+ }
+
+ return null;
+ }
+
///
/// Check if state exists for a monitor (by HardwareId)
///
@@ -215,6 +279,7 @@ namespace PowerDisplay.Helpers
ColorTemperature = entry.ColorTemperature,
Contrast = entry.Contrast,
Volume = entry.Volume,
+ CapabilitiesRaw = entry.CapabilitiesRaw,
};
}
}
@@ -263,6 +328,7 @@ namespace PowerDisplay.Helpers
ColorTemperature = state.ColorTemperature,
Contrast = state.Contrast,
Volume = state.Volume,
+ CapabilitiesRaw = state.CapabilitiesRaw,
LastUpdated = now,
};
}
diff --git a/src/modules/powerdisplay/PowerDisplay/Native/DDC/DdcCiController.cs b/src/modules/powerdisplay/PowerDisplay/Native/DDC/DdcCiController.cs
index 47e8d55ace..162adc07bb 100644
--- a/src/modules/powerdisplay/PowerDisplay/Native/DDC/DdcCiController.cs
+++ b/src/modules/powerdisplay/PowerDisplay/Native/DDC/DdcCiController.cs
@@ -239,7 +239,7 @@ namespace PowerDisplay.Native.DDC
}
///
- /// Get monitor capabilities string
+ /// Get monitor capabilities string with retry logic
///
public async Task GetCapabilitiesStringAsync(Monitor monitor, CancellationToken cancellationToken = default)
{
@@ -253,25 +253,62 @@ namespace PowerDisplay.Native.DDC
try
{
- if (GetCapabilitiesStringLength(monitor.Handle, out uint length) && length > 0)
+ // Step 1: Get capabilities string length (retry up to 3 times)
+ uint length = 0;
+ const int lengthMaxRetries = 3;
+ for (int i = 0; i < lengthMaxRetries; i++)
+ {
+ if (GetCapabilitiesStringLength(monitor.Handle, out length) && length > 0)
+ {
+ Logger.LogDebug($"Got capabilities length: {length} (attempt {i + 1})");
+ break;
+ }
+
+ if (i < lengthMaxRetries - 1)
+ {
+ Thread.Sleep(100); // 100ms delay between retries
+ }
+ }
+
+ if (length == 0)
+ {
+ Logger.LogWarning("Failed to get capabilities string length after retries");
+ return string.Empty;
+ }
+
+ // Step 2: Get actual capabilities string (retry up to 5 times)
+ const int capsMaxRetries = 5;
+ for (int i = 0; i < capsMaxRetries; i++)
{
var buffer = System.Runtime.InteropServices.Marshal.AllocHGlobal((int)length);
try
{
if (CapabilitiesRequestAndCapabilitiesReply(monitor.Handle, buffer, length))
{
- return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(buffer) ?? string.Empty;
+ var capsString = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(buffer) ?? string.Empty;
+ if (!string.IsNullOrEmpty(capsString))
+ {
+ Logger.LogInfo($"Got capabilities string (length: {capsString.Length}, attempt: {i + 1})");
+ return capsString;
+ }
}
}
finally
{
System.Runtime.InteropServices.Marshal.FreeHGlobal(buffer);
}
+
+ if (i < capsMaxRetries - 1)
+ {
+ Thread.Sleep(100); // 100ms delay between retries
+ }
}
+
+ Logger.LogWarning("Failed to get capabilities string after retries");
}
catch (Exception ex)
{
- Logger.LogWarning($"Failed to get capabilities string: {ex.Message}");
+ Logger.LogError($"Exception getting capabilities string: {ex.Message}");
}
return string.Empty;
@@ -323,7 +360,7 @@ namespace PowerDisplay.Native.DDC
try
{
- // Get all display devices with stable device IDs (Twinkle Tray style)
+ // Get all display devices with stable device IDs
var displayDevices = DdcCiNative.GetAllDisplayDevices();
// Also get hardware info for friendly names
@@ -355,8 +392,7 @@ namespace PowerDisplay.Native.DDC
continue;
}
- // Sometimes Windows returns NULL handles. Implement Twinkle Tray's retry logic.
- // See: twinkle-tray/src/Monitors.js line 617
+ // Sometimes Windows returns NULL handles, so we implement retry logic
PHYSICAL_MONITOR[]? physicalMonitors = null;
const int maxRetries = 3;
const int retryDelayMs = 200;
@@ -380,7 +416,7 @@ namespace PowerDisplay.Native.DDC
continue;
}
- // Check if any handle is NULL (Twinkle Tray checks handleIsValid)
+ // Check if any handle is NULL
bool hasNullHandle = false;
for (int i = 0; i < physicalMonitors.Length; i++)
{
@@ -414,7 +450,7 @@ namespace PowerDisplay.Native.DDC
continue;
}
- // Match physical monitors with DisplayDeviceInfo (Twinkle Tray logic)
+ // Match physical monitors with DisplayDeviceInfo
// For each physical monitor on this adapter, find the corresponding DisplayDeviceInfo
for (int i = 0; i < physicalMonitors.Length; i++)
{
diff --git a/src/modules/powerdisplay/PowerDisplay/Native/DDC/DdcCiNative.cs b/src/modules/powerdisplay/PowerDisplay/Native/DDC/DdcCiNative.cs
index 442b7f3485..2bc8be2564 100644
--- a/src/modules/powerdisplay/PowerDisplay/Native/DDC/DdcCiNative.cs
+++ b/src/modules/powerdisplay/PowerDisplay/Native/DDC/DdcCiNative.cs
@@ -394,8 +394,7 @@ namespace PowerDisplay.Native.DDC
}
///
- /// Get all display device information (using EnumDisplayDevices API)
- /// Implementation consistent with Twinkle Tray
+ /// Get all display device information using EnumDisplayDevices API
///
/// List of display device information
public static unsafe List GetAllDisplayDevices()
diff --git a/src/modules/powerdisplay/PowerDisplay/Native/DDC/PhysicalMonitorHandleManager.cs b/src/modules/powerdisplay/PowerDisplay/Native/DDC/PhysicalMonitorHandleManager.cs
index 6cdafca285..ae2dfc3c7d 100644
--- a/src/modules/powerdisplay/PowerDisplay/Native/DDC/PhysicalMonitorHandleManager.cs
+++ b/src/modules/powerdisplay/PowerDisplay/Native/DDC/PhysicalMonitorHandleManager.cs
@@ -12,11 +12,10 @@ namespace PowerDisplay.Native.DDC
{
///
/// Manages physical monitor handles - reuse, cleanup, and validation
- /// Twinkle Tray style handle management
///
public partial class PhysicalMonitorHandleManager : IDisposable
{
- // Twinkle Tray style mapping: deviceKey -> physical handle
+ // Mapping: deviceKey -> physical handle
private readonly Dictionary _deviceKeyToHandleMap = new();
private bool _disposed;
diff --git a/src/modules/powerdisplay/PowerDisplay/Native/DDC/VcpCodeResolver.cs b/src/modules/powerdisplay/PowerDisplay/Native/DDC/VcpCodeResolver.cs
index 5fd7d43c42..146fdd8890 100644
--- a/src/modules/powerdisplay/PowerDisplay/Native/DDC/VcpCodeResolver.cs
+++ b/src/modules/powerdisplay/PowerDisplay/Native/DDC/VcpCodeResolver.cs
@@ -28,12 +28,13 @@ namespace PowerDisplay.Native.DDC
};
// VCP code priority order (for color temperature control)
+ // Per MCCS specification:
+ // - 0x0C (Color Temperature Request): Set specific color temperature preset
+ // - 0x0B (Color Temperature Increment): Increment color temperature value
private static readonly byte[] ColorTemperatureVcpCodes =
{
- NativeConstants.VcpCodeColorTemperature, // 0x0C - Standard color temperature
- NativeConstants.VcpCodeColorTemperatureIncrement, // 0x0B - Color temperature increment
- NativeConstants.VcpCodeSelectColorPreset, // 0x14 - Color preset selection
- NativeConstants.VcpCodeGamma, // 0x72 - Gamma correction
+ NativeConstants.VcpCodeColorTemperature, // 0x0C - Standard color temperature (primary)
+ NativeConstants.VcpCodeColorTemperatureIncrement, // 0x0B - Color temperature increment (fallback)
};
///
diff --git a/src/modules/powerdisplay/PowerDisplay/Native/NativeConstants.cs b/src/modules/powerdisplay/PowerDisplay/Native/NativeConstants.cs
index c7c8788d74..7116a2daf7 100644
--- a/src/modules/powerdisplay/PowerDisplay/Native/NativeConstants.cs
+++ b/src/modules/powerdisplay/PowerDisplay/Native/NativeConstants.cs
@@ -42,12 +42,20 @@ namespace PowerDisplay.Native
public const byte VcpCodeMute = 0x8D;
///
- /// VCP code: Color temperature request (primary color temperature control)
+ /// VCP code: Color Temperature Request (0x0C)
+ /// Per MCCS v2.2a specification:
+ /// - Used to SET and GET specific color temperature presets
+ /// - Typically supports discrete values (e.g., 5000K, 6500K, 9300K)
+ /// - Primary method for color temperature control
///
public const byte VcpCodeColorTemperature = 0x0C;
///
- /// VCP code: Color temperature increment (incremental color temperature adjustment)
+ /// VCP code: Color Temperature Increment (0x0B)
+ /// Per MCCS v2.2a specification:
+ /// - Used for incremental color temperature adjustment
+ /// - Typically supports continuous range (0-100 or custom range)
+ /// - Fallback method when 0x0C is not supported
///
public const byte VcpCodeColorTemperatureIncrement = 0x0B;
diff --git a/src/modules/powerdisplay/PowerDisplay/Serialization/JsonSourceGenerationContext.cs b/src/modules/powerdisplay/PowerDisplay/Serialization/JsonSourceGenerationContext.cs
index 671ab1eec7..5a744bee8c 100644
--- a/src/modules/powerdisplay/PowerDisplay/Serialization/JsonSourceGenerationContext.cs
+++ b/src/modules/powerdisplay/PowerDisplay/Serialization/JsonSourceGenerationContext.cs
@@ -71,6 +71,9 @@ namespace PowerDisplay.Serialization
[JsonPropertyName("volume")]
public int Volume { get; set; }
+ [JsonPropertyName("capabilitiesRaw")]
+ public string? CapabilitiesRaw { get; set; }
+
[JsonPropertyName("lastUpdated")]
public DateTime LastUpdated { get; set; }
}
diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs
index b3f1203541..48695559bc 100644
--- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs
+++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs
@@ -687,7 +687,18 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
communicationMethod: GetCommunicationMethodString(vm.Type),
monitorType: vm.Type.ToString(),
currentBrightness: vm.Brightness,
- colorTemperature: vm.ColorTemperature);
+ colorTemperature: vm.ColorTemperature)
+ {
+ CapabilitiesRaw = vm.CapabilitiesRaw,
+ VcpCodes = vm.VcpCapabilitiesInfo?.SupportedVcpCodes
+ .OrderBy(kvp => kvp.Key)
+ .Select(kvp => $"0x{kvp.Key:X2}")
+ .ToList() ?? new List(),
+ VcpCodesFormatted = vm.VcpCapabilitiesInfo?.SupportedVcpCodes
+ .OrderBy(kvp => kvp.Key)
+ .Select(kvp => FormatVcpCodeForDisplay(kvp.Key, kvp.Value))
+ .ToList() ?? new List(),
+ };
monitors.Add(monitorInfo);
}
@@ -712,8 +723,36 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
}
///
- /// Get communication method string based on monitor type
+ /// Format VCP code information for display in Settings UI
///
+ private Microsoft.PowerToys.Settings.UI.Library.VcpCodeDisplayInfo FormatVcpCodeForDisplay(byte code, VcpCodeInfo info)
+ {
+ var result = new Microsoft.PowerToys.Settings.UI.Library.VcpCodeDisplayInfo
+ {
+ Title = $"{info.Name} (0x{code:X2})",
+ };
+
+ if (info.IsContinuous)
+ {
+ result.Values = "Continuous range";
+ result.HasValues = true;
+ }
+ else if (info.HasDiscreteValues)
+ {
+ var formattedValues = info.SupportedValues
+ .Select(v => Core.Utils.VcpValueNames.GetName(code, v))
+ .ToList();
+ result.Values = $"Values: {string.Join(", ", formattedValues)}";
+ result.HasValues = true;
+ }
+ else
+ {
+ result.HasValues = false;
+ }
+
+ return result;
+ }
+
private string GetCommunicationMethodString(MonitorType type)
{
return type switch
diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs
index 2c1078e631..2144ec1de9 100644
--- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs
+++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs
@@ -138,6 +138,10 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
public string TypeDisplay => Type == MonitorType.Internal ? "Internal" : "External";
+ public string? CapabilitiesRaw => _monitor.CapabilitiesRaw;
+
+ public VcpCapabilities? VcpCapabilitiesInfo => _monitor.VcpCapabilitiesInfo;
+
///
/// Gets the icon glyph based on monitor type
///
diff --git a/src/settings-ui/Settings.UI.Library/MonitorInfo.cs b/src/settings-ui/Settings.UI.Library/MonitorInfo.cs
index 3e8fc90155..a80e2b34ee 100644
--- a/src/settings-ui/Settings.UI.Library/MonitorInfo.cs
+++ b/src/settings-ui/Settings.UI.Library/MonitorInfo.cs
@@ -2,6 +2,8 @@
// 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 System.Linq;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
@@ -20,6 +22,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
private bool _enableColorTemperature;
private bool _enableContrast;
private bool _enableVolume;
+ private string _capabilitiesRaw = string.Empty;
+ private List _vcpCodes = new List();
+ private List _vcpCodesFormatted = new List();
public MonitorInfo()
{
@@ -197,5 +202,70 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
}
+
+ [JsonPropertyName("capabilitiesRaw")]
+ public string CapabilitiesRaw
+ {
+ get => _capabilitiesRaw;
+ set
+ {
+ if (_capabilitiesRaw != value)
+ {
+ _capabilitiesRaw = value ?? string.Empty;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(HasCapabilities));
+ }
+ }
+ }
+
+ [JsonPropertyName("vcpCodes")]
+ public List VcpCodes
+ {
+ get => _vcpCodes;
+ set
+ {
+ if (_vcpCodes != value)
+ {
+ _vcpCodes = value ?? new List();
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(VcpCodesSummary));
+ }
+ }
+ }
+
+ [JsonPropertyName("vcpCodesFormatted")]
+ public List VcpCodesFormatted
+ {
+ get => _vcpCodesFormatted;
+ set
+ {
+ if (_vcpCodesFormatted != value)
+ {
+ _vcpCodesFormatted = value ?? new List();
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ [JsonIgnore]
+ public string VcpCodesSummary
+ {
+ get
+ {
+ if (_vcpCodes == null || _vcpCodes.Count == 0)
+ {
+ return "No VCP codes detected";
+ }
+
+ var count = _vcpCodes.Count;
+ var preview = string.Join(", ", _vcpCodes.Take(10));
+ return count > 10
+ ? $"{count} VCP codes: {preview}..."
+ : $"{count} VCP codes: {preview}";
+ }
+ }
+
+ [JsonIgnore]
+ public bool HasCapabilities => !string.IsNullOrEmpty(_capabilitiesRaw);
}
}
diff --git a/src/settings-ui/Settings.UI.Library/MonitorInfoData.cs b/src/settings-ui/Settings.UI.Library/MonitorInfoData.cs
index 248d5994cb..649f68cec9 100644
--- a/src/settings-ui/Settings.UI.Library/MonitorInfoData.cs
+++ b/src/settings-ui/Settings.UI.Library/MonitorInfoData.cs
@@ -2,6 +2,7 @@
// 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 System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
@@ -31,5 +32,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("colorTemperature")]
public int ColorTemperature { get; set; }
+
+ [JsonPropertyName("capabilitiesRaw")]
+ public string CapabilitiesRaw { get; set; } = string.Empty;
+
+ [JsonPropertyName("vcpCodes")]
+ public List VcpCodes { get; set; } = new List();
+
+ [JsonPropertyName("vcpCodesFormatted")]
+ public List VcpCodesFormatted { get; set; } = new List();
}
}
diff --git a/src/settings-ui/Settings.UI.Library/VcpCodeDisplayInfo.cs b/src/settings-ui/Settings.UI.Library/VcpCodeDisplayInfo.cs
new file mode 100644
index 0000000000..c62550bc14
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Library/VcpCodeDisplayInfo.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation
+// 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.Text.Json.Serialization;
+
+namespace Microsoft.PowerToys.Settings.UI.Library
+{
+ ///
+ /// Formatted VCP code display information
+ ///
+ public class VcpCodeDisplayInfo
+ {
+ [JsonPropertyName("title")]
+ public string Title { get; set; } = string.Empty;
+
+ [JsonPropertyName("values")]
+ public string Values { get; set; } = string.Empty;
+
+ [JsonPropertyName("hasValues")]
+ public bool HasValues { get; set; }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml
index c6b5b06e30..1270ecabbd 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml
@@ -117,6 +117,48 @@
+
+
+
+
+