mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Add display rotation feature with UI controls and settings integration
This commit is contained in:
@@ -325,5 +325,96 @@ namespace PowerDisplay.Common.Drivers
|
||||
/// The display is rotated 270 degrees (measured clockwise) from its natural orientation.
|
||||
/// </summary>
|
||||
public const int Dmdo270 = 3;
|
||||
|
||||
// ==================== DEVMODE field flags ====================
|
||||
|
||||
/// <summary>
|
||||
/// DmDisplayOrientation field is valid.
|
||||
/// </summary>
|
||||
public const int DmDisplayOrientation = 0x00000080;
|
||||
|
||||
/// <summary>
|
||||
/// DmPelsWidth field is valid.
|
||||
/// </summary>
|
||||
public const int DmPelsWidth = 0x00080000;
|
||||
|
||||
/// <summary>
|
||||
/// DmPelsHeight field is valid.
|
||||
/// </summary>
|
||||
public const int DmPelsHeight = 0x00100000;
|
||||
|
||||
// ==================== ChangeDisplaySettings flags ====================
|
||||
|
||||
/// <summary>
|
||||
/// The settings change is temporary. Not saved to registry.
|
||||
/// </summary>
|
||||
public const uint CdsUpdateregistry = 0x00000001;
|
||||
|
||||
/// <summary>
|
||||
/// Test the graphics mode but don't actually set it.
|
||||
/// </summary>
|
||||
public const uint CdsTest = 0x00000002;
|
||||
|
||||
/// <summary>
|
||||
/// The mode is fullscreen.
|
||||
/// </summary>
|
||||
public const uint CdsFullscreen = 0x00000004;
|
||||
|
||||
/// <summary>
|
||||
/// The settings apply to all users.
|
||||
/// </summary>
|
||||
public const uint CdsGlobal = 0x00000008;
|
||||
|
||||
/// <summary>
|
||||
/// Set the primary display.
|
||||
/// </summary>
|
||||
public const uint CdsSetPrimary = 0x00000010;
|
||||
|
||||
/// <summary>
|
||||
/// Reset the mode after a dynamic mode change.
|
||||
/// </summary>
|
||||
public const uint CdsReset = 0x40000000;
|
||||
|
||||
/// <summary>
|
||||
/// Don't reset the mode.
|
||||
/// </summary>
|
||||
public const uint CdsNoreset = 0x10000000;
|
||||
|
||||
// ==================== ChangeDisplaySettings result codes ====================
|
||||
|
||||
/// <summary>
|
||||
/// The settings change was successful.
|
||||
/// </summary>
|
||||
public const int DispChangeSuccessful = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The computer must be restarted for the graphics mode to work.
|
||||
/// </summary>
|
||||
public const int DispChangeRestart = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The display driver failed the specified graphics mode.
|
||||
/// </summary>
|
||||
public const int DispChangeFailed = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The graphics mode is not supported.
|
||||
/// </summary>
|
||||
public const int DispChangeBadmode = -2;
|
||||
|
||||
/// <summary>
|
||||
/// Unable to write settings to the registry.
|
||||
/// </summary>
|
||||
public const int DispChangeNotupdated = -3;
|
||||
|
||||
/// <summary>
|
||||
/// An invalid set of flags was passed in.
|
||||
/// </summary>
|
||||
public const int DispChangeBadflags = -4;
|
||||
|
||||
/// <summary>
|
||||
/// An invalid parameter was passed in.
|
||||
/// </summary>
|
||||
public const int DispChangeBadparam = -5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,14 @@ namespace PowerDisplay.Common.Drivers
|
||||
int iModeNum,
|
||||
DevMode* lpDevMode);
|
||||
|
||||
[LibraryImport("user32.dll", EntryPoint = "ChangeDisplaySettingsExW", StringMarshalling = StringMarshalling.Utf16)]
|
||||
internal static unsafe partial int ChangeDisplaySettingsEx(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string? lpszDeviceName,
|
||||
DevMode* lpDevMode,
|
||||
IntPtr hwnd,
|
||||
uint dwflags,
|
||||
IntPtr lParam);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
internal static partial IntPtr MonitorFromWindow(
|
||||
IntPtr hwnd,
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// 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 ManagedCommon;
|
||||
using PowerDisplay.Common.Models;
|
||||
using static PowerDisplay.Common.Drivers.NativeConstants;
|
||||
using static PowerDisplay.Common.Drivers.PInvoke;
|
||||
|
||||
using DevMode = PowerDisplay.Common.Drivers.DevMode;
|
||||
|
||||
namespace PowerDisplay.Common.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for controlling display rotation/orientation.
|
||||
/// Uses ChangeDisplaySettingsEx API to change display orientation.
|
||||
/// </summary>
|
||||
public class DisplayRotationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Set display rotation for a specific monitor.
|
||||
/// </summary>
|
||||
/// <param name="monitorNumber">Monitor number (1, 2, 3...)</param>
|
||||
/// <param name="newOrientation">New orientation: 0=normal, 1=90°, 2=180°, 3=270°</param>
|
||||
/// <returns>Operation result</returns>
|
||||
public MonitorOperationResult SetRotation(int monitorNumber, int newOrientation)
|
||||
{
|
||||
if (monitorNumber <= 0)
|
||||
{
|
||||
return MonitorOperationResult.Failure("Invalid monitor number");
|
||||
}
|
||||
|
||||
if (newOrientation < 0 || newOrientation > 3)
|
||||
{
|
||||
return MonitorOperationResult.Failure($"Invalid orientation value: {newOrientation}. Must be 0-3.");
|
||||
}
|
||||
|
||||
// Construct adapter name from monitor number (e.g., 1 -> "\\.\DISPLAY1")
|
||||
string adapterName = $"\\\\.\\DISPLAY{monitorNumber}";
|
||||
|
||||
return SetRotationByAdapterName(adapterName, newOrientation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set display rotation by adapter name.
|
||||
/// </summary>
|
||||
/// <param name="adapterName">Adapter name (e.g., "\\.\DISPLAY1")</param>
|
||||
/// <param name="newOrientation">New orientation: 0=normal, 1=90°, 2=180°, 3=270°</param>
|
||||
/// <returns>Operation result</returns>
|
||||
public unsafe MonitorOperationResult SetRotationByAdapterName(string adapterName, int newOrientation)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInfo($"SetRotation: Setting {adapterName} to orientation {newOrientation}");
|
||||
|
||||
// 1. Get current display settings
|
||||
DevMode devMode = default;
|
||||
devMode.DmSize = (short)sizeof(DevMode);
|
||||
|
||||
if (!EnumDisplaySettings(adapterName, EnumCurrentSettings, &devMode))
|
||||
{
|
||||
var error = GetLastError();
|
||||
Logger.LogError($"SetRotation: EnumDisplaySettings failed for {adapterName}, error: {error}");
|
||||
return MonitorOperationResult.Failure($"Failed to get current display settings for {adapterName}", (int)error);
|
||||
}
|
||||
|
||||
int currentOrientation = devMode.DmDisplayOrientation;
|
||||
Logger.LogDebug($"SetRotation: Current orientation={currentOrientation}, target={newOrientation}");
|
||||
|
||||
// If already at target orientation, return success
|
||||
if (currentOrientation == newOrientation)
|
||||
{
|
||||
Logger.LogDebug($"SetRotation: Already at target orientation {newOrientation}");
|
||||
return MonitorOperationResult.Success();
|
||||
}
|
||||
|
||||
// 2. Determine if we need to swap width and height
|
||||
// When switching between landscape (0°/180°) and portrait (90°/270°), swap dimensions
|
||||
bool currentIsLandscape = currentOrientation == DmdoDefault || currentOrientation == Dmdo180;
|
||||
bool newIsLandscape = newOrientation == DmdoDefault || newOrientation == Dmdo180;
|
||||
|
||||
if (currentIsLandscape != newIsLandscape)
|
||||
{
|
||||
// Swap width and height
|
||||
int temp = devMode.DmPelsWidth;
|
||||
devMode.DmPelsWidth = devMode.DmPelsHeight;
|
||||
devMode.DmPelsHeight = temp;
|
||||
Logger.LogDebug($"SetRotation: Swapped dimensions to {devMode.DmPelsWidth}x{devMode.DmPelsHeight}");
|
||||
}
|
||||
|
||||
// 3. Set new orientation
|
||||
devMode.DmDisplayOrientation = newOrientation;
|
||||
devMode.DmFields = DmDisplayOrientation | DmPelsWidth | DmPelsHeight;
|
||||
|
||||
// 4. Test the settings first using CDS_TEST flag
|
||||
int testResult = ChangeDisplaySettingsEx(adapterName, &devMode, IntPtr.Zero, CdsTest, IntPtr.Zero);
|
||||
if (testResult != DispChangeSuccessful)
|
||||
{
|
||||
string errorMsg = GetChangeDisplaySettingsErrorMessage(testResult);
|
||||
Logger.LogError($"SetRotation: Test failed for {adapterName}: {errorMsg}");
|
||||
return MonitorOperationResult.Failure($"Display settings test failed: {errorMsg}", testResult);
|
||||
}
|
||||
|
||||
Logger.LogDebug($"SetRotation: Test passed, applying settings...");
|
||||
|
||||
// 5. Apply the settings (without CDS_UPDATEREGISTRY to make it temporary)
|
||||
int result = ChangeDisplaySettingsEx(adapterName, &devMode, IntPtr.Zero, 0, IntPtr.Zero);
|
||||
if (result != DispChangeSuccessful)
|
||||
{
|
||||
string errorMsg = GetChangeDisplaySettingsErrorMessage(result);
|
||||
Logger.LogError($"SetRotation: Apply failed for {adapterName}: {errorMsg}");
|
||||
return MonitorOperationResult.Failure($"Failed to apply display settings: {errorMsg}", result);
|
||||
}
|
||||
|
||||
Logger.LogInfo($"SetRotation: Successfully set {adapterName} to orientation {newOrientation}");
|
||||
return MonitorOperationResult.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"SetRotation: Exception for {adapterName}: {ex.Message}");
|
||||
return MonitorOperationResult.Failure($"Exception while setting rotation: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current rotation for a specific monitor.
|
||||
/// </summary>
|
||||
/// <param name="monitorNumber">Monitor number (1, 2, 3...)</param>
|
||||
/// <returns>Current orientation (0-3), or -1 if failed</returns>
|
||||
public int GetCurrentRotation(int monitorNumber)
|
||||
{
|
||||
if (monitorNumber <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
string adapterName = $"\\\\.\\DISPLAY{monitorNumber}";
|
||||
return GetCurrentRotationByAdapterName(adapterName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current rotation by adapter name.
|
||||
/// </summary>
|
||||
/// <param name="adapterName">Adapter name (e.g., "\\.\DISPLAY1")</param>
|
||||
/// <returns>Current orientation (0-3), or -1 if failed</returns>
|
||||
public unsafe int GetCurrentRotationByAdapterName(string adapterName)
|
||||
{
|
||||
try
|
||||
{
|
||||
DevMode devMode = default;
|
||||
devMode.DmSize = (short)sizeof(DevMode);
|
||||
|
||||
if (EnumDisplaySettings(adapterName, EnumCurrentSettings, &devMode))
|
||||
{
|
||||
return devMode.DmDisplayOrientation;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"GetCurrentRotation: Exception for {adapterName}: {ex.Message}");
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get human-readable error message for ChangeDisplaySettings result code.
|
||||
/// </summary>
|
||||
private static string GetChangeDisplaySettingsErrorMessage(int resultCode)
|
||||
{
|
||||
return resultCode switch
|
||||
{
|
||||
DispChangeSuccessful => "Success",
|
||||
DispChangeRestart => "Computer must be restarted",
|
||||
DispChangeFailed => "Display driver failed the specified graphics mode",
|
||||
DispChangeBadmode => "Graphics mode is not supported",
|
||||
DispChangeNotupdated => "Unable to write settings to registry",
|
||||
DispChangeBadflags => "Invalid flags",
|
||||
DispChangeBadparam => "Invalid parameter",
|
||||
_ => $"Unknown error code: {resultCode}",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ using PowerDisplay.Common.Drivers.DDC;
|
||||
using PowerDisplay.Common.Drivers.WMI;
|
||||
using PowerDisplay.Common.Interfaces;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Services;
|
||||
using PowerDisplay.Common.Utils;
|
||||
using PowerDisplay.Core.Interfaces;
|
||||
using Monitor = PowerDisplay.Common.Models.Monitor;
|
||||
@@ -29,6 +30,7 @@ namespace PowerDisplay.Core
|
||||
private readonly Dictionary<string, Monitor> _monitorLookup = new();
|
||||
private readonly List<IMonitorController> _controllers = new();
|
||||
private readonly SemaphoreSlim _discoveryLock = new(1, 1);
|
||||
private readonly DisplayRotationService _rotationService = new();
|
||||
private bool _disposed;
|
||||
|
||||
public IReadOnlyList<Monitor> Monitors => _monitors.AsReadOnly();
|
||||
@@ -486,6 +488,46 @@ namespace PowerDisplay.Core
|
||||
(mon, val) => mon.CurrentInputSource = val,
|
||||
cancellationToken);
|
||||
|
||||
/// <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"));
|
||||
}
|
||||
|
||||
if (monitor.MonitorNumber <= 0)
|
||||
{
|
||||
Logger.LogError($"[MonitorManager] SetRotation: Invalid monitor number for {monitorId}");
|
||||
return Task.FromResult(MonitorOperationResult.Failure("Invalid monitor number"));
|
||||
}
|
||||
|
||||
// Rotation uses Windows display settings API, not DDC/CI controller
|
||||
var result = _rotationService.SetRotation(monitor.MonitorNumber, orientation);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize input source for a monitor (async operation)
|
||||
/// </summary>
|
||||
|
||||
@@ -264,6 +264,72 @@
|
||||
ValueChanged="Slider_ValueChanged"
|
||||
Value="{x:Bind Volume, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Rotation Controls -->
|
||||
<Grid
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Visibility="{x:Bind ConvertBoolToVisibility(ShowRotation), Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16" />
|
||||
<ColumnDefinition Width="16" />
|
||||
<!-- Spacing -->
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<FontIcon
|
||||
x:Uid="RotationTooltip"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontSize="16"
|
||||
Glyph="" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
Orientation="Horizontal"
|
||||
Spacing="4">
|
||||
<!-- Normal (0°) -->
|
||||
<ToggleButton
|
||||
x:Uid="RotateNormalTooltip"
|
||||
Click="RotationButton_Click"
|
||||
DataContext="{x:Bind}"
|
||||
IsChecked="{x:Bind IsRotation0, Mode=OneWay}"
|
||||
IsEnabled="{x:Bind IsInteractionEnabled, Mode=OneWay}"
|
||||
Tag="0">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
</ToggleButton>
|
||||
<!-- Left (270°) -->
|
||||
<ToggleButton
|
||||
x:Uid="RotateLeftTooltip"
|
||||
Click="RotationButton_Click"
|
||||
DataContext="{x:Bind}"
|
||||
IsChecked="{x:Bind IsRotation3, Mode=OneWay}"
|
||||
IsEnabled="{x:Bind IsInteractionEnabled, Mode=OneWay}"
|
||||
Tag="3">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
</ToggleButton>
|
||||
<!-- Right (90°) -->
|
||||
<ToggleButton
|
||||
x:Uid="RotateRightTooltip"
|
||||
Click="RotationButton_Click"
|
||||
DataContext="{x:Bind}"
|
||||
IsChecked="{x:Bind IsRotation1, Mode=OneWay}"
|
||||
IsEnabled="{x:Bind IsInteractionEnabled, Mode=OneWay}"
|
||||
Tag="1">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
</ToggleButton>
|
||||
<!-- Inverted (180°) -->
|
||||
<ToggleButton
|
||||
x:Uid="RotateInvertedTooltip"
|
||||
Click="RotationButton_Click"
|
||||
DataContext="{x:Bind}"
|
||||
IsChecked="{x:Bind IsRotation2, Mode=OneWay}"
|
||||
IsEnabled="{x:Bind IsInteractionEnabled, Mode=OneWay}"
|
||||
Tag="2">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -768,6 +768,43 @@ namespace PowerDisplay
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation button click handler - changes monitor orientation
|
||||
/// </summary>
|
||||
private async void RotationButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not Microsoft.UI.Xaml.Controls.Primitives.ToggleButton toggleButton)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the orientation from the Tag
|
||||
if (toggleButton.Tag is not string tagStr || !int.TryParse(tagStr, out int orientation))
|
||||
{
|
||||
Logger.LogWarning("[UI] RotationButton_Click: Invalid Tag");
|
||||
return;
|
||||
}
|
||||
|
||||
var monitorVm = toggleButton.DataContext as MonitorViewModel;
|
||||
if (monitorVm == null)
|
||||
{
|
||||
Logger.LogWarning("[UI] RotationButton_Click: Could not find MonitorViewModel");
|
||||
return;
|
||||
}
|
||||
|
||||
// If clicking the current orientation, restore the checked state and do nothing
|
||||
if (monitorVm.CurrentRotation == orientation)
|
||||
{
|
||||
toggleButton.IsChecked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"[UI] RotationButton_Click: Setting rotation for {monitorVm.Name} to {orientation}");
|
||||
|
||||
// Set the rotation
|
||||
await monitorVm.SetRotationAsync(orientation);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_viewModel?.Dispose();
|
||||
|
||||
@@ -44,6 +44,21 @@
|
||||
</data>
|
||||
<data name="VolumeTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Volume</value>
|
||||
</data>
|
||||
<data name="RotationTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Rotation</value>
|
||||
</data>
|
||||
<data name="RotateNormalTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Normal (0°)</value>
|
||||
</data>
|
||||
<data name="RotateLeftTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Rotate left (270°)</value>
|
||||
</data>
|
||||
<data name="RotateRightTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Rotate right (90°)</value>
|
||||
</data>
|
||||
<data name="RotateInvertedTooltip.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Inverted (180°)</value>
|
||||
</data>
|
||||
<data name="VolumeAutomation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Volume</value>
|
||||
|
||||
@@ -452,11 +452,12 @@ public partial class MainViewModel
|
||||
|
||||
if (monitorSettings != null)
|
||||
{
|
||||
Logger.LogInfo($"[Startup] Applying feature visibility for Hardware ID '{monitorVm.HardwareId}': Contrast={monitorSettings.EnableContrast}, Volume={monitorSettings.EnableVolume}, InputSource={monitorSettings.EnableInputSource}");
|
||||
Logger.LogInfo($"[Startup] Applying feature visibility for Hardware ID '{monitorVm.HardwareId}': Contrast={monitorSettings.EnableContrast}, Volume={monitorSettings.EnableVolume}, InputSource={monitorSettings.EnableInputSource}, Rotation={monitorSettings.EnableRotation}");
|
||||
|
||||
monitorVm.ShowContrast = monitorSettings.EnableContrast;
|
||||
monitorVm.ShowVolume = monitorSettings.EnableVolume;
|
||||
monitorVm.ShowInputSource = monitorSettings.EnableInputSource;
|
||||
monitorVm.ShowRotation = monitorSettings.EnableRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -608,6 +609,7 @@ public partial class MainViewModel
|
||||
monitorInfo.EnableContrast = existingMonitor.EnableContrast;
|
||||
monitorInfo.EnableVolume = existingMonitor.EnableVolume;
|
||||
monitorInfo.EnableInputSource = existingMonitor.EnableInputSource;
|
||||
monitorInfo.EnableRotation = existingMonitor.EnableRotation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
private bool _showContrast;
|
||||
private bool _showVolume;
|
||||
private bool _showInputSource;
|
||||
private bool _showRotation;
|
||||
|
||||
/// <summary>
|
||||
/// Updates a property value directly without triggering hardware updates.
|
||||
@@ -356,6 +357,87 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to show rotation controls (controlled by Settings UI, default false)
|
||||
/// </summary>
|
||||
public bool ShowRotation
|
||||
{
|
||||
get => _showRotation;
|
||||
set
|
||||
{
|
||||
if (_showRotation != value)
|
||||
{
|
||||
_showRotation = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current rotation/orientation of the monitor (0=normal, 1=90°, 2=180°, 3=270°)
|
||||
/// </summary>
|
||||
public int CurrentRotation => _monitor.Orientation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the current rotation is 0° (normal/default)
|
||||
/// </summary>
|
||||
public bool IsRotation0 => CurrentRotation == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the current rotation is 90° (rotated right)
|
||||
/// </summary>
|
||||
public bool IsRotation1 => CurrentRotation == 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the current rotation is 180° (inverted)
|
||||
/// </summary>
|
||||
public bool IsRotation2 => CurrentRotation == 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the current rotation is 270° (rotated left)
|
||||
/// </summary>
|
||||
public bool IsRotation3 => CurrentRotation == 3;
|
||||
|
||||
/// <summary>
|
||||
/// Set rotation/orientation for this monitor
|
||||
/// </summary>
|
||||
/// <param name="orientation">Orientation: 0=normal, 1=90°, 2=180°, 3=270°</param>
|
||||
public async Task SetRotationAsync(int orientation)
|
||||
{
|
||||
// If already at this orientation, do nothing
|
||||
if (CurrentRotation == orientation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Logger.LogInfo($"[{HardwareId}] Setting rotation to {orientation}");
|
||||
|
||||
var result = await _monitorManager.SetRotationAsync(Id, orientation);
|
||||
|
||||
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($"[{HardwareId}] Rotation set successfully to {orientation}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning($"[{HardwareId}] Failed to set rotation: {result.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"[{HardwareId}] Exception setting rotation: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public int Brightness
|
||||
{
|
||||
get => _brightness;
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
private bool _enableContrast;
|
||||
private bool _enableVolume;
|
||||
private bool _enableInputSource;
|
||||
private bool _enableRotation;
|
||||
private string _capabilitiesRaw = string.Empty;
|
||||
private List<string> _vcpCodes = new List<string>();
|
||||
private List<VcpCodeDisplayInfo> _vcpCodesFormatted = new List<VcpCodeDisplayInfo>();
|
||||
@@ -414,6 +415,20 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("enableRotation")]
|
||||
public bool EnableRotation
|
||||
{
|
||||
get => _enableRotation;
|
||||
set
|
||||
{
|
||||
if (_enableRotation != value)
|
||||
{
|
||||
_enableRotation = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("capabilitiesRaw")]
|
||||
public string CapabilitiesRaw
|
||||
{
|
||||
@@ -792,6 +807,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
EnableContrast = other.EnableContrast;
|
||||
EnableVolume = other.EnableVolume;
|
||||
EnableInputSource = other.EnableInputSource;
|
||||
EnableRotation = other.EnableRotation;
|
||||
CapabilitiesRaw = other.CapabilitiesRaw;
|
||||
VcpCodes = other.VcpCodes;
|
||||
VcpCodesFormatted = other.VcpCodesFormatted;
|
||||
|
||||
@@ -177,6 +177,9 @@
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind SupportsInputSource, Mode=OneWay}">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_EnableInputSource" IsChecked="{x:Bind EnableInputSource, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_EnableRotation" IsChecked="{x:Bind EnableRotation, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<CheckBox x:Uid="PowerDisplay_Monitor_HideMonitor" IsChecked="{x:Bind IsHidden, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
@@ -5661,6 +5661,9 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<data name="PowerDisplay_Monitor_EnableInputSource.Content" xml:space="preserve">
|
||||
<value>Show input source control</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_EnableRotation.Content" xml:space="preserve">
|
||||
<value>Show rotation control</value>
|
||||
</data>
|
||||
<data name="PowerDisplay_Monitor_HideMonitor.Content" xml:space="preserve">
|
||||
<value>Hide monitor</value>
|
||||
</data>
|
||||
|
||||
Reference in New Issue
Block a user