2025-10-20 16:22:47 +08:00
|
|
|
// 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;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using ManagedCommon;
|
2025-11-24 21:58:34 +08:00
|
|
|
using PowerDisplay.Common.Drivers;
|
|
|
|
|
using PowerDisplay.Common.Drivers.DDC;
|
|
|
|
|
using PowerDisplay.Common.Drivers.WMI;
|
2025-11-24 18:08:11 +08:00
|
|
|
using PowerDisplay.Common.Interfaces;
|
|
|
|
|
using PowerDisplay.Common.Models;
|
2025-11-28 05:08:55 +08:00
|
|
|
using PowerDisplay.Common.Services;
|
2025-11-24 18:08:11 +08:00
|
|
|
using PowerDisplay.Common.Utils;
|
|
|
|
|
using Monitor = PowerDisplay.Common.Models.Monitor;
|
2025-10-20 16:22:47 +08:00
|
|
|
|
2025-12-04 06:14:01 +08:00
|
|
|
namespace PowerDisplay.Helpers
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Monitor manager for unified control of all monitors
|
|
|
|
|
/// No interface abstraction - KISS principle (only one implementation needed)
|
|
|
|
|
/// </summary>
|
|
|
|
|
public partial class MonitorManager : IDisposable
|
|
|
|
|
{
|
|
|
|
|
private readonly List<Monitor> _monitors = new();
|
2025-11-24 23:36:25 +08:00
|
|
|
private readonly Dictionary<string, Monitor> _monitorLookup = new();
|
2025-10-20 16:22:47 +08:00
|
|
|
private readonly SemaphoreSlim _discoveryLock = new(1, 1);
|
2025-11-28 05:08:55 +08:00
|
|
|
private readonly DisplayRotationService _rotationService = new();
|
2025-12-10 17:17:13 +08:00
|
|
|
|
|
|
|
|
// Controllers stored by type for O(1) lookup based on CommunicationMethod
|
|
|
|
|
private DdcCiController? _ddcController;
|
|
|
|
|
private WmiController? _wmiController;
|
2025-10-20 16:22:47 +08:00
|
|
|
private bool _disposed;
|
|
|
|
|
|
|
|
|
|
public IReadOnlyList<Monitor> Monitors => _monitors.AsReadOnly();
|
|
|
|
|
|
|
|
|
|
public MonitorManager()
|
|
|
|
|
{
|
|
|
|
|
// Initialize controllers
|
|
|
|
|
InitializeControllers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initialize controllers
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void InitializeControllers()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// DDC/CI controller (external monitors)
|
2025-12-10 17:17:13 +08:00
|
|
|
_ddcController = new DdcCiController();
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogWarning($"Failed to initialize DDC/CI controller: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// WMI controller (internal monitors)
|
|
|
|
|
// First check if WMI is available
|
|
|
|
|
if (WmiController.IsWmiAvailable())
|
|
|
|
|
{
|
2025-12-10 17:17:13 +08:00
|
|
|
_wmiController = new WmiController();
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-11-13 14:14:49 +08:00
|
|
|
Logger.LogWarning("WMI brightness control not available on this system");
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogWarning($"Failed to initialize WMI controller: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-11-26 05:02:49 +08:00
|
|
|
/// Discover all monitors from all controllers.
|
2025-12-11 09:27:10 +08:00
|
|
|
/// Each controller is responsible for fully initializing its monitors
|
|
|
|
|
/// (including brightness, capabilities, input source, color temperature, etc.)
|
2025-10-20 16:22:47 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public async Task<IReadOnlyList<Monitor>> DiscoverMonitorsAsync(CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
await _discoveryLock.WaitAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-12-10 17:17:13 +08:00
|
|
|
var discoveredMonitors = await DiscoverFromAllControllersAsync(cancellationToken);
|
2025-10-20 16:22:47 +08:00
|
|
|
|
2025-12-10 11:00:36 +08:00
|
|
|
// Update collections
|
|
|
|
|
_monitors.Clear();
|
|
|
|
|
_monitorLookup.Clear();
|
|
|
|
|
|
2025-12-10 17:17:13 +08:00
|
|
|
var sortedMonitors = discoveredMonitors
|
2025-12-10 11:00:36 +08:00
|
|
|
.OrderBy(m => m.MonitorNumber)
|
|
|
|
|
.ToList();
|
2025-10-20 16:22:47 +08:00
|
|
|
|
2025-12-10 17:17:13 +08:00
|
|
|
_monitors.AddRange(sortedMonitors);
|
|
|
|
|
foreach (var monitor in sortedMonitors)
|
2025-12-10 11:00:36 +08:00
|
|
|
{
|
|
|
|
|
_monitorLookup[monitor.Id] = monitor;
|
|
|
|
|
}
|
2025-11-19 15:08:00 +08:00
|
|
|
|
2025-11-26 05:02:49 +08:00
|
|
|
return _monitors.AsReadOnly();
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_discoveryLock.Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Discover monitors from all registered controllers in parallel.
|
|
|
|
|
/// </summary>
|
2025-12-10 17:17:13 +08:00
|
|
|
private async Task<List<Monitor>> DiscoverFromAllControllersAsync(CancellationToken cancellationToken)
|
2025-11-26 05:02:49 +08:00
|
|
|
{
|
2025-12-10 17:17:13 +08:00
|
|
|
var tasks = new List<Task<IEnumerable<Monitor>>>();
|
|
|
|
|
|
|
|
|
|
if (_ddcController != null)
|
2025-11-26 05:02:49 +08:00
|
|
|
{
|
2025-12-10 17:17:13 +08:00
|
|
|
tasks.Add(SafeDiscoverAsync(_ddcController, cancellationToken));
|
|
|
|
|
}
|
2025-11-26 05:02:49 +08:00
|
|
|
|
2025-12-10 17:17:13 +08:00
|
|
|
if (_wmiController != null)
|
|
|
|
|
{
|
|
|
|
|
tasks.Add(SafeDiscoverAsync(_wmiController, cancellationToken));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var results = await Task.WhenAll(tasks);
|
|
|
|
|
return results.SelectMany(m => m).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Safely discover monitors from a controller, returning empty list on failure.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static async Task<IEnumerable<Monitor>> SafeDiscoverAsync(
|
|
|
|
|
IMonitorController controller,
|
|
|
|
|
CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return await controller.DiscoverMonitorsAsync(cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogWarning($"Controller {controller.Name} discovery failed: {ex.Message}");
|
|
|
|
|
return Enumerable.Empty<Monitor>();
|
|
|
|
|
}
|
2025-11-26 05:02:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 16:22:47 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Get brightness of the specified monitor
|
|
|
|
|
/// </summary>
|
2025-12-10 19:04:19 +08:00
|
|
|
public async Task<VcpFeatureValue> GetBrightnessAsync(string monitorId, CancellationToken cancellationToken = default)
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
|
|
|
|
var monitor = GetMonitor(monitorId);
|
|
|
|
|
if (monitor == null)
|
|
|
|
|
{
|
2025-12-10 19:04:19 +08:00
|
|
|
return VcpFeatureValue.Invalid;
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-10 17:17:13 +08:00
|
|
|
var controller = GetControllerForMonitor(monitor);
|
2025-10-20 16:22:47 +08:00
|
|
|
if (controller == null)
|
|
|
|
|
{
|
2025-12-10 19:04:19 +08:00
|
|
|
return VcpFeatureValue.Invalid;
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var brightnessInfo = await controller.GetBrightnessAsync(monitor, cancellationToken);
|
|
|
|
|
|
|
|
|
|
// Update cached brightness value
|
|
|
|
|
if (brightnessInfo.IsValid)
|
|
|
|
|
{
|
|
|
|
|
monitor.UpdateStatus(brightnessInfo.ToPercentage(), true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return brightnessInfo;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
// Mark monitor as unavailable
|
|
|
|
|
Logger.LogError($"Failed to get brightness for monitor {monitorId}: {ex.Message}");
|
|
|
|
|
monitor.IsAvailable = false;
|
2025-12-10 19:04:19 +08:00
|
|
|
return VcpFeatureValue.Invalid;
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Set brightness of the specified monitor
|
|
|
|
|
/// </summary>
|
2025-11-27 22:43:28 +08:00
|
|
|
public Task<MonitorOperationResult> SetBrightnessAsync(string monitorId, int brightness, CancellationToken cancellationToken = default)
|
|
|
|
|
=> ExecuteMonitorOperationAsync(
|
|
|
|
|
monitorId,
|
|
|
|
|
brightness,
|
|
|
|
|
(ctrl, mon, val, ct) => ctrl.SetBrightnessAsync(mon, val, ct),
|
|
|
|
|
(mon, val) => mon.UpdateStatus(val, true),
|
|
|
|
|
cancellationToken);
|
2025-10-20 16:22:47 +08:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Set contrast of the specified monitor
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Task<MonitorOperationResult> SetContrastAsync(string monitorId, int contrast, CancellationToken cancellationToken = default)
|
|
|
|
|
=> ExecuteMonitorOperationAsync(
|
|
|
|
|
monitorId,
|
|
|
|
|
contrast,
|
|
|
|
|
(ctrl, mon, val, ct) => ctrl.SetContrastAsync(mon, val, ct),
|
|
|
|
|
(mon, val) => mon.CurrentContrast = val,
|
|
|
|
|
cancellationToken);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Set volume of the specified monitor
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Task<MonitorOperationResult> SetVolumeAsync(string monitorId, int volume, CancellationToken cancellationToken = default)
|
|
|
|
|
=> ExecuteMonitorOperationAsync(
|
|
|
|
|
monitorId,
|
|
|
|
|
volume,
|
|
|
|
|
(ctrl, mon, val, ct) => ctrl.SetVolumeAsync(mon, val, ct),
|
|
|
|
|
(mon, val) => mon.CurrentVolume = val,
|
|
|
|
|
cancellationToken);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get monitor color temperature
|
|
|
|
|
/// </summary>
|
2025-12-10 19:04:19 +08:00
|
|
|
public async Task<VcpFeatureValue> GetColorTemperatureAsync(string monitorId, CancellationToken cancellationToken = default)
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
|
|
|
|
var monitor = GetMonitor(monitorId);
|
|
|
|
|
if (monitor == null)
|
|
|
|
|
{
|
2025-12-10 19:04:19 +08:00
|
|
|
return VcpFeatureValue.Invalid;
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-10 17:17:13 +08:00
|
|
|
var controller = GetControllerForMonitor(monitor);
|
2025-10-20 16:22:47 +08:00
|
|
|
if (controller == null)
|
|
|
|
|
{
|
2025-12-10 19:04:19 +08:00
|
|
|
return VcpFeatureValue.Invalid;
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return await controller.GetColorTemperatureAsync(monitor, cancellationToken);
|
|
|
|
|
}
|
2025-11-24 18:08:11 +08:00
|
|
|
catch (Exception ex) when (ex is not OutOfMemoryException)
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
2025-11-24 18:08:11 +08:00
|
|
|
Logger.LogDebug($"GetColorTemperatureAsync failed: {ex.Message}");
|
2025-12-10 19:04:19 +08:00
|
|
|
return VcpFeatureValue.Invalid;
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Set monitor color temperature
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Task<MonitorOperationResult> SetColorTemperatureAsync(string monitorId, int colorTemperature, CancellationToken cancellationToken = default)
|
|
|
|
|
=> ExecuteMonitorOperationAsync(
|
|
|
|
|
monitorId,
|
|
|
|
|
colorTemperature,
|
|
|
|
|
(ctrl, mon, val, ct) => ctrl.SetColorTemperatureAsync(mon, val, ct),
|
|
|
|
|
(mon, val) => mon.CurrentColorTemperature = val,
|
|
|
|
|
cancellationToken);
|
|
|
|
|
|
2025-11-27 14:51:31 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Get current input source for a monitor
|
|
|
|
|
/// </summary>
|
2025-12-10 19:04:19 +08:00
|
|
|
public async Task<VcpFeatureValue> GetInputSourceAsync(string monitorId, CancellationToken cancellationToken = default)
|
2025-11-27 14:51:31 +08:00
|
|
|
{
|
|
|
|
|
var monitor = GetMonitor(monitorId);
|
|
|
|
|
if (monitor == null)
|
|
|
|
|
{
|
2025-12-10 19:04:19 +08:00
|
|
|
return VcpFeatureValue.Invalid;
|
2025-11-27 14:51:31 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-10 17:17:13 +08:00
|
|
|
var controller = GetControllerForMonitor(monitor);
|
2025-11-27 14:51:31 +08:00
|
|
|
if (controller == null)
|
|
|
|
|
{
|
2025-12-10 19:04:19 +08:00
|
|
|
return VcpFeatureValue.Invalid;
|
2025-11-27 14:51:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return await controller.GetInputSourceAsync(monitor, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) when (ex is not OutOfMemoryException)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogDebug($"GetInputSourceAsync failed: {ex.Message}");
|
2025-12-10 19:04:19 +08:00
|
|
|
return VcpFeatureValue.Invalid;
|
2025-11-27 14:51:31 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Set input source for a monitor
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Task<MonitorOperationResult> SetInputSourceAsync(string monitorId, int inputSource, CancellationToken cancellationToken = default)
|
|
|
|
|
=> ExecuteMonitorOperationAsync(
|
|
|
|
|
monitorId,
|
|
|
|
|
inputSource,
|
|
|
|
|
(ctrl, mon, val, ct) => ctrl.SetInputSourceAsync(mon, val, ct),
|
|
|
|
|
(mon, val) => mon.CurrentInputSource = val,
|
|
|
|
|
cancellationToken);
|
|
|
|
|
|
2025-11-28 05:08:55 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Set rotation/orientation for a monitor.
|
|
|
|
|
/// Uses Windows ChangeDisplaySettingsEx API (not DDC/CI).
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="monitorId">Monitor ID</param>
|
|
|
|
|
/// <param name="orientation">Orientation: 0=normal, 1=90°, 2=180°, 3=270°</param>
|
|
|
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
|
|
|
|
/// <returns>Operation result</returns>
|
|
|
|
|
public Task<MonitorOperationResult> SetRotationAsync(string monitorId, int orientation, CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
var monitor = GetMonitor(monitorId);
|
|
|
|
|
if (monitor == null)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogError($"[MonitorManager] SetRotation: Monitor not found: {monitorId}");
|
|
|
|
|
return Task.FromResult(MonitorOperationResult.Failure("Monitor not found"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rotation uses Windows display settings API, not DDC/CI controller
|
2025-12-10 14:16:28 +08:00
|
|
|
// Prefer using Monitor object which contains GdiDeviceName for accurate adapter targeting
|
|
|
|
|
var result = _rotationService.SetRotation(monitor, orientation);
|
2025-11-28 05:08:55 +08:00
|
|
|
|
|
|
|
|
if (result.IsSuccess)
|
|
|
|
|
{
|
|
|
|
|
monitor.Orientation = orientation;
|
|
|
|
|
monitor.LastUpdate = DateTime.Now;
|
|
|
|
|
Logger.LogInfo($"[MonitorManager] SetRotation: Successfully set {monitorId} to orientation {orientation}");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Logger.LogError($"[MonitorManager] SetRotation: Failed for {monitorId}: {result.ErrorMessage}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Task.FromResult(result);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 16:22:47 +08:00
|
|
|
/// <summary>
|
2025-11-24 23:36:25 +08:00
|
|
|
/// Get monitor by ID. Uses dictionary lookup for O(1) performance.
|
2025-10-20 16:22:47 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public Monitor? GetMonitor(string monitorId)
|
|
|
|
|
{
|
2025-11-24 23:36:25 +08:00
|
|
|
return _monitorLookup.TryGetValue(monitorId, out var monitor) ? monitor : null;
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-12-10 17:17:13 +08:00
|
|
|
/// Get controller for the monitor based on CommunicationMethod.
|
|
|
|
|
/// O(1) lookup - no async validation needed since controller type is determined at discovery.
|
2025-10-20 16:22:47 +08:00
|
|
|
/// </summary>
|
2025-12-10 17:17:13 +08:00
|
|
|
private IMonitorController? GetControllerForMonitor(Monitor monitor)
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
2025-12-10 17:17:13 +08:00
|
|
|
return monitor.CommunicationMethod switch
|
2025-11-17 14:53:43 +08:00
|
|
|
{
|
2025-12-10 17:17:13 +08:00
|
|
|
"WMI" => _wmiController,
|
|
|
|
|
"DDC/CI" => _ddcController,
|
|
|
|
|
_ => null,
|
|
|
|
|
};
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generic helper to execute monitor operations with common error handling.
|
|
|
|
|
/// Eliminates code duplication across Set* methods.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private async Task<MonitorOperationResult> ExecuteMonitorOperationAsync<T>(
|
|
|
|
|
string monitorId,
|
|
|
|
|
T value,
|
|
|
|
|
Func<IMonitorController, Monitor, T, CancellationToken, Task<MonitorOperationResult>> operation,
|
|
|
|
|
Action<Monitor, T> onSuccess,
|
|
|
|
|
CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
var monitor = GetMonitor(monitorId);
|
|
|
|
|
if (monitor == null)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogError($"[MonitorManager] Monitor not found: {monitorId}");
|
|
|
|
|
return MonitorOperationResult.Failure("Monitor not found");
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-10 17:17:13 +08:00
|
|
|
var controller = GetControllerForMonitor(monitor);
|
2025-10-20 16:22:47 +08:00
|
|
|
if (controller == null)
|
|
|
|
|
{
|
Refactor PowerDisplay for dynamic monitor capabilities
Removed reliance on static `MonitorType` enumeration, replacing it with dynamic `CommunicationMethod` for better flexibility. Updated `IMonitorController` and `MonitorManager` to dynamically determine monitor control capabilities.
Refactored `Monitor` model to streamline properties and improve color temperature handling. Enhanced `MonitorViewModel` with unified methods for brightness, contrast, volume, and color temperature updates, improving UI responsiveness and hardware synchronization.
Improved settings handling by adding support for hidden monitors, preserving user preferences, and separating UI configuration from hardware parameter updates. Updated the PowerDisplay Settings UI with warnings, confirmation dialogs, and better VCP capabilities formatting.
Removed legacy IPC code in favor of event-driven settings updates. Conducted general code cleanup, improving logging, error handling, and documentation for maintainability.
2025-11-14 16:45:22 +08:00
|
|
|
Logger.LogError($"[MonitorManager] No controller available for monitor {monitorId}");
|
2025-10-20 16:22:47 +08:00
|
|
|
return MonitorOperationResult.Failure("No controller available for this monitor");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var result = await operation(controller, monitor, value, cancellationToken);
|
|
|
|
|
|
|
|
|
|
if (result.IsSuccess)
|
|
|
|
|
{
|
|
|
|
|
onSuccess(monitor, value);
|
|
|
|
|
monitor.LastUpdate = DateTime.Now;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
monitor.IsAvailable = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
monitor.IsAvailable = false;
|
|
|
|
|
Logger.LogError($"[MonitorManager] Operation failed for {monitorId}: {ex.Message}");
|
|
|
|
|
return MonitorOperationResult.Failure($"Exception: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (!_disposed && disposing)
|
|
|
|
|
{
|
|
|
|
|
_discoveryLock?.Dispose();
|
|
|
|
|
|
2025-12-10 17:17:13 +08:00
|
|
|
// Release controllers
|
|
|
|
|
_ddcController?.Dispose();
|
|
|
|
|
_wmiController?.Dispose();
|
2025-10-20 16:22:47 +08:00
|
|
|
|
|
|
|
|
_monitors.Clear();
|
2025-11-24 23:36:25 +08:00
|
|
|
_monitorLookup.Clear();
|
2025-10-20 16:22:47 +08:00
|
|
|
_disposed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|