mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Add DisplayChangeWatcher for monitor hot-plug detection
Introduced the `DisplayChangeWatcher` component to detect monitor connect/disconnect events using the WinRT `DeviceWatcher` API. Implemented 1-second debouncing to coalesce rapid changes and trigger a `DisplayChanged` event for refreshing the monitor list. Integrated `DisplayChangeWatcher` into `MainViewModel`, adding lifecycle management methods (`StartDisplayWatching`, `StopDisplayWatching`) and handling the `DisplayChanged` event to refresh monitors dynamically. Updated `Dispose` in `MainViewModel` to ensure proper cleanup of the `DisplayChangeWatcher`. Enhanced logging and added detailed comments for better traceability. Modified `design.md` to document the new component, updated flowcharts, and marked "Monitor Hot-Plug" as implemented. Reflected changes in the `PowerDisplay` directory structure.
This commit is contained in:
@@ -173,28 +173,51 @@ src/modules/powerdisplay/
|
|||||||
│ │ └── IMonitorController.cs # Controller abstraction
|
│ │ └── IMonitorController.cs # Controller abstraction
|
||||||
│ ├── Models/
|
│ ├── Models/
|
||||||
│ │ ├── Monitor.cs # Runtime monitor data
|
│ │ ├── Monitor.cs # Runtime monitor data
|
||||||
|
│ │ ├── MonitorOperationResult.cs # Operation result enum
|
||||||
│ │ ├── PowerDisplayProfile.cs # Profile definition
|
│ │ ├── PowerDisplayProfile.cs # Profile definition
|
||||||
|
│ │ ├── PowerDisplayProfiles.cs # Profile collection
|
||||||
│ │ └── ProfileMonitorSetting.cs # Per-monitor settings
|
│ │ └── ProfileMonitorSetting.cs # Per-monitor settings
|
||||||
│ ├── Services/
|
│ ├── Services/
|
||||||
│ │ ├── ProfileService.cs # Profile persistence
|
│ │ ├── LightSwitchListener.cs # Theme change listener
|
||||||
│ │ ├── MonitorStateManager.cs # State persistence
|
│ │ ├── MonitorStateManager.cs # State persistence (debounced)
|
||||||
│ │ └── LightSwitchListener.cs # Theme change listener
|
│ │ └── ProfileService.cs # Profile persistence
|
||||||
│ └── Utils/
|
│ └── Utils/
|
||||||
|
│ ├── ColorTemperatureHelper.cs # Color temp utilities
|
||||||
│ ├── MccsCapabilitiesParser.cs # DDC/CI capabilities parser
|
│ ├── MccsCapabilitiesParser.cs # DDC/CI capabilities parser
|
||||||
│ └── ColorTemperatureHelper.cs
|
│ └── VcpCapabilities.cs # VCP capabilities model
|
||||||
│
|
│
|
||||||
├── PowerDisplay/ # WinUI 3 application
|
├── PowerDisplay/ # WinUI 3 application
|
||||||
│ ├── Core/
|
│ ├── Assets/ # App icons and images
|
||||||
│ │ └── MonitorManager.cs # Discovery orchestrator
|
│ ├── Common/
|
||||||
|
│ │ ├── Debouncer/
|
||||||
|
│ │ │ └── SimpleDebouncer.cs # Slider input debouncing
|
||||||
|
│ │ └── Models/
|
||||||
|
│ │ └── Monitor.cs # UI-layer monitor model
|
||||||
|
│ ├── Converters/ # XAML value converters
|
||||||
|
│ ├── Helpers/
|
||||||
|
│ │ ├── DisplayChangeWatcher.cs # Monitor hot-plug detection (WinRT DeviceWatcher)
|
||||||
|
│ │ ├── DisplayRotationService.cs # Display rotation control
|
||||||
|
│ │ ├── MonitorManager.cs # Discovery orchestrator
|
||||||
|
│ │ ├── NativeMethodsHelper.cs # Window positioning
|
||||||
|
│ │ ├── TrayIconService.cs # System tray integration
|
||||||
|
│ │ └── WindowHelpers.cs # Window utilities
|
||||||
|
│ ├── Strings/ # Localization resources
|
||||||
|
│ ├── Styles/ # Custom control styles
|
||||||
│ ├── ViewModels/
|
│ ├── ViewModels/
|
||||||
│ │ ├── MainViewModel.cs
|
│ │ ├── MainViewModel.cs # Main VM (partial class)
|
||||||
│ │ └── MonitorViewModel.cs
|
│ │ ├── MainViewModel.Monitors.cs # Monitor discovery methods
|
||||||
|
│ │ ├── MainViewModel.Profiles.cs # Profile management methods
|
||||||
|
│ │ ├── MainViewModel.Settings.cs # Settings persistence methods
|
||||||
|
│ │ └── MonitorViewModel.cs # Per-monitor VM
|
||||||
│ └── Views/
|
│ └── Views/
|
||||||
│ └── MainWindow.xaml
|
│ ├── MainWindow.xaml # Main UI window
|
||||||
|
│ └── MainWindow.xaml.cs
|
||||||
│
|
│
|
||||||
└── PowerDisplayModuleInterface/ # C++ DLL (module interface)
|
└── PowerDisplayModuleInterface/ # C++ DLL (module interface)
|
||||||
├── dllmain.cpp # PowertoyModuleIface impl
|
├── dllmain.cpp # PowertoyModuleIface impl
|
||||||
└── Constants.h # Module constants
|
├── Constants.h # Module constants
|
||||||
|
├── pch.h / pch.cpp # Precompiled headers
|
||||||
|
└── trace.h / trace.cpp # Telemetry tracing
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -221,6 +244,7 @@ flowchart TB
|
|||||||
MainViewModel
|
MainViewModel
|
||||||
MonitorViewModel
|
MonitorViewModel
|
||||||
MonitorManager
|
MonitorManager
|
||||||
|
DisplayChangeWatcher["DisplayChangeWatcher<br/>(Hot-Plug Detection)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph PowerDisplayLib["PowerDisplay.Lib"]
|
subgraph PowerDisplayLib["PowerDisplay.Lib"]
|
||||||
@@ -259,6 +283,7 @@ flowchart TB
|
|||||||
LightSwitchListener -.->|"ThemeChanged event"| MainViewModel
|
LightSwitchListener -.->|"ThemeChanged event"| MainViewModel
|
||||||
MainViewModel --> MonitorViewModel
|
MainViewModel --> MonitorViewModel
|
||||||
MonitorViewModel --> MonitorManager
|
MonitorViewModel --> MonitorManager
|
||||||
|
DisplayChangeWatcher -.->|"DisplayChanged event"| MainViewModel
|
||||||
|
|
||||||
%% App to Lib services
|
%% App to Lib services
|
||||||
MainViewModel --> ProfileService
|
MainViewModel --> ProfileService
|
||||||
@@ -287,6 +312,35 @@ flowchart TB
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### DisplayChangeWatcher - Monitor Hot-Plug Detection
|
||||||
|
|
||||||
|
The `DisplayChangeWatcher` component provides automatic detection of monitor connect/disconnect events using the WinRT DeviceWatcher API.
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Uses `DisplayMonitor.GetDeviceSelector()` to watch for display device changes
|
||||||
|
- Implements 1-second debouncing to coalesce rapid connect/disconnect events
|
||||||
|
- Triggers `DisplayChanged` event to notify `MainViewModel` for monitor list refresh
|
||||||
|
- Runs continuously after initial monitor discovery completes
|
||||||
|
|
||||||
|
**Implementation Details:**
|
||||||
|
```csharp
|
||||||
|
// Device selector for display monitors
|
||||||
|
string selector = DisplayMonitor.GetDeviceSelector();
|
||||||
|
_deviceWatcher = DeviceInformation.CreateWatcher(selector);
|
||||||
|
|
||||||
|
// Events monitored
|
||||||
|
_deviceWatcher.Added += OnDeviceAdded; // New monitor connected
|
||||||
|
_deviceWatcher.Removed += OnDeviceRemoved; // Monitor disconnected
|
||||||
|
_deviceWatcher.Updated += OnDeviceUpdated; // Monitor properties changed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debouncing Strategy:**
|
||||||
|
- Each device change event schedules a `DisplayChanged` event after 1 second
|
||||||
|
- Subsequent events within the debounce window cancel the previous timer
|
||||||
|
- This prevents excessive refreshes when multiple monitors change simultaneously
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### DDC/CI and WMI Interaction Architecture
|
### DDC/CI and WMI Interaction Architecture
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -620,7 +674,8 @@ flowchart TB
|
|||||||
|
|
||||||
InitLoop --> UpdateCollection["Update _monitors Collection"]
|
InitLoop --> UpdateCollection["Update _monitors Collection"]
|
||||||
UpdateCollection --> FireEvent["Fire MonitorsChanged Event"]
|
UpdateCollection --> FireEvent["Fire MonitorsChanged Event"]
|
||||||
FireEvent --> End([Discovery Complete])
|
FireEvent --> StartWatcher["Start DisplayChangeWatcher"]
|
||||||
|
StartWatcher --> End([Discovery Complete])
|
||||||
|
|
||||||
style ParallelDiscover fill:#e3f2fd
|
style ParallelDiscover fill:#e3f2fd
|
||||||
style InitLoop fill:#e8f5e9
|
style InitLoop fill:#e8f5e9
|
||||||
@@ -1058,11 +1113,12 @@ classDiagram
|
|||||||
|
|
||||||
1. **Hardware Cursor Brightness**: Support for displays with hardware cursor brightness
|
1. **Hardware Cursor Brightness**: Support for displays with hardware cursor brightness
|
||||||
2. **Multi-GPU Support**: Better handling of monitors across different GPUs
|
2. **Multi-GPU Support**: Better handling of monitors across different GPUs
|
||||||
3. **Monitor Hot-Plug**: Improved detection and recovery for monitor connect/disconnect
|
3. ~~**Monitor Hot-Plug**: Improved detection and recovery for monitor connect/disconnect~~ **Implemented** - `DisplayChangeWatcher` uses WinRT DeviceWatcher + DisplayMonitor API with 1-second debouncing
|
||||||
4. **Advanced Color Management**: Integration with Windows Color Management
|
4. **Advanced Color Management**: Integration with Windows Color Management
|
||||||
5. **Scheduled Profiles**: Time-based automatic profile switching (beyond LightSwitch)
|
5. **Scheduled Profiles**: Time-based automatic profile switching (beyond LightSwitch)
|
||||||
6. **Monitor Groups**: Ability to control multiple monitors as a single entity
|
6. **Monitor Groups**: Ability to control multiple monitors as a single entity
|
||||||
7. **Remote Control**: Network-based control for multi-system setups
|
7. **Remote Control**: Network-based control for multi-system setups
|
||||||
|
8. ~~**Display Rotation**: Control display orientation~~ **Implemented** - `DisplayRotationService` uses Windows ChangeDisplaySettingsEx API
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ManagedCommon;
|
||||||
|
using Microsoft.UI.Dispatching;
|
||||||
|
using Windows.Devices.Display;
|
||||||
|
using Windows.Devices.Enumeration;
|
||||||
|
|
||||||
|
namespace PowerDisplay.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Watches for display/monitor connection changes using WinRT DeviceWatcher.
|
||||||
|
/// Triggers DisplayChanged event when monitors are added, removed, or updated.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DisplayChangeWatcher : IDisposable
|
||||||
|
{
|
||||||
|
private readonly DispatcherQueue _dispatcherQueue;
|
||||||
|
private readonly TimeSpan _debounceDelay = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
private DeviceWatcher? _deviceWatcher;
|
||||||
|
private CancellationTokenSource? _debounceCts;
|
||||||
|
private bool _isRunning;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when display configuration changes (after debounce period).
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler? DisplayChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DisplayChangeWatcher"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dispatcherQueue">The dispatcher queue for UI thread marshalling.</param>
|
||||||
|
public DisplayChangeWatcher(DispatcherQueue dispatcherQueue)
|
||||||
|
{
|
||||||
|
_dispatcherQueue = dispatcherQueue ?? throw new ArgumentNullException(nameof(dispatcherQueue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the watcher is currently running.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRunning => _isRunning;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts watching for display changes.
|
||||||
|
/// </summary>
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
|
if (_isRunning)
|
||||||
|
{
|
||||||
|
Logger.LogDebug("[DisplayChangeWatcher] Already running, ignoring Start()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get the device selector for display monitors
|
||||||
|
string selector = DisplayMonitor.GetDeviceSelector();
|
||||||
|
Logger.LogInfo($"[DisplayChangeWatcher] Using device selector: {selector}");
|
||||||
|
|
||||||
|
// Create the device watcher
|
||||||
|
_deviceWatcher = DeviceInformation.CreateWatcher(selector);
|
||||||
|
|
||||||
|
// Subscribe to events
|
||||||
|
_deviceWatcher.Added += OnDeviceAdded;
|
||||||
|
_deviceWatcher.Removed += OnDeviceRemoved;
|
||||||
|
_deviceWatcher.Updated += OnDeviceUpdated;
|
||||||
|
_deviceWatcher.EnumerationCompleted += OnEnumerationCompleted;
|
||||||
|
_deviceWatcher.Stopped += OnWatcherStopped;
|
||||||
|
|
||||||
|
// Start watching
|
||||||
|
_deviceWatcher.Start();
|
||||||
|
_isRunning = true;
|
||||||
|
|
||||||
|
Logger.LogInfo("[DisplayChangeWatcher] Started watching for display changes");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"[DisplayChangeWatcher] Failed to start: {ex.Message}");
|
||||||
|
_isRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops watching for display changes.
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
if (!_isRunning || _deviceWatcher == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Cancel any pending debounce
|
||||||
|
CancelDebounce();
|
||||||
|
|
||||||
|
// Stop the watcher
|
||||||
|
_deviceWatcher.Stop();
|
||||||
|
|
||||||
|
Logger.LogInfo("[DisplayChangeWatcher] Stopped watching for display changes");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"[DisplayChangeWatcher] Error stopping watcher: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeviceAdded(DeviceWatcher sender, DeviceInformation args)
|
||||||
|
{
|
||||||
|
Logger.LogInfo($"[DisplayChangeWatcher] Display added: {args.Name} ({args.Id})");
|
||||||
|
ScheduleDisplayChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeviceRemoved(DeviceWatcher sender, DeviceInformationUpdate args)
|
||||||
|
{
|
||||||
|
Logger.LogInfo($"[DisplayChangeWatcher] Display removed: {args.Id}");
|
||||||
|
ScheduleDisplayChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeviceUpdated(DeviceWatcher sender, DeviceInformationUpdate args)
|
||||||
|
{
|
||||||
|
Logger.LogDebug($"[DisplayChangeWatcher] Display updated: {args.Id}");
|
||||||
|
|
||||||
|
// Only trigger refresh for significant updates, not every property change.
|
||||||
|
// For now, we'll skip updates to avoid excessive refreshes.
|
||||||
|
// The Added and Removed events are the primary triggers for monitor changes.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnumerationCompleted(DeviceWatcher sender, object args)
|
||||||
|
{
|
||||||
|
Logger.LogInfo("[DisplayChangeWatcher] Initial enumeration completed");
|
||||||
|
|
||||||
|
// Don't trigger refresh on initial enumeration - MainViewModel handles initial discovery
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWatcherStopped(DeviceWatcher sender, object args)
|
||||||
|
{
|
||||||
|
_isRunning = false;
|
||||||
|
Logger.LogInfo("[DisplayChangeWatcher] Watcher stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schedules a DisplayChanged event with debouncing.
|
||||||
|
/// Multiple rapid changes will only trigger one event after the debounce period.
|
||||||
|
/// </summary>
|
||||||
|
private void ScheduleDisplayChanged()
|
||||||
|
{
|
||||||
|
// Cancel any pending debounce
|
||||||
|
CancelDebounce();
|
||||||
|
|
||||||
|
// Create new cancellation token
|
||||||
|
_debounceCts = new CancellationTokenSource();
|
||||||
|
var token = _debounceCts.Token;
|
||||||
|
|
||||||
|
// Schedule the event after debounce delay
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(_debounceDelay, token);
|
||||||
|
|
||||||
|
if (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Dispatch to UI thread
|
||||||
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
Logger.LogInfo("[DisplayChangeWatcher] Triggering DisplayChanged event");
|
||||||
|
DisplayChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Debounce was cancelled by a newer event, this is expected
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"[DisplayChangeWatcher] Error in debounce task: {ex.Message}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelDebounce()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_debounceCts?.Cancel();
|
||||||
|
_debounceCts?.Dispose();
|
||||||
|
_debounceCts = null;
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// Already disposed, ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes resources used by the watcher.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
// Stop watching
|
||||||
|
Stop();
|
||||||
|
|
||||||
|
// Unsubscribe from events
|
||||||
|
if (_deviceWatcher != null)
|
||||||
|
{
|
||||||
|
_deviceWatcher.Added -= OnDeviceAdded;
|
||||||
|
_deviceWatcher.Removed -= OnDeviceRemoved;
|
||||||
|
_deviceWatcher.Updated -= OnDeviceUpdated;
|
||||||
|
_deviceWatcher.EnumerationCompleted -= OnEnumerationCompleted;
|
||||||
|
_deviceWatcher.Stopped -= OnWatcherStopped;
|
||||||
|
_deviceWatcher = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel debounce
|
||||||
|
CancelDebounce();
|
||||||
|
|
||||||
|
Logger.LogInfo("[DisplayChangeWatcher] Disposed");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,9 @@ public partial class MainViewModel
|
|||||||
IsScanning = false;
|
IsScanning = false;
|
||||||
IsInitialized = true;
|
IsInitialized = true;
|
||||||
|
|
||||||
|
// Start watching for display changes after initialization
|
||||||
|
StartDisplayWatching();
|
||||||
|
|
||||||
if (monitors.Count > 0)
|
if (monitors.Count > 0)
|
||||||
{
|
{
|
||||||
StatusText = $"Found {monitors.Count} monitors";
|
StatusText = $"Found {monitors.Count} monitors";
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
private readonly ISettingsUtils _settingsUtils;
|
private readonly ISettingsUtils _settingsUtils;
|
||||||
private readonly MonitorStateManager _stateManager;
|
private readonly MonitorStateManager _stateManager;
|
||||||
private readonly LightSwitchListener _lightSwitchListener;
|
private readonly LightSwitchListener _lightSwitchListener;
|
||||||
|
private readonly DisplayChangeWatcher _displayChangeWatcher;
|
||||||
|
|
||||||
private ObservableCollection<MonitorViewModel> _monitors;
|
private ObservableCollection<MonitorViewModel> _monitors;
|
||||||
private ObservableCollection<PowerDisplayProfile> _profiles;
|
private ObservableCollection<PowerDisplayProfile> _profiles;
|
||||||
@@ -78,6 +79,10 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
// Load profiles for quick apply feature
|
// Load profiles for quick apply feature
|
||||||
LoadProfiles();
|
LoadProfiles();
|
||||||
|
|
||||||
|
// Initialize display change watcher for auto-refresh on monitor plug/unplug
|
||||||
|
_displayChangeWatcher = new DisplayChangeWatcher(_dispatcherQueue);
|
||||||
|
_displayChangeWatcher.DisplayChanged += OnDisplayChanged;
|
||||||
|
|
||||||
// Start initial discovery
|
// Start initial discovery
|
||||||
_ = InitializeAsync(_cancellationTokenSource.Token);
|
_ = InitializeAsync(_cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
@@ -236,6 +241,7 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
_cancellationTokenSource?.Cancel();
|
_cancellationTokenSource?.Cancel();
|
||||||
|
|
||||||
// Dispose all resources safely (don't throw from Dispose)
|
// Dispose all resources safely (don't throw from Dispose)
|
||||||
|
SafeDispose(_displayChangeWatcher, "DisplayChangeWatcher");
|
||||||
SafeDispose(_lightSwitchListener, "LightSwitchListener");
|
SafeDispose(_lightSwitchListener, "LightSwitchListener");
|
||||||
|
|
||||||
// Dispose monitor view models
|
// Dispose monitor view models
|
||||||
@@ -304,4 +310,30 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
Logger.LogError($"[Profile] Failed to load profiles: {ex.Message}");
|
Logger.LogError($"[Profile] Failed to load profiles: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles display configuration changes detected by the DisplayChangeWatcher.
|
||||||
|
/// Triggers a monitor refresh to update the UI.
|
||||||
|
/// </summary>
|
||||||
|
private async void OnDisplayChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Logger.LogInfo("[MainViewModel] Display change detected, refreshing monitors...");
|
||||||
|
await RefreshMonitorsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts watching for display changes. Call after initialization is complete.
|
||||||
|
/// </summary>
|
||||||
|
public void StartDisplayWatching()
|
||||||
|
{
|
||||||
|
_displayChangeWatcher.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops watching for display changes.
|
||||||
|
/// </summary>
|
||||||
|
public void StopDisplayWatching()
|
||||||
|
{
|
||||||
|
_displayChangeWatcher.Stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user