diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs
index aeb8ea521f..d59dee081f 100644
--- a/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs
+++ b/src/modules/powerdisplay/PowerDisplay.Lib/Drivers/WMI/WmiController.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
+using PowerDisplay.Common.Helpers;
using PowerDisplay.Common.Interfaces;
using PowerDisplay.Common.Models;
using WmiLight;
@@ -25,7 +26,6 @@ namespace PowerDisplay.Common.Drivers.WMI
private const string WmiNamespace = @"root\WMI";
private const string BrightnessQueryClass = "WmiMonitorBrightness";
private const string BrightnessMethodClass = "WmiMonitorBrightnessMethods";
- private const string MonitorIdClass = "WmiMonitorID";
// Common WMI error codes for classification
private const int WbemENotFound = unchecked((int)0x80041002);
@@ -244,7 +244,9 @@ namespace PowerDisplay.Common.Drivers.WMI
}
///
- /// Discover supported monitors
+ /// Discover supported monitors.
+ /// WMI brightness control is typically only available on internal laptop displays,
+ /// which don't have meaningful UserFriendlyName in WmiMonitorID, so we use "Built-in Display".
///
public async Task> DiscoverMonitorsAsync(CancellationToken cancellationToken = default)
{
@@ -257,8 +259,8 @@ namespace PowerDisplay.Common.Drivers.WMI
{
using var connection = new WmiConnection(WmiNamespace);
- // First check if WMI brightness support is available
- var brightnessQuery = $"SELECT * FROM {BrightnessQueryClass}";
+ // Query WMI brightness support - only internal displays typically support this
+ var brightnessQuery = $"SELECT InstanceName, CurrentBrightness FROM {BrightnessQueryClass}";
var brightnessResults = connection.CreateQuery(brightnessQuery).ToList();
if (brightnessResults.Count == 0)
@@ -266,37 +268,6 @@ namespace PowerDisplay.Common.Drivers.WMI
return monitors;
}
- // Get monitor information
- var idQuery = $"SELECT * FROM {MonitorIdClass}";
- var idResults = connection.CreateQuery(idQuery).ToList();
-
- var monitorInfos = new Dictionary();
-
- foreach (var obj in idResults)
- {
- try
- {
- var instanceName = obj.GetPropertyValue("InstanceName") ?? string.Empty;
- var userFriendlyName = GetUserFriendlyName(obj);
- Logger.LogDebug($"WMI MonitorID: InstanceName='{instanceName}', UserFriendlyName='{userFriendlyName ?? "(null)"}'");
-
- if (string.IsNullOrEmpty(userFriendlyName))
- {
- userFriendlyName = "Internal Display";
- }
-
- if (!string.IsNullOrEmpty(instanceName))
- {
- monitorInfos[instanceName] = (userFriendlyName, instanceName);
- }
- }
- catch (Exception ex)
- {
- // Skip problematic entries
- Logger.LogDebug($"Failed to parse WMI monitor info: {ex.Message}");
- }
- }
-
// Get MonitorDisplayInfo from QueryDisplayConfig - this provides the correct monitor numbers
var monitorDisplayInfos = Drivers.DDC.DdcCiNative.GetAllMonitorDisplayInfo();
@@ -308,31 +279,29 @@ namespace PowerDisplay.Common.Drivers.WMI
var instanceName = obj.GetPropertyValue("InstanceName") ?? string.Empty;
var currentBrightness = obj.GetPropertyValue("CurrentBrightness");
- var name = "Internal Display";
- if (monitorInfos.TryGetValue(instanceName, out var info))
- {
- name = info.Name;
- }
+ // Extract hardware ID from InstanceName
+ // e.g., "DISPLAY\LEN4038\4&40f4dee&0&UID8388688_0" -> "LEN4038"
+ var hardwareId = ExtractHardwareIdFromInstanceName(instanceName);
- // Extract EdidId from InstanceName
- // e.g., "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0" -> "BOE0900"
- var edidId = ExtractHardwareIdFromInstanceName(instanceName);
-
- // Get MonitorDisplayInfo from QueryDisplayConfig by matching EdidId
+ // Get MonitorDisplayInfo from QueryDisplayConfig by matching hardware ID
// This provides MonitorNumber and GdiDeviceName for display settings APIs
- var displayInfo = GetMonitorDisplayInfoByHardwareId(edidId, monitorDisplayInfos);
+ var displayInfo = GetMonitorDisplayInfoByHardwareId(hardwareId, monitorDisplayInfos);
int monitorNumber = displayInfo?.MonitorNumber ?? 0;
string gdiDeviceName = displayInfo?.GdiDeviceName ?? string.Empty;
- // Generate unique monitor Id: "WMI_{EdidId}_{MonitorNumber}"
- string monitorId = !string.IsNullOrEmpty(edidId)
- ? $"WMI_{edidId}_{monitorNumber}"
+ // Generate unique monitor Id: "WMI_{HardwareId}_{MonitorNumber}"
+ string monitorId = !string.IsNullOrEmpty(hardwareId)
+ ? $"WMI_{hardwareId}_{monitorNumber}"
: $"WMI_Unknown_{monitorNumber}";
+ // Get display name from PnP manufacturer ID (e.g., "Lenovo Built-in Display")
+ var displayName = PnpIdHelper.GetBuiltInDisplayName(hardwareId);
+ Logger.LogDebug($"WMI: Found internal display '{hardwareId}' -> '{displayName}'");
+
var monitor = new Monitor
{
Id = monitorId,
- Name = name,
+ Name = displayName,
CurrentBrightness = currentBrightness,
MinBrightness = 0,
MaxBrightness = 100,
@@ -349,14 +318,12 @@ namespace PowerDisplay.Common.Drivers.WMI
}
catch (Exception ex)
{
- // Skip problematic monitors
Logger.LogWarning($"Failed to create monitor from WMI data: {ex.Message}");
}
}
}
catch (WmiException ex)
{
- // Return empty list instead of throwing exception
Logger.LogError($"WMI DiscoverMonitors failed: {ex.Message} (HResult: 0x{ex.HResult:X})");
}
catch (Exception ex)
@@ -369,40 +336,6 @@ namespace PowerDisplay.Common.Drivers.WMI
cancellationToken);
}
- ///
- /// Get user-friendly name from WMI object.
- /// WmiMonitorID returns UserFriendlyName as a fixed-size uint16 array buffer,
- /// with UserFriendlyNameLength indicating the actual character count.
- ///
- private static string? GetUserFriendlyName(WmiObject monitorObject)
- {
- try
- {
- var userFriendlyName = monitorObject.GetPropertyValue("UserFriendlyName");
- var nameLength = monitorObject.GetPropertyValue("UserFriendlyNameLength");
-
- if (userFriendlyName != null && nameLength > 0 && nameLength <= userFriendlyName.Length)
- {
- // Use UserFriendlyNameLength to extract only valid characters
- var chars = userFriendlyName
- .Take(nameLength)
- .Select(c => (char)c)
- .ToArray();
-
- if (chars.Length > 0)
- {
- return new string(chars).Trim();
- }
- }
- }
- catch (Exception ex)
- {
- Logger.LogDebug($"Failed to parse UserFriendlyName: {ex.Message}");
- }
-
- return null;
- }
-
// Extended features not supported by WMI
public Task SetContrastAsync(Monitor monitor, int contrast, CancellationToken cancellationToken = default)
{
diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Helpers/PnpIdHelper.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Helpers/PnpIdHelper.cs
new file mode 100644
index 0000000000..7bfba133e4
--- /dev/null
+++ b/src/modules/powerdisplay/PowerDisplay.Lib/Helpers/PnpIdHelper.cs
@@ -0,0 +1,86 @@
+// 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.Frozen;
+using System.Collections.Generic;
+
+namespace PowerDisplay.Common.Helpers;
+
+///
+/// Helper class for mapping PnP (Plug and Play) manufacturer IDs to display names.
+/// PnP IDs are 3-character codes assigned by Microsoft to hardware manufacturers.
+/// See: https://uefi.org/pnp_id_list
+///
+public static class PnpIdHelper
+{
+ ///
+ /// Map of common laptop/monitor manufacturer PnP IDs to display names.
+ /// Only includes manufacturers known to produce laptops with internal displays.
+ ///
+ private static readonly FrozenDictionary ManufacturerNames = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ // Major laptop manufacturers
+ { "ACR", "Acer" },
+ { "AUO", "AU Optronics" },
+ { "BOE", "BOE" },
+ { "CMN", "Chi Mei Innolux" },
+ { "DEL", "Dell" },
+ { "HWP", "HP" },
+ { "IVO", "InfoVision" },
+ { "LEN", "Lenovo" },
+ { "LGD", "LG Display" },
+ { "NCP", "Najing CEC Panda" },
+ { "SAM", "Samsung" },
+ { "SDC", "Samsung Display" },
+ { "SEC", "Samsung Electronics" },
+ { "SHP", "Sharp" },
+ { "AUS", "ASUS" },
+ { "MSI", "MSI" },
+ { "APP", "Apple" },
+ { "SNY", "Sony" },
+ { "PHL", "Philips" },
+ { "HSD", "HannStar" },
+ { "CPT", "Chunghwa Picture Tubes" },
+ { "QDS", "Quanta Display" },
+ { "TMX", "Tianma Microelectronics" },
+ { "CSO", "CSOT" },
+
+ // Microsoft Surface
+ { "MSF", "Microsoft" },
+ }.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Extract the 3-character PnP manufacturer ID from a hardware ID.
+ ///
+ /// Hardware ID like "LEN4038" or "BOE0900".
+ /// The 3-character PnP ID (e.g., "LEN"), or null if invalid.
+ public static string? ExtractPnpId(string? hardwareId)
+ {
+ if (string.IsNullOrEmpty(hardwareId) || hardwareId.Length < 3)
+ {
+ return null;
+ }
+
+ // PnP ID is the first 3 characters
+ return hardwareId.Substring(0, 3).ToUpperInvariant();
+ }
+
+ ///
+ /// Get a user-friendly display name for an internal display based on its hardware ID.
+ ///
+ /// Hardware ID like "LEN4038" or "BOE0900".
+ /// Display name like "Lenovo Built-in Display" or "Built-in Display" as fallback.
+ public static string GetBuiltInDisplayName(string? hardwareId)
+ {
+ var pnpId = ExtractPnpId(hardwareId);
+
+ if (pnpId != null && ManufacturerNames.TryGetValue(pnpId, out var manufacturer))
+ {
+ return $"{manufacturer} Built-in Display";
+ }
+
+ return "Built-in Display";
+ }
+}
diff --git a/src/modules/powerdisplay/PowerDisplay/Assets/PowerDisplay.ico b/src/modules/powerdisplay/PowerDisplay/Assets/PowerDisplay/PowerDisplay.ico
similarity index 100%
rename from src/modules/powerdisplay/PowerDisplay/Assets/PowerDisplay.ico
rename to src/modules/powerdisplay/PowerDisplay/Assets/PowerDisplay/PowerDisplay.ico
diff --git a/src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj b/src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj
index 42ab688fc5..78107c261b 100644
--- a/src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj
+++ b/src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj
@@ -7,7 +7,7 @@
WinExe
PowerDisplay
app.manifest
- Assets\PowerDisplay.ico
+ Assets\PowerDisplay\PowerDisplay.ico
x64;ARM64
true
true
@@ -89,7 +89,7 @@
-
+
PreserveNewest
diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Monitors.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Monitors.cs
index f7b0b1d889..1e602a55c0 100644
--- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Monitors.cs
+++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Monitors.cs
@@ -58,23 +58,31 @@ public partial class MainViewModel
}
}
- public async Task RefreshMonitorsAsync()
+ ///
+ /// Refresh monitors list asynchronously.
+ ///
+ /// If true, skip the IsScanning check (used by OnDisplayChanged which sets IsScanning before calling).
+ public async Task RefreshMonitorsAsync(bool skipScanningCheck = false)
{
- if (IsScanning)
+ if (!skipScanningCheck && IsScanning)
{
+ Logger.LogDebug("[RefreshMonitorsAsync] Skipping refresh - already scanning");
return;
}
try
{
IsScanning = true;
+ Logger.LogInfo("[RefreshMonitorsAsync] Starting monitor discovery...");
var monitors = await _monitorManager.DiscoverMonitorsAsync(_cancellationTokenSource.Token);
+ Logger.LogInfo($"[RefreshMonitorsAsync] Discovery complete, found {monitors.Count} monitors");
_dispatcherQueue.TryEnqueue(() =>
{
UpdateMonitorList(monitors, isInitialLoad: false);
IsScanning = false;
+ Logger.LogInfo("[RefreshMonitorsAsync] UI update complete, scanning stopped");
});
}
catch (Exception ex)
diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs
index 4b5b41b849..069c23168f 100644
--- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs
+++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs
@@ -341,9 +341,9 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
// Wait for hardware to stabilize (DDC/CI may not be ready immediately after plug)
await Task.Delay(TimeSpan.FromSeconds(5));
- // Perform actual refresh
+ // Perform actual refresh - skip scanning check since we already set IsScanning above
Logger.LogInfo("[MainViewModel] Delay complete, now refreshing monitors...");
- await RefreshMonitorsAsync();
+ await RefreshMonitorsAsync(skipScanningCheck: true);
}
///