diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs index d864e4fb69..24981af567 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Models/Monitor.cs @@ -24,6 +24,7 @@ namespace PowerDisplay.Common.Models private int _currentColorTemperature = 0x05; // Default to 6500K preset (VCP 0x14 value) private int _currentInputSource; // VCP 0x60 value private bool _isAvailable = true; + private int _orientation; /// /// Gets or sets unique identifier for all purposes: UI lookups, IPC, persistent storage, and handle management. @@ -323,9 +324,21 @@ namespace PowerDisplay.Common.Models public string GdiDeviceName { get; set; } = string.Empty; /// - /// Gets or sets monitor orientation (0=0, 1=90, 2=180, 3=270) + /// Gets or sets monitor orientation (0=0, 1=90, 2=180, 3=270). + /// Fires PropertyChanged when value changes. /// - public int Orientation { get; set; } + public int Orientation + { + get => _orientation; + set + { + if (_orientation != value) + { + _orientation = value; + OnPropertyChanged(); + } + } + } /// int IMonitorData.MonitorNumber diff --git a/src/modules/powerdisplay/PowerDisplay.Lib/Services/DisplayRotationService.cs b/src/modules/powerdisplay/PowerDisplay.Lib/Services/DisplayRotationService.cs index 53887e09fd..5529e43c06 100644 --- a/src/modules/powerdisplay/PowerDisplay.Lib/Services/DisplayRotationService.cs +++ b/src/modules/powerdisplay/PowerDisplay.Lib/Services/DisplayRotationService.cs @@ -128,6 +128,38 @@ namespace PowerDisplay.Common.Services } } + /// + /// Get current orientation for a GDI device name. + /// + /// GDI device name (e.g., "\\.\DISPLAY1") + /// Current orientation (0-3), or -1 if query failed + public unsafe int GetCurrentOrientation(string gdiDeviceName) + { + if (string.IsNullOrEmpty(gdiDeviceName)) + { + return -1; + } + + try + { + DevMode devMode = default; + devMode.DmSize = (short)sizeof(DevMode); + + if (!EnumDisplaySettings(gdiDeviceName, EnumCurrentSettings, &devMode)) + { + Logger.LogDebug($"GetCurrentOrientation: EnumDisplaySettings failed for {gdiDeviceName}"); + return -1; + } + + return devMode.DmDisplayOrientation; + } + catch (Exception ex) + { + Logger.LogDebug($"GetCurrentOrientation: Exception for {gdiDeviceName}: {ex.Message}"); + return -1; + } + } + /// /// Get human-readable error message for ChangeDisplaySettings result code. /// diff --git a/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorManager.cs b/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorManager.cs index 2e0c6b8a7c..2f17731f54 100644 --- a/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorManager.cs +++ b/src/modules/powerdisplay/PowerDisplay/Helpers/MonitorManager.cs @@ -296,6 +296,8 @@ namespace PowerDisplay.Helpers /// /// Set rotation/orientation for a monitor. /// Uses Windows ChangeDisplaySettingsEx API (not DDC/CI). + /// After successful rotation, refreshes orientation for all monitors sharing the same GdiDeviceName + /// (important for mirror/clone mode where multiple monitors share one display source). /// /// Monitor ID /// Orientation: 0=normal, 1=90°, 2=180°, 3=270° @@ -316,8 +318,9 @@ namespace PowerDisplay.Helpers if (result.IsSuccess) { - monitor.Orientation = orientation; - monitor.LastUpdate = DateTime.Now; + // Refresh orientation for all monitors - rotation affects the GdiDeviceName (display source), + // and in mirror mode multiple monitors may share the same GdiDeviceName + RefreshAllOrientations(); Logger.LogInfo($"[MonitorManager] SetRotation: Successfully set {monitorId} to orientation {orientation}"); } else @@ -328,6 +331,30 @@ namespace PowerDisplay.Helpers return Task.FromResult(result); } + /// + /// Refresh orientation values for all monitors by querying current display settings. + /// This ensures all monitors reflect the actual system state, which is important + /// in mirror mode where multiple monitors share the same GdiDeviceName. + /// + public void RefreshAllOrientations() + { + foreach (var monitor in _monitors) + { + if (string.IsNullOrEmpty(monitor.GdiDeviceName)) + { + continue; + } + + var currentOrientation = _rotationService.GetCurrentOrientation(monitor.GdiDeviceName); + if (currentOrientation >= 0 && currentOrientation != monitor.Orientation) + { + Logger.LogDebug($"[MonitorManager] RefreshAllOrientations: {monitor.Id} orientation updated from {monitor.Orientation} to {currentOrientation}"); + monitor.Orientation = currentOrientation; + monitor.LastUpdate = DateTime.Now; + } + } + } + /// /// Get monitor by ID. Uses dictionary lookup for O(1) performance. /// diff --git a/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs b/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs index 68ae76c6d7..94221c8a4c 100644 --- a/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs +++ b/src/modules/powerdisplay/PowerDisplay/ViewModels/MonitorViewModel.cs @@ -239,6 +239,9 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable _mainViewModel.PropertyChanged += OnMainViewModelPropertyChanged; } + // Subscribe to underlying Monitor property changes (e.g., Orientation updates in mirror mode) + _monitor.PropertyChanged += OnMonitorPropertyChanged; + // Initialize Show properties based on hardware capabilities _showContrast = monitor.SupportsContrast; _showVolume = monitor.SupportsVolume; @@ -401,7 +404,9 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable public bool IsRotation3 => CurrentRotation == 3; /// - /// Set rotation/orientation for this monitor + /// Set rotation/orientation for this monitor. + /// Note: MonitorManager.SetRotationAsync will refresh all monitors' orientations after success, + /// which triggers PropertyChanged through OnMonitorPropertyChanged - no manual notification needed here. /// /// Orientation: 0=normal, 1=90°, 2=180°, 3=270° public async Task SetRotationAsync(int orientation) @@ -427,13 +432,6 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable if (result.IsSuccess) { - // Notify all rotation-related properties changed - OnPropertyChanged(nameof(CurrentRotation)); - OnPropertyChanged(nameof(IsRotation0)); - OnPropertyChanged(nameof(IsRotation1)); - OnPropertyChanged(nameof(IsRotation2)); - OnPropertyChanged(nameof(IsRotation3)); - Logger.LogInfo($"[{Id}] Rotation set successfully to {orientation}"); } else @@ -685,6 +683,21 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable } } + private void OnMonitorPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + // Forward Orientation changes from underlying Monitor to ViewModel properties + // This is important for mirror mode where MonitorManager.RefreshAllOrientations() + // updates multiple monitors sharing the same GdiDeviceName + if (e.PropertyName == nameof(Monitor.Orientation)) + { + OnPropertyChanged(nameof(CurrentRotation)); + OnPropertyChanged(nameof(IsRotation0)); + OnPropertyChanged(nameof(IsRotation1)); + OnPropertyChanged(nameof(IsRotation2)); + OnPropertyChanged(nameof(IsRotation3)); + } + } + public void Dispose() { // Unsubscribe from MainViewModel events @@ -693,6 +706,9 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable _mainViewModel.PropertyChanged -= OnMainViewModelPropertyChanged; } + // Unsubscribe from underlying Monitor events + _monitor.PropertyChanged -= OnMonitorPropertyChanged; + // Dispose all debouncers _brightnessDebouncer?.Dispose(); _contrastDebouncer?.Dispose();