Remove Manufacturer/ConnectionType from Monitor model

Simplifies monitor metadata by removing Manufacturer and ConnectionType properties and related extraction logic. Refactors WMI controller to always instantiate, relying on monitor discovery for support. Updates model property summaries for clarity, adjusts LightSwitchListener log prefix, and removes Manufacturer from MonitorViewModel. Streamlines code and improves documentation consistency.
This commit is contained in:
Yu Leng
2025-12-11 09:52:43 +08:00
parent 0fc2fc42d3
commit 54006a8ef1
9 changed files with 92 additions and 191 deletions

View File

@@ -140,9 +140,7 @@ namespace PowerDisplay.Common.Drivers.DDC
IsAvailable = true,
Handle = physicalMonitor.HPhysicalMonitor,
Capabilities = MonitorCapabilities.DdcCi,
ConnectionType = "External",
CommunicationMethod = "DDC/CI",
Manufacturer = ExtractManufacturer(name),
CapabilitiesStatus = "unknown",
MonitorNumber = monitorInfo.MonitorNumber,
GdiDeviceName = monitorInfo.GdiDeviceName ?? string.Empty,
@@ -160,32 +158,5 @@ namespace PowerDisplay.Common.Drivers.DDC
return null;
}
}
/// <summary>
/// Extract manufacturer from name
/// </summary>
private static string ExtractManufacturer(string name)
{
if (string.IsNullOrEmpty(name))
{
return "Unknown";
}
// Common manufacturer prefixes
var manufacturers = new[] { "DELL", "HP", "LG", "Samsung", "ASUS", "Acer", "BenQ", "AOC", "ViewSonic" };
var upperName = name.ToUpperInvariant();
foreach (var manufacturer in manufacturers)
{
if (upperName.Contains(manufacturer))
{
return manufacturer;
}
}
// Return first word as manufacturer
var firstWord = name.Split(' ')[0];
return firstWord.Length > 2 ? firstWord : "Unknown";
}
}
}

View File

@@ -34,8 +34,6 @@ namespace PowerDisplay.Common.Drivers.WMI
private const int WbemEInvalidQuery = unchecked((int)0x80041017);
private const int WmiFeatureNotSupported = 0x1068;
private bool _disposed;
/// <summary>
/// Classifies WMI exceptions into user-friendly error messages.
/// </summary>
@@ -54,14 +52,6 @@ namespace PowerDisplay.Common.Drivers.WMI
};
}
/// <summary>
/// Determines if the WMI error is expected for systems without WMI brightness support.
/// </summary>
private static bool IsExpectedUnsupportedError(WmiException ex)
{
return ex.HResult == WmiFeatureNotSupported || ex.HResult == WbemENotFound;
}
/// <summary>
/// Escape special characters in WMI query strings.
/// WMI requires backslashes and single quotes to be escaped in WHERE clauses.
@@ -106,6 +96,19 @@ namespace PowerDisplay.Common.Drivers.WMI
return string.Empty;
}
/// <summary>
/// Build a WMI query filtered by monitor instance name.
/// </summary>
/// <param name="wmiClass">The WMI class to query.</param>
/// <param name="instanceName">The monitor instance name to filter by.</param>
/// <param name="selectClause">Optional SELECT clause fields (defaults to "*").</param>
/// <returns>The formatted WMI query string.</returns>
private static string BuildInstanceNameQuery(string wmiClass, string instanceName, string selectClause = "*")
{
var escapedInstanceName = EscapeWmiString(instanceName);
return $"SELECT {selectClause} FROM {wmiClass} WHERE InstanceName = '{escapedInstanceName}'";
}
/// <summary>
/// Get MonitorDisplayInfo from dictionary by matching HardwareId.
/// Uses QueryDisplayConfig path index which matches Windows Display Settings "Identify" feature.
@@ -120,21 +123,21 @@ namespace PowerDisplay.Common.Drivers.WMI
return null;
}
foreach (var kvp in monitorDisplayInfos)
var match = monitorDisplayInfos.Values.FirstOrDefault(
v => hardwareId.Equals(v.HardwareId, StringComparison.OrdinalIgnoreCase));
// Check if match was found (struct default has null/empty HardwareId)
if (!string.IsNullOrEmpty(match.HardwareId))
{
if (!string.IsNullOrEmpty(kvp.Value.HardwareId) &&
kvp.Value.HardwareId.Equals(hardwareId, StringComparison.OrdinalIgnoreCase))
{
Logger.LogDebug($"WMI: Matched HardwareId '{hardwareId}' to MonitorNumber {kvp.Value.MonitorNumber}, GdiDeviceName={kvp.Value.GdiDeviceName}");
return kvp.Value;
}
Logger.LogDebug($"WMI: Matched HardwareId '{hardwareId}' to MonitorNumber {match.MonitorNumber}, GdiDeviceName={match.GdiDeviceName}");
return match;
}
Logger.LogWarning($"WMI: Could not find MonitorDisplayInfo for HardwareId '{hardwareId}'");
return null;
}
public string Name => "WMI Monitor Controller (WmiLight)";
public string Name => "WMI Monitor Controller";
/// <summary>
/// Get monitor brightness
@@ -149,10 +152,7 @@ namespace PowerDisplay.Common.Drivers.WMI
try
{
using var connection = new WmiConnection(WmiNamespace);
// Filter by InstanceName to target the specific monitor
var escapedInstanceName = EscapeWmiString(monitor.InstanceName);
var query = $"SELECT CurrentBrightness FROM {BrightnessQueryClass} WHERE InstanceName = '{escapedInstanceName}'";
var query = BuildInstanceNameQuery(BrightnessQueryClass, monitor.InstanceName, "CurrentBrightness");
var results = connection.CreateQuery(query);
foreach (var obj in results)
@@ -194,10 +194,7 @@ namespace PowerDisplay.Common.Drivers.WMI
try
{
using var connection = new WmiConnection(WmiNamespace);
// Filter by InstanceName to target the specific monitor
var escapedInstanceName = EscapeWmiString(monitor.InstanceName);
var query = $"SELECT * FROM {BrightnessMethodClass} WHERE InstanceName = '{escapedInstanceName}'";
var query = BuildInstanceNameQuery(BrightnessMethodClass, monitor.InstanceName);
var results = connection.CreateQuery(query);
foreach (var obj in results)
@@ -221,10 +218,8 @@ namespace PowerDisplay.Common.Drivers.WMI
{
return MonitorOperationResult.Success();
}
else
{
return MonitorOperationResult.Failure($"WMI method returned error code: {result}", (int)result);
}
return MonitorOperationResult.Failure($"WMI method returned error code: {result}", (int)result);
}
}
@@ -338,9 +333,7 @@ namespace PowerDisplay.Common.Drivers.WMI
IsAvailable = true,
InstanceName = instanceName,
Capabilities = MonitorCapabilities.Brightness | MonitorCapabilities.Wmi,
ConnectionType = "Internal",
CommunicationMethod = "WMI",
Manufacturer = edidId.Length >= 3 ? edidId.Substring(0, 3) : "Internal",
SupportsColorTemperature = false,
MonitorNumber = monitorNumber,
GdiDeviceName = gdiDeviceName,
@@ -404,38 +397,6 @@ namespace PowerDisplay.Common.Drivers.WMI
return null;
}
/// <summary>
/// Check WMI service availability
/// </summary>
public static bool IsWmiAvailable()
{
try
{
using var connection = new WmiConnection(WmiNamespace);
var query = $"SELECT * FROM {BrightnessQueryClass}";
var results = connection.CreateQuery(query).ToList();
return results.Count > 0;
}
catch (WmiException ex) when (IsExpectedUnsupportedError(ex))
{
// Expected on systems without WMI brightness support (desktops, some laptops)
Logger.LogInfo("WMI brightness control not supported on this system (expected for desktops)");
return false;
}
catch (WmiException ex)
{
// Unexpected WMI error - log with details for debugging
Logger.LogWarning($"WMI availability check failed: {ex.Message} (HResult: 0x{ex.HResult:X})");
return false;
}
catch (Exception ex)
{
// Unexpected non-WMI error
Logger.LogDebug($"WMI availability check failed: {ex.Message}");
return false;
}
}
// Extended features not supported by WMI
public Task<MonitorOperationResult> SetContrastAsync(Monitor monitor, int contrast, CancellationToken cancellationToken = default)
{
@@ -471,17 +432,9 @@ namespace PowerDisplay.Common.Drivers.WMI
public void Dispose()
{
Dispose(true);
// WmiLight objects are created per-operation and disposed immediately via using statements.
// No instance-level resources require cleanup.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
// WmiLight objects are automatically cleaned up, no specific cleanup needed here
_disposed = true;
}
}
}
}

View File

@@ -102,8 +102,4 @@ namespace PowerDisplay.Common.Interfaces
/// </summary>
void Dispose();
}
// IMonitorManager interface removed - YAGNI principle
// Only one implementation exists (MonitorManager), so interface abstraction is unnecessary
// This simplifies the codebase and eliminates maintenance overhead
}

View File

@@ -26,7 +26,7 @@ namespace PowerDisplay.Common.Models
private bool _isAvailable = true;
/// <summary>
/// Unique identifier for all purposes: UI lookups, IPC, persistent storage, and handle management.
/// Gets or sets unique identifier for all purposes: UI lookups, IPC, persistent storage, and handle management.
/// </summary>
/// <remarks>
/// Format: "{Source}_{EdidId}_{MonitorNumber}" where Source is "DDC" or "WMI".
@@ -36,12 +36,12 @@ namespace PowerDisplay.Common.Models
public string Id { get; set; } = string.Empty;
/// <summary>
/// Display name
/// Gets or sets display name
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Current brightness (0-100)
/// Gets or sets current brightness (0-100)
/// </summary>
public int CurrentBrightness
{
@@ -58,17 +58,17 @@ namespace PowerDisplay.Common.Models
}
/// <summary>
/// Minimum brightness value
/// Gets or sets minimum brightness value
/// </summary>
public int MinBrightness { get; set; }
/// <summary>
/// Maximum brightness value
/// Gets or sets maximum brightness value
/// </summary>
public int MaxBrightness { get; set; } = 100;
/// <summary>
/// Current color temperature VCP preset value (from VCP code 0x14).
/// Gets or sets current color temperature VCP preset value (from VCP code 0x14).
/// This stores the raw VCP value (e.g., 0x05 for 6500K), not Kelvin temperature.
/// Use ColorTemperaturePresetName to get human-readable name.
/// </summary>
@@ -87,18 +87,18 @@ namespace PowerDisplay.Common.Models
}
/// <summary>
/// Human-readable color temperature preset name (e.g., "6500K (0x05)", "sRGB (0x01)")
/// Gets human-readable color temperature preset name (e.g., "6500K (0x05)", "sRGB (0x01)")
/// </summary>
public string ColorTemperaturePresetName =>
VcpValueNames.GetFormattedName(0x14, CurrentColorTemperature);
/// <summary>
/// Whether supports color temperature adjustment via VCP 0x14
/// Gets or sets a value indicating whether whether supports color temperature adjustment via VCP 0x14
/// </summary>
public bool SupportsColorTemperature { get; set; }
/// <summary>
/// Current input source VCP value (from VCP code 0x60).
/// Gets or sets current input source VCP value (from VCP code 0x60).
/// This stores the raw VCP value (e.g., 0x11 for HDMI-1).
/// Use InputSourceName to get human-readable name.
/// </summary>
@@ -117,35 +117,35 @@ namespace PowerDisplay.Common.Models
}
/// <summary>
/// Human-readable input source name (e.g., "HDMI-1", "DisplayPort-1")
/// Gets human-readable input source name (e.g., "HDMI-1", "DisplayPort-1")
/// Returns just the name without hex value for cleaner UI display.
/// </summary>
public string InputSourceName =>
VcpValueNames.GetName(0x60, CurrentInputSource) ?? $"Source 0x{CurrentInputSource:X2}";
/// <summary>
/// Whether supports input source switching via VCP 0x60
/// Gets a value indicating whether whether supports input source switching via VCP 0x60
/// </summary>
public bool SupportsInputSource => VcpCapabilitiesInfo?.SupportsVcpCode(0x60) ?? false;
/// <summary>
/// Get supported input sources from capabilities (as list of VCP values)
/// Gets get supported input sources from capabilities (as list of VCP values)
/// </summary>
public System.Collections.Generic.IReadOnlyList<int>? SupportedInputSources =>
VcpCapabilitiesInfo?.GetSupportedValues(0x60);
/// <summary>
/// Capabilities detection status: "available", "unavailable", or "unknown"
/// Gets or sets capabilities detection status: "available", "unavailable", or "unknown"
/// </summary>
public string CapabilitiesStatus { get; set; } = "unknown";
/// <summary>
/// Whether supports contrast adjustment
/// Gets a value indicating whether whether supports contrast adjustment
/// </summary>
public bool SupportsContrast => Capabilities.HasFlag(MonitorCapabilities.Contrast);
/// <summary>
/// Whether supports volume adjustment (for audio-capable monitors)
/// Gets a value indicating whether whether supports volume adjustment (for audio-capable monitors)
/// </summary>
public bool SupportsVolume => Capabilities.HasFlag(MonitorCapabilities.Volume);
@@ -153,7 +153,7 @@ namespace PowerDisplay.Common.Models
private int _currentVolume = 50;
/// <summary>
/// Current contrast (0-100)
/// Gets or sets current contrast (0-100)
/// </summary>
public int CurrentContrast
{
@@ -170,17 +170,17 @@ namespace PowerDisplay.Common.Models
}
/// <summary>
/// Minimum contrast value
/// Gets or sets minimum contrast value
/// </summary>
public int MinContrast { get; set; }
/// <summary>
/// Maximum contrast value
/// Gets or sets maximum contrast value
/// </summary>
public int MaxContrast { get; set; } = 100;
/// <summary>
/// Current volume (0-100)
/// Gets or sets current volume (0-100)
/// </summary>
public int CurrentVolume
{
@@ -197,17 +197,17 @@ namespace PowerDisplay.Common.Models
}
/// <summary>
/// Minimum volume value
/// Gets or sets minimum volume value
/// </summary>
public int MinVolume { get; set; }
/// <summary>
/// Maximum volume value
/// Gets or sets maximum volume value
/// </summary>
public int MaxVolume { get; set; } = 100;
/// <summary>
/// Whether available/online
/// Gets or sets a value indicating whether whether available/online
/// </summary>
public bool IsAvailable
{
@@ -223,47 +223,37 @@ namespace PowerDisplay.Common.Models
}
/// <summary>
/// Physical monitor handle (for DDC/CI)
/// Gets or sets physical monitor handle (for DDC/CI)
/// </summary>
public IntPtr Handle { get; set; } = IntPtr.Zero;
/// <summary>
/// Instance name (used by WMI)
/// Gets or sets instance name (used by WMI)
/// </summary>
public string InstanceName { get; set; } = string.Empty;
/// <summary>
/// Manufacturer information
/// </summary>
public string Manufacturer { get; set; } = string.Empty;
/// <summary>
/// Connection type (HDMI, DP, VGA, etc.)
/// </summary>
public string ConnectionType { get; set; } = string.Empty;
/// <summary>
/// Communication method (DDC/CI, WMI, HDR API, etc.)
/// Gets or sets communication method (DDC/CI, WMI, HDR API, etc.)
/// </summary>
public string CommunicationMethod { get; set; } = string.Empty;
/// <summary>
/// Supported control methods
/// Gets or sets supported control methods
/// </summary>
public MonitorCapabilities Capabilities { get; set; } = MonitorCapabilities.None;
/// <summary>
/// Raw DDC/CI capabilities string (MCCS format)
/// Gets or sets raw DDC/CI capabilities string (MCCS format)
/// </summary>
public string? CapabilitiesRaw { get; set; }
/// <summary>
/// Parsed VCP capabilities information
/// Gets or sets parsed VCP capabilities information
/// </summary>
public VcpCapabilities? VcpCapabilitiesInfo { get; set; }
/// <summary>
/// Last update time
/// Gets or sets last update time
/// </summary>
public DateTime LastUpdate { get; set; } = DateTime.Now;

View File

@@ -12,22 +12,22 @@ namespace PowerDisplay.Common.Models
public readonly struct MonitorOperationResult
{
/// <summary>
/// Whether the operation was successful
/// Gets a value indicating whether whether the operation was successful
/// </summary>
public bool IsSuccess { get; }
/// <summary>
/// Error message
/// Gets error message
/// </summary>
public string? ErrorMessage { get; }
/// <summary>
/// System error code
/// Gets system error code
/// </summary>
public int? ErrorCode { get; }
/// <summary>
/// Operation timestamp
/// Gets operation timestamp
/// </summary>
public DateTime Timestamp { get; }

View File

@@ -13,47 +13,47 @@ namespace PowerDisplay.Common.Models
public class VcpCapabilities
{
/// <summary>
/// Raw capabilities string (MCCS format)
/// Gets or sets raw capabilities string (MCCS format)
/// </summary>
public string Raw { get; set; } = string.Empty;
/// <summary>
/// Monitor model name from capabilities
/// Gets or sets monitor model name from capabilities
/// </summary>
public string? Model { get; set; }
/// <summary>
/// Monitor type from capabilities (e.g., "LCD")
/// Gets or sets monitor type from capabilities (e.g., "LCD")
/// </summary>
public string? Type { get; set; }
/// <summary>
/// MCCS protocol version
/// Gets or sets mCCS protocol version
/// </summary>
public string? Protocol { get; set; }
/// <summary>
/// MCCS version (e.g., "2.2", "2.1")
/// Gets or sets mCCS version (e.g., "2.2", "2.1")
/// </summary>
public string? MccsVersion { get; set; }
/// <summary>
/// Supported command codes
/// Gets or sets supported command codes
/// </summary>
public List<byte> SupportedCommands { get; set; } = new();
/// <summary>
/// Supported VCP codes with their information
/// Gets or sets supported VCP codes with their information
/// </summary>
public Dictionary<byte, VcpCodeInfo> SupportedVcpCodes { get; set; } = new();
/// <summary>
/// Window capabilities for PIP/PBP support
/// Gets or sets window capabilities for PIP/PBP support
/// </summary>
public List<WindowCapability> Windows { get; set; } = new();
/// <summary>
/// Check if display supports PIP/PBP windows
/// Gets a value indicating whether check if display supports PIP/PBP windows
/// </summary>
public bool HasWindowSupport => Windows.Count > 0;
@@ -119,7 +119,7 @@ namespace PowerDisplay.Common.Models
}
/// <summary>
/// Creates an empty capabilities object
/// Gets creates an empty capabilities object
/// </summary>
public static VcpCapabilities Empty => new();
@@ -135,27 +135,27 @@ namespace PowerDisplay.Common.Models
public readonly struct VcpCodeInfo
{
/// <summary>
/// VCP code (e.g., 0x10 for brightness)
/// Gets vCP code (e.g., 0x10 for brightness)
/// </summary>
public byte Code { get; }
/// <summary>
/// Human-readable name of the VCP code
/// Gets human-readable name of the VCP code
/// </summary>
public string Name { get; }
/// <summary>
/// Supported discrete values (empty if continuous range)
/// Gets supported discrete values (empty if continuous range)
/// </summary>
public IReadOnlyList<int> SupportedValues { get; }
/// <summary>
/// Whether this VCP code has discrete values
/// Gets a value indicating whether whether this VCP code has discrete values
/// </summary>
public bool HasDiscreteValues => SupportedValues.Count > 0;
/// <summary>
/// Whether this VCP code supports a continuous range
/// Gets a value indicating whether whether this VCP code supports a continuous range
/// </summary>
public bool IsContinuous => SupportedValues.Count == 0;
@@ -193,12 +193,12 @@ namespace PowerDisplay.Common.Models
public readonly struct WindowSize
{
/// <summary>
/// Width in pixels
/// Gets width in pixels
/// </summary>
public int Width { get; }
/// <summary>
/// Height in pixels
/// Gets height in pixels
/// </summary>
public int Height { get; }
@@ -217,32 +217,32 @@ namespace PowerDisplay.Common.Models
public readonly struct WindowArea
{
/// <summary>
/// Top-left X coordinate
/// Gets top-left X coordinate
/// </summary>
public int X1 { get; }
/// <summary>
/// Top-left Y coordinate
/// Gets top-left Y coordinate
/// </summary>
public int Y1 { get; }
/// <summary>
/// Bottom-right X coordinate
/// Gets bottom-right X coordinate
/// </summary>
public int X2 { get; }
/// <summary>
/// Bottom-right Y coordinate
/// Gets bottom-right Y coordinate
/// </summary>
public int Y2 { get; }
/// <summary>
/// Width of the area
/// Gets width of the area
/// </summary>
public int Width => X2 - X1;
/// <summary>
/// Height of the area
/// Gets height of the area
/// </summary>
public int Height => Y2 - Y1;
@@ -263,32 +263,32 @@ namespace PowerDisplay.Common.Models
public readonly struct WindowCapability
{
/// <summary>
/// Window number (1, 2, 3, etc.)
/// Gets window number (1, 2, 3, etc.)
/// </summary>
public int WindowNumber { get; }
/// <summary>
/// Window type (e.g., "PIP", "PBP")
/// Gets window type (e.g., "PIP", "PBP")
/// </summary>
public string Type { get; }
/// <summary>
/// Window area coordinates
/// Gets window area coordinates
/// </summary>
public WindowArea Area { get; }
/// <summary>
/// Maximum window size
/// Gets maximum window size
/// </summary>
public WindowSize MaxSize { get; }
/// <summary>
/// Minimum window size
/// Gets minimum window size
/// </summary>
public WindowSize MinSize { get; }
/// <summary>
/// Window identifier
/// Gets window identifier
/// </summary>
public int WindowId { get; }

View File

@@ -20,7 +20,7 @@ namespace PowerDisplay.Common.Services
/// </summary>
public sealed partial class LightSwitchListener : IDisposable
{
private const string LogPrefix = "[LightSwitch Integration]";
private const string LogPrefix = "[LightSwitch Listener]";
private Thread? _listenerThread;
private CancellationTokenSource? _cancellationTokenSource;

View File

@@ -61,15 +61,8 @@ namespace PowerDisplay.Helpers
try
{
// WMI controller (internal monitors)
// First check if WMI is available
if (WmiController.IsWmiAvailable())
{
_wmiController = new WmiController();
}
else
{
Logger.LogWarning("WMI brightness control not available on this system");
}
// Always create - DiscoverMonitorsAsync returns empty list if WMI is unavailable
_wmiController = new WmiController();
}
catch (Exception ex)
{

View File

@@ -286,8 +286,6 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
}
}
public string Manufacturer => _monitor.Manufacturer;
public string CommunicationMethod => _monitor.CommunicationMethod;
public bool IsInternal => _monitor.CommunicationMethod == "WMI";