mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 03:07:56 +01:00
Improve monitor orientation sync for mirror/clone mode
Refactor orientation tracking to use property change notifications in the Monitor model. Add GetCurrentOrientation to DisplayRotationService and a RefreshAllOrientations method in MonitorManager to ensure all monitors sharing a GdiDeviceName are updated after rotation. Update MonitorViewModel to subscribe to orientation changes and forward them to the UI, and clean up event subscriptions on dispose. These changes ensure accurate orientation state in both UI and data models, especially for mirrored displays.
This commit is contained in:
@@ -24,6 +24,7 @@ namespace PowerDisplay.Common.Models
|
|||||||
private int _currentColorTemperature = 0x05; // Default to 6500K preset (VCP 0x14 value)
|
private int _currentColorTemperature = 0x05; // Default to 6500K preset (VCP 0x14 value)
|
||||||
private int _currentInputSource; // VCP 0x60 value
|
private int _currentInputSource; // VCP 0x60 value
|
||||||
private bool _isAvailable = true;
|
private bool _isAvailable = true;
|
||||||
|
private int _orientation;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets 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.
|
||||||
@@ -323,9 +324,21 @@ namespace PowerDisplay.Common.Models
|
|||||||
public string GdiDeviceName { get; set; } = string.Empty;
|
public string GdiDeviceName { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Orientation { get; set; }
|
public int Orientation
|
||||||
|
{
|
||||||
|
get => _orientation;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_orientation != value)
|
||||||
|
{
|
||||||
|
_orientation = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
int IMonitorData.MonitorNumber
|
int IMonitorData.MonitorNumber
|
||||||
|
|||||||
@@ -128,6 +128,38 @@ namespace PowerDisplay.Common.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get current orientation for a GDI device name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gdiDeviceName">GDI device name (e.g., "\\.\DISPLAY1")</param>
|
||||||
|
/// <returns>Current orientation (0-3), or -1 if query failed</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get human-readable error message for ChangeDisplaySettings result code.
|
/// Get human-readable error message for ChangeDisplaySettings result code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -296,6 +296,8 @@ namespace PowerDisplay.Helpers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set rotation/orientation for a monitor.
|
/// Set rotation/orientation for a monitor.
|
||||||
/// Uses Windows ChangeDisplaySettingsEx API (not DDC/CI).
|
/// 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).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="monitorId">Monitor ID</param>
|
/// <param name="monitorId">Monitor ID</param>
|
||||||
/// <param name="orientation">Orientation: 0=normal, 1=90°, 2=180°, 3=270°</param>
|
/// <param name="orientation">Orientation: 0=normal, 1=90°, 2=180°, 3=270°</param>
|
||||||
@@ -316,8 +318,9 @@ namespace PowerDisplay.Helpers
|
|||||||
|
|
||||||
if (result.IsSuccess)
|
if (result.IsSuccess)
|
||||||
{
|
{
|
||||||
monitor.Orientation = orientation;
|
// Refresh orientation for all monitors - rotation affects the GdiDeviceName (display source),
|
||||||
monitor.LastUpdate = DateTime.Now;
|
// and in mirror mode multiple monitors may share the same GdiDeviceName
|
||||||
|
RefreshAllOrientations();
|
||||||
Logger.LogInfo($"[MonitorManager] SetRotation: Successfully set {monitorId} to orientation {orientation}");
|
Logger.LogInfo($"[MonitorManager] SetRotation: Successfully set {monitorId} to orientation {orientation}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -328,6 +331,30 @@ namespace PowerDisplay.Helpers
|
|||||||
return Task.FromResult(result);
|
return Task.FromResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get monitor by ID. Uses dictionary lookup for O(1) performance.
|
/// Get monitor by ID. Uses dictionary lookup for O(1) performance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -239,6 +239,9 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
_mainViewModel.PropertyChanged += OnMainViewModelPropertyChanged;
|
_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
|
// Initialize Show properties based on hardware capabilities
|
||||||
_showContrast = monitor.SupportsContrast;
|
_showContrast = monitor.SupportsContrast;
|
||||||
_showVolume = monitor.SupportsVolume;
|
_showVolume = monitor.SupportsVolume;
|
||||||
@@ -401,7 +404,9 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
public bool IsRotation3 => CurrentRotation == 3;
|
public bool IsRotation3 => CurrentRotation == 3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="orientation">Orientation: 0=normal, 1=90°, 2=180°, 3=270°</param>
|
/// <param name="orientation">Orientation: 0=normal, 1=90°, 2=180°, 3=270°</param>
|
||||||
public async Task SetRotationAsync(int orientation)
|
public async Task SetRotationAsync(int orientation)
|
||||||
@@ -427,13 +432,6 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
|
|
||||||
if (result.IsSuccess)
|
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}");
|
Logger.LogInfo($"[{Id}] Rotation set successfully to {orientation}");
|
||||||
}
|
}
|
||||||
else
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// Unsubscribe from MainViewModel events
|
// Unsubscribe from MainViewModel events
|
||||||
@@ -693,6 +706,9 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
|||||||
_mainViewModel.PropertyChanged -= OnMainViewModelPropertyChanged;
|
_mainViewModel.PropertyChanged -= OnMainViewModelPropertyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unsubscribe from underlying Monitor events
|
||||||
|
_monitor.PropertyChanged -= OnMonitorPropertyChanged;
|
||||||
|
|
||||||
// Dispose all debouncers
|
// Dispose all debouncers
|
||||||
_brightnessDebouncer?.Dispose();
|
_brightnessDebouncer?.Dispose();
|
||||||
_contrastDebouncer?.Dispose();
|
_contrastDebouncer?.Dispose();
|
||||||
|
|||||||
Reference in New Issue
Block a user