diff --git a/src/modules/powerdisplay/PowerDisplay/Helpers/DisplayChangeWatcher.cs b/src/modules/powerdisplay/PowerDisplay/Helpers/DisplayChangeWatcher.cs index 07e949ef9c..06eafb30ca 100644 --- a/src/modules/powerdisplay/PowerDisplay/Helpers/DisplayChangeWatcher.cs +++ b/src/modules/powerdisplay/PowerDisplay/Helpers/DisplayChangeWatcher.cs @@ -25,6 +25,7 @@ public sealed partial class DisplayChangeWatcher : IDisposable private CancellationTokenSource? _debounceCts; private bool _isRunning; private bool _disposed; + private bool _initialEnumerationComplete; /// /// Event triggered when display configuration changes (after debounce period). @@ -74,9 +75,12 @@ public sealed partial class DisplayChangeWatcher : IDisposable _deviceWatcher.EnumerationCompleted += OnEnumerationCompleted; _deviceWatcher.Stopped += OnWatcherStopped; + // Reset state before starting (must be before Start() to avoid race) + _initialEnumerationComplete = false; + _isRunning = true; + // Start watching _deviceWatcher.Start(); - _isRunning = true; Logger.LogInfo("[DisplayChangeWatcher] Started watching for display changes"); } @@ -115,14 +119,36 @@ public sealed partial class DisplayChangeWatcher : IDisposable private void OnDeviceAdded(DeviceWatcher sender, DeviceInformation args) { - Logger.LogInfo($"[DisplayChangeWatcher] Display added: {args.Name} ({args.Id})"); - ScheduleDisplayChanged(); + // Dispatch to UI thread to ensure thread-safe state access + _dispatcherQueue.TryEnqueue(() => + { + // Ignore events during initial enumeration or after disposal + if (_disposed || !_initialEnumerationComplete) + { + Logger.LogDebug($"[DisplayChangeWatcher] Ignoring add: {args.Name} (disposed={_disposed}, enumComplete={_initialEnumerationComplete})"); + return; + } + + 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(); + // Dispatch to UI thread to ensure thread-safe state access + _dispatcherQueue.TryEnqueue(() => + { + // Ignore events during initial enumeration or after disposal + if (_disposed || !_initialEnumerationComplete) + { + Logger.LogDebug($"[DisplayChangeWatcher] Ignoring remove: {args.Id} (disposed={_disposed}, enumComplete={_initialEnumerationComplete})"); + return; + } + + Logger.LogInfo($"[DisplayChangeWatcher] Display removed: {args.Id}"); + ScheduleDisplayChanged(); + }); } private void OnDeviceUpdated(DeviceWatcher sender, DeviceInformationUpdate args) @@ -136,15 +162,23 @@ public sealed partial class DisplayChangeWatcher : IDisposable private void OnEnumerationCompleted(DeviceWatcher sender, object args) { - Logger.LogInfo("[DisplayChangeWatcher] Initial enumeration completed"); - - // Don't trigger refresh on initial enumeration - MainViewModel handles initial discovery + // Dispatch to UI thread to ensure thread-safe state access + _dispatcherQueue.TryEnqueue(() => + { + _initialEnumerationComplete = true; + Logger.LogInfo("[DisplayChangeWatcher] Initial enumeration completed, now responding to display changes"); + }); } private void OnWatcherStopped(DeviceWatcher sender, object args) { - _isRunning = false; - Logger.LogInfo("[DisplayChangeWatcher] Watcher stopped"); + // Dispatch to UI thread to ensure thread-safe state access + _dispatcherQueue.TryEnqueue(() => + { + _isRunning = false; + _initialEnumerationComplete = false; + Logger.LogInfo("[DisplayChangeWatcher] Watcher stopped"); + }); } /// diff --git a/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorListChangedEventArgs.cs b/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorListChangedEventArgs.cs deleted file mode 100644 index a2ee090fff..0000000000 --- a/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorListChangedEventArgs.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 PowerDisplay.Common.Models; - -namespace PowerDisplay.Helpers -{ - /// - /// Monitor list changed event arguments - /// - public class MonitorListChangedEventArgs : EventArgs - { - public IReadOnlyList AddedMonitors { get; } - - public IReadOnlyList RemovedMonitors { get; } - - public IReadOnlyList AllMonitors { get; } - - public MonitorListChangedEventArgs( - IReadOnlyList addedMonitors, - IReadOnlyList removedMonitors, - IReadOnlyList allMonitors) - { - AddedMonitors = addedMonitors; - RemovedMonitors = removedMonitors; - AllMonitors = allMonitors; - } - } -} diff --git a/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorManager.cs b/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorManager.cs index c0230c87cc..a17fbfb447 100644 --- a/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorManager.cs +++ b/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorManager.cs @@ -34,8 +34,6 @@ namespace PowerDisplay.Helpers public IReadOnlyList Monitors => _monitors.AsReadOnly(); - public event EventHandler? MonitorsChanged; - public MonitorManager() { // Initialize controllers @@ -313,16 +311,6 @@ namespace PowerDisplay.Helpers } // Trigger change events - var addedMonitors = newMonitors.Where(m => !oldMonitors.Any(o => o.Id == m.Id)).ToList(); - var removedMonitors = oldMonitors.Where(o => !newMonitors.Any(m => m.Id == o.Id)).ToList(); - - if (addedMonitors.Count > 0 || removedMonitors.Count > 0) - { - MonitorsChanged?.Invoke(this, new MonitorListChangedEventArgs( - addedMonitors.AsReadOnly(), - removedMonitors.AsReadOnly(), - _monitors.AsReadOnly())); - } } /// diff --git a/src/modules/powerdisplay/PowerDisplay/Helpers/TrayIconService.cs b/src/modules/powerdisplay/PowerDisplay/Helpers/TrayIconService.cs index abe98f8372..1ea5a15fff 100644 --- a/src/modules/powerdisplay/PowerDisplay/Helpers/TrayIconService.cs +++ b/src/modules/powerdisplay/PowerDisplay/Helpers/TrayIconService.cs @@ -24,7 +24,7 @@ namespace PowerDisplay.Helpers /// Handle to the window. /// The message. /// Additional message information. - /// Additional message information. + /// Additional message. /// The result of the message processing. internal delegate nint WndProcDelegate(nint hwnd, uint msg, nuint wParam, nint lParam); @@ -36,7 +36,6 @@ namespace PowerDisplay.Helpers private const uint WM_TRAY_ICON = PInvoke.WM_USER + 1; private readonly ISettingsUtils _settingsUtils; - private readonly Action _showWindowAction; private readonly Action _toggleWindowAction; private readonly Action _exitAction; private readonly Action _openSettingsAction; @@ -58,7 +57,6 @@ namespace PowerDisplay.Helpers Action openSettingsAction) { _settingsUtils = settingsUtils; - _showWindowAction = showWindowAction; _toggleWindowAction = toggleWindowAction; _exitAction = exitAction; _openSettingsAction = openSettingsAction; @@ -175,14 +173,7 @@ namespace PowerDisplay.Helpers } catch { - // Fallback if resource not found - return key switch - { - "AppName" => "PowerDisplay", - "TrayMenu_Settings" => "Settings", - "TrayMenu_Exit" => "Exit", - _ => key, - }; + return "unknown"; } } diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Monitors.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Monitors.cs index 41dffda9e6..5966f8fc27 100644 --- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Monitors.cs +++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.Monitors.cs @@ -200,55 +200,6 @@ public partial class MainViewModel } } - private void OnMonitorsChanged(object? sender, MonitorListChangedEventArgs e) - { - _dispatcherQueue.TryEnqueue(() => - { - // Load settings to check for hidden monitors - var settings = _settingsUtils.GetSettingsOrDefault(PowerDisplaySettings.ModuleName); - var hiddenMonitorIds = GetHiddenMonitorIds(settings); - - // Handle monitors being added or removed - if (e.AddedMonitors.Count > 0) - { - foreach (var monitor in e.AddedMonitors) - { - // Skip monitors that are marked as hidden - if (hiddenMonitorIds.Contains(monitor.Id)) - { - Logger.LogInfo($"[OnMonitorsChanged] Skipping hidden monitor (added): {monitor.Name} ({monitor.Id})"); - continue; - } - - var existingVm = GetMonitorViewModel(monitor.Id); - if (existingVm == null) - { - var vm = new MonitorViewModel(monitor, _monitorManager, this); - Monitors.Add(vm); - } - } - } - - if (e.RemovedMonitors.Count > 0) - { - foreach (var monitor in e.RemovedMonitors) - { - var vm = GetMonitorViewModel(monitor.Id); - if (vm != null) - { - Monitors.Remove(vm); - vm.Dispose(); - } - } - } - - StatusText = $"Monitor list updated ({Monitors.Count} total)"; - - // Note: SaveMonitorsToSettings() is called by UpdateMonitorList() after full scan completes - // to avoid double-firing the refresh event during re-scan operations - }); - } - private MonitorViewModel? GetMonitorViewModel(string monitorId) => Monitors.FirstOrDefault(vm => vm.Id == monitorId); diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs index 6015881dd7..224dfaf61f 100644 --- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs +++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MainViewModel.cs @@ -68,9 +68,6 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable // Initialize the monitor manager _monitorManager = new MonitorManager(); - // Subscribe to events - _monitorManager.MonitorsChanged += OnMonitorsChanged; - // Initialize and start LightSwitch integration listener _lightSwitchListener = new LightSwitchListener(); _lightSwitchListener.ThemeChanged += OnLightSwitchThemeChanged;