mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
Refactor and enhance monitor management system
Refactored namespaces to improve modularity, including moving `PowerDisplay.Native` to `PowerDisplay.Common.Drivers`. Introduced the `IMonitorData` interface for better abstraction of monitor hardware data. Replaced `ColorTemperature` with `ColorTemperatureVcp` for precise VCP-based color temperature control, adding utilities for Kelvin conversion. Enhanced monitor state management with a new `MonitorStateFile` for JSON persistence and updated `MonitorStateManager` for debounced saves. Added `MonitorMatchingHelper` for consistent monitor identification and `ProfileHelper` for profile management operations. Refactored P/Invoke declarations into helper classes, updated UI bindings for `ColorTemperatureVcp`, and improved logging for better runtime visibility. Removed redundant code, added new utility classes (`MonitorValueConverter`, `MonitorMatchingHelper`), and ensured backward compatibility. These changes improve code organization, maintainability, and extensibility while aligning with hardware-level control standards.
This commit is contained in:
@@ -11,19 +11,18 @@ using ManagedCommon;
|
||||
using PowerDisplay.Common.Interfaces;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Utils;
|
||||
using PowerDisplay.Helpers;
|
||||
using static PowerDisplay.Native.NativeConstants;
|
||||
using static PowerDisplay.Native.NativeDelegates;
|
||||
using static PowerDisplay.Native.PInvoke;
|
||||
using static PowerDisplay.Common.Drivers.NativeConstants;
|
||||
using static PowerDisplay.Common.Drivers.NativeDelegates;
|
||||
using static PowerDisplay.Common.Drivers.PInvoke;
|
||||
using Monitor = PowerDisplay.Common.Models.Monitor;
|
||||
|
||||
// Type aliases matching Windows API naming conventions for better readability when working with native structures.
|
||||
// These uppercase aliases are used consistently throughout this file to match Win32 API documentation.
|
||||
using MONITORINFOEX = PowerDisplay.Native.MonitorInfoEx;
|
||||
using PHYSICAL_MONITOR = PowerDisplay.Native.PhysicalMonitor;
|
||||
using RECT = PowerDisplay.Native.Rect;
|
||||
using MONITORINFOEX = PowerDisplay.Common.Drivers.MonitorInfoEx;
|
||||
using PHYSICAL_MONITOR = PowerDisplay.Common.Drivers.PhysicalMonitor;
|
||||
using RECT = PowerDisplay.Common.Drivers.Rect;
|
||||
|
||||
namespace PowerDisplay.Native.DDC
|
||||
namespace PowerDisplay.Common.Drivers.DDC
|
||||
{
|
||||
/// <summary>
|
||||
/// DDC/CI monitor controller for controlling external monitors
|
||||
@@ -6,25 +6,25 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using ManagedCommon;
|
||||
using static PowerDisplay.Native.NativeConstants;
|
||||
using static PowerDisplay.Native.NativeDelegates;
|
||||
using static PowerDisplay.Native.PInvoke;
|
||||
using static PowerDisplay.Common.Drivers.NativeConstants;
|
||||
using static PowerDisplay.Common.Drivers.NativeDelegates;
|
||||
using static PowerDisplay.Common.Drivers.PInvoke;
|
||||
|
||||
// Type aliases for Windows API naming conventions compatibility
|
||||
using DISPLAY_DEVICE = PowerDisplay.Native.DisplayDevice;
|
||||
using DISPLAYCONFIG_DEVICE_INFO_HEADER = PowerDisplay.Native.DISPLAYCONFIG_DEVICE_INFO_HEADER;
|
||||
using DISPLAYCONFIG_MODE_INFO = PowerDisplay.Native.DISPLAYCONFIG_MODE_INFO;
|
||||
using DISPLAYCONFIG_PATH_INFO = PowerDisplay.Native.DISPLAYCONFIG_PATH_INFO;
|
||||
using DISPLAYCONFIG_TARGET_DEVICE_NAME = PowerDisplay.Native.DISPLAYCONFIG_TARGET_DEVICE_NAME;
|
||||
using LUID = PowerDisplay.Native.Luid;
|
||||
using MONITORINFOEX = PowerDisplay.Native.MonitorInfoEx;
|
||||
using PHYSICAL_MONITOR = PowerDisplay.Native.PhysicalMonitor;
|
||||
using RECT = PowerDisplay.Native.Rect;
|
||||
using DISPLAY_DEVICE = PowerDisplay.Common.Drivers.DisplayDevice;
|
||||
using DISPLAYCONFIG_DEVICE_INFO_HEADER = PowerDisplay.Common.Drivers.DISPLAYCONFIG_DEVICE_INFO_HEADER;
|
||||
using DISPLAYCONFIG_MODE_INFO = PowerDisplay.Common.Drivers.DISPLAYCONFIG_MODE_INFO;
|
||||
using DISPLAYCONFIG_PATH_INFO = PowerDisplay.Common.Drivers.DISPLAYCONFIG_PATH_INFO;
|
||||
using DISPLAYCONFIG_TARGET_DEVICE_NAME = PowerDisplay.Common.Drivers.DISPLAYCONFIG_TARGET_DEVICE_NAME;
|
||||
using LUID = PowerDisplay.Common.Drivers.Luid;
|
||||
using MONITORINFOEX = PowerDisplay.Common.Drivers.MonitorInfoEx;
|
||||
using PHYSICAL_MONITOR = PowerDisplay.Common.Drivers.PhysicalMonitor;
|
||||
using RECT = PowerDisplay.Common.Drivers.Rect;
|
||||
|
||||
#pragma warning disable SA1649 // File name should match first type name - Multiple related types for DDC/CI
|
||||
#pragma warning disable SA1402 // File may only contain a single type - Related DDC/CI types grouped together
|
||||
|
||||
namespace PowerDisplay.Native.DDC
|
||||
namespace PowerDisplay.Common.Drivers.DDC
|
||||
{
|
||||
/// <summary>
|
||||
/// Display device information class
|
||||
@@ -227,7 +227,7 @@ namespace PowerDisplay.Native.DDC
|
||||
/// Gets all monitor friendly names by enumerating display configurations
|
||||
/// </summary>
|
||||
/// <returns>Mapping of device path to friendly name</returns>
|
||||
public static Dictionary<string, string> GetAllMonitorFriendlyNames()
|
||||
public static unsafe Dictionary<string, string> GetAllMonitorFriendlyNames()
|
||||
{
|
||||
var friendlyNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -244,11 +244,17 @@ namespace PowerDisplay.Native.DDC
|
||||
var paths = new DISPLAYCONFIG_PATH_INFO[pathCount];
|
||||
var modes = new DISPLAYCONFIG_MODE_INFO[modeCount];
|
||||
|
||||
// Query display configuration
|
||||
result = QueryDisplayConfig(QdcOnlyActivePaths, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero);
|
||||
if (result != 0)
|
||||
// Query display configuration using fixed pointer
|
||||
fixed (DISPLAYCONFIG_PATH_INFO* pathsPtr = paths)
|
||||
{
|
||||
return friendlyNames;
|
||||
fixed (DISPLAYCONFIG_MODE_INFO* modesPtr = modes)
|
||||
{
|
||||
result = QueryDisplayConfig(QdcOnlyActivePaths, ref pathCount, pathsPtr, ref modeCount, modesPtr, IntPtr.Zero);
|
||||
if (result != 0)
|
||||
{
|
||||
return friendlyNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get friendly name for each path
|
||||
@@ -345,7 +351,7 @@ namespace PowerDisplay.Native.DDC
|
||||
/// Gets complete information for all monitors, including friendly name and hardware ID
|
||||
/// </summary>
|
||||
/// <returns>Dictionary containing monitor information</returns>
|
||||
public static Dictionary<string, MonitorDisplayInfo> GetAllMonitorDisplayInfo()
|
||||
public static unsafe Dictionary<string, MonitorDisplayInfo> GetAllMonitorDisplayInfo()
|
||||
{
|
||||
var monitorInfo = new Dictionary<string, MonitorDisplayInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -362,11 +368,17 @@ namespace PowerDisplay.Native.DDC
|
||||
var paths = new DISPLAYCONFIG_PATH_INFO[pathCount];
|
||||
var modes = new DISPLAYCONFIG_MODE_INFO[modeCount];
|
||||
|
||||
// Query display configuration
|
||||
result = QueryDisplayConfig(QdcOnlyActivePaths, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero);
|
||||
if (result != 0)
|
||||
// Query display configuration using fixed pointer
|
||||
fixed (DISPLAYCONFIG_PATH_INFO* pathsPtr = paths)
|
||||
{
|
||||
return monitorInfo;
|
||||
fixed (DISPLAYCONFIG_MODE_INFO* modesPtr = modes)
|
||||
{
|
||||
result = QueryDisplayConfig(QdcOnlyActivePaths, ref pathCount, pathsPtr, ref modeCount, modesPtr, IntPtr.Zero);
|
||||
if (result != 0)
|
||||
{
|
||||
return monitorInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get information for each path
|
||||
@@ -8,14 +8,14 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using ManagedCommon;
|
||||
using PowerDisplay.Common.Models;
|
||||
using static PowerDisplay.Native.NativeConstants;
|
||||
using static PowerDisplay.Native.PInvoke;
|
||||
using static PowerDisplay.Common.Drivers.NativeConstants;
|
||||
using static PowerDisplay.Common.Drivers.PInvoke;
|
||||
|
||||
using MONITORINFOEX = PowerDisplay.Native.MonitorInfoEx;
|
||||
using PHYSICAL_MONITOR = PowerDisplay.Native.PhysicalMonitor;
|
||||
using RECT = PowerDisplay.Native.Rect;
|
||||
using MONITORINFOEX = PowerDisplay.Common.Drivers.MonitorInfoEx;
|
||||
using PHYSICAL_MONITOR = PowerDisplay.Common.Drivers.PhysicalMonitor;
|
||||
using RECT = PowerDisplay.Common.Drivers.Rect;
|
||||
|
||||
namespace PowerDisplay.Native.DDC
|
||||
namespace PowerDisplay.Common.Drivers.DDC
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for discovering and creating monitor objects
|
||||
@@ -6,9 +6,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using ManagedCommon;
|
||||
using PowerDisplay.Common.Models;
|
||||
using static PowerDisplay.Native.PInvoke;
|
||||
using static PowerDisplay.Common.Drivers.PInvoke;
|
||||
|
||||
namespace PowerDisplay.Native.DDC
|
||||
namespace PowerDisplay.Common.Drivers.DDC
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages physical monitor handles - reuse, cleanup, and validation
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace PowerDisplay.Native
|
||||
namespace PowerDisplay.Common.Drivers
|
||||
{
|
||||
/// <summary>
|
||||
/// Windows API constant definitions
|
||||
@@ -5,10 +5,7 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// Type aliases for Windows API naming conventions compatibility
|
||||
using RECT = PowerDisplay.Native.Rect;
|
||||
|
||||
namespace PowerDisplay.Native;
|
||||
namespace PowerDisplay.Common.Drivers;
|
||||
|
||||
/// <summary>
|
||||
/// Native delegate type definitions
|
||||
@@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
|
||||
|
||||
#pragma warning disable SA1649 // File name should match first type name - Multiple related P/Invoke structures
|
||||
|
||||
namespace PowerDisplay.Native
|
||||
namespace PowerDisplay.Common.Drivers
|
||||
{
|
||||
/// <summary>
|
||||
/// Physical monitor structure for DDC/CI
|
||||
@@ -5,118 +5,13 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PowerDisplay.Native
|
||||
namespace PowerDisplay.Common.Drivers
|
||||
{
|
||||
/// <summary>
|
||||
/// P/Invoke declarations using LibraryImport source generator
|
||||
/// </summary>
|
||||
internal static partial class PInvoke
|
||||
{
|
||||
// ==================== User32.dll - Window Management ====================
|
||||
// GetWindowLong: On 64-bit use GetWindowLongPtrW, on 32-bit use GetWindowLongW
|
||||
#if WIN64
|
||||
[LibraryImport("user32.dll", EntryPoint = "GetWindowLongPtrW")]
|
||||
internal static partial IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
#else
|
||||
[LibraryImport("user32.dll", EntryPoint = "GetWindowLongW")]
|
||||
internal static partial int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
#endif
|
||||
|
||||
// SetWindowLong: On 64-bit use SetWindowLongPtrW, on 32-bit use SetWindowLongW
|
||||
#if WIN64
|
||||
[LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
|
||||
internal static partial IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
#else
|
||||
[LibraryImport("user32.dll", EntryPoint = "SetWindowLongW")]
|
||||
internal static partial int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
#endif
|
||||
|
||||
// SetWindowLongPtr: Always uses the Ptr variant (64-bit)
|
||||
[LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
|
||||
internal static partial IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool SetWindowPos(
|
||||
IntPtr hWnd,
|
||||
IntPtr hWndInsertAfter,
|
||||
int x,
|
||||
int y,
|
||||
int cx,
|
||||
int cy,
|
||||
uint uFlags);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
// ==================== User32.dll - Window Creation and Messaging ====================
|
||||
[LibraryImport("user32.dll", EntryPoint = "CreateWindowExW", StringMarshalling = StringMarshalling.Utf16)]
|
||||
internal static partial IntPtr CreateWindowEx(
|
||||
uint dwExStyle,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpClassName,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpWindowName,
|
||||
uint dwStyle,
|
||||
int x,
|
||||
int y,
|
||||
int nWidth,
|
||||
int nHeight,
|
||||
IntPtr hWndParent,
|
||||
IntPtr hMenu,
|
||||
IntPtr hInstance,
|
||||
IntPtr lpParam);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool DestroyWindow(IntPtr hWnd);
|
||||
|
||||
[LibraryImport("user32.dll", EntryPoint = "DefWindowProcW")]
|
||||
internal static partial IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
internal static partial IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
|
||||
|
||||
[LibraryImport("user32.dll", EntryPoint = "RegisterWindowMessageW", StringMarshalling = StringMarshalling.Utf16)]
|
||||
internal static partial uint RegisterWindowMessage([MarshalAs(UnmanagedType.LPWStr)] string lpString);
|
||||
|
||||
// ==================== User32.dll - Menu Functions ====================
|
||||
[LibraryImport("user32.dll")]
|
||||
internal static partial IntPtr CreatePopupMenu();
|
||||
|
||||
[LibraryImport("user32.dll", EntryPoint = "AppendMenuW", StringMarshalling = StringMarshalling.Utf16)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool AppendMenu(
|
||||
IntPtr hMenu,
|
||||
uint uFlags,
|
||||
uint uIDNewItem,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpNewItem);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool DestroyMenu(IntPtr hMenu);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
internal static partial int TrackPopupMenu(
|
||||
IntPtr hMenu,
|
||||
uint uFlags,
|
||||
int x,
|
||||
int y,
|
||||
int nReserved,
|
||||
IntPtr hWnd,
|
||||
IntPtr prcRect);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool GetCursorPos(out POINT lpPoint);
|
||||
|
||||
// ==================== User32.dll - Display Configuration ====================
|
||||
[LibraryImport("user32.dll")]
|
||||
internal static partial int GetDisplayConfigBufferSizes(
|
||||
@@ -124,14 +19,14 @@ namespace PowerDisplay.Native
|
||||
out uint numPathArrayElements,
|
||||
out uint numModeInfoArrayElements);
|
||||
|
||||
// With DisableRuntimeMarshalling, LibraryImport can handle struct arrays
|
||||
// Use unsafe pointer to avoid runtime marshalling
|
||||
[LibraryImport("user32.dll")]
|
||||
internal static partial int QueryDisplayConfig(
|
||||
internal static unsafe partial int QueryDisplayConfig(
|
||||
uint flags,
|
||||
ref uint numPathArrayElements,
|
||||
[Out] DISPLAYCONFIG_PATH_INFO[] pathArray,
|
||||
DISPLAYCONFIG_PATH_INFO* pathArray,
|
||||
ref uint numModeInfoArrayElements,
|
||||
[Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray,
|
||||
DISPLAYCONFIG_MODE_INFO* modeInfoArray,
|
||||
IntPtr currentTopologyId);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
@@ -13,7 +13,7 @@ using PowerDisplay.Common.Models;
|
||||
using WmiLight;
|
||||
using Monitor = PowerDisplay.Common.Models.Monitor;
|
||||
|
||||
namespace PowerDisplay.Native.WMI
|
||||
namespace PowerDisplay.Common.Drivers.WMI
|
||||
{
|
||||
/// <summary>
|
||||
/// WMI monitor controller for controlling internal laptop displays.
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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.
|
||||
|
||||
namespace PowerDisplay.Common.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Core interface representing monitor hardware data.
|
||||
/// This interface defines the actual hardware values for a monitor.
|
||||
/// Implementations can add UI-specific properties and use converters for display formatting.
|
||||
/// </summary>
|
||||
public interface IMonitorData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the monitor.
|
||||
/// </summary>
|
||||
string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display name of the monitor.
|
||||
/// </summary>
|
||||
string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the hardware ID (EDID format like GSM5C6D).
|
||||
/// </summary>
|
||||
string HardwareId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current brightness value (0-100).
|
||||
/// </summary>
|
||||
int Brightness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current contrast value (0-100).
|
||||
/// </summary>
|
||||
int Contrast { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current volume value (0-100).
|
||||
/// </summary>
|
||||
int Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color temperature VCP preset value (raw DDC/CI value from VCP code 0x14).
|
||||
/// This stores the raw VCP value (e.g., 0x05 for 6500K preset), not the Kelvin temperature.
|
||||
/// Use MonitorValueConverter to convert to/from human-readable Kelvin values.
|
||||
/// </summary>
|
||||
int ColorTemperatureVcp { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,12 @@ namespace PowerDisplay.Common.Models
|
||||
[JsonPropertyName("monitor_id")]
|
||||
public string MonitorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color temperature VCP preset value.
|
||||
/// JSON property name kept as "color_temperature" for IPC compatibility.
|
||||
/// </summary>
|
||||
[JsonPropertyName("color_temperature")]
|
||||
public int ColorTemperature { get; set; }
|
||||
public int ColorTemperatureVcp { get; set; }
|
||||
|
||||
public ColorTemperatureOperation()
|
||||
{
|
||||
|
||||
@@ -5,14 +5,16 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using PowerDisplay.Common.Interfaces;
|
||||
using PowerDisplay.Common.Utils;
|
||||
|
||||
namespace PowerDisplay.Common.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Monitor model that implements property change notification
|
||||
/// Monitor model that implements property change notification.
|
||||
/// Implements IMonitorData to provide a common interface for monitor hardware values.
|
||||
/// </summary>
|
||||
public partial class Monitor : INotifyPropertyChanged
|
||||
public partial class Monitor : INotifyPropertyChanged, IMonitorData
|
||||
{
|
||||
private int _currentBrightness;
|
||||
private int _currentColorTemperature = 0x05; // Default to 6500K preset (VCP 0x14 value)
|
||||
@@ -252,5 +254,33 @@ namespace PowerDisplay.Common.Models
|
||||
LastUpdate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int IMonitorData.Brightness
|
||||
{
|
||||
get => CurrentBrightness;
|
||||
set => CurrentBrightness = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int IMonitorData.Contrast
|
||||
{
|
||||
get => CurrentContrast;
|
||||
set => CurrentContrast = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int IMonitorData.Volume
|
||||
{
|
||||
get => CurrentVolume;
|
||||
set => CurrentVolume = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int IMonitorData.ColorTemperatureVcp
|
||||
{
|
||||
get => CurrentColorTemperature;
|
||||
set => CurrentColorTemperature = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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.Text.Json.Serialization;
|
||||
|
||||
namespace PowerDisplay.Common.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Individual monitor state entry for JSON persistence.
|
||||
/// Stores the current state of a monitor's adjustable parameters.
|
||||
/// </summary>
|
||||
public sealed class MonitorStateEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the brightness level (0-100).
|
||||
/// </summary>
|
||||
[JsonPropertyName("brightness")]
|
||||
public int Brightness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color temperature VCP value.
|
||||
/// </summary>
|
||||
[JsonPropertyName("colorTemperature")]
|
||||
public int ColorTemperatureVcp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the contrast level (0-100).
|
||||
/// </summary>
|
||||
[JsonPropertyName("contrast")]
|
||||
public int Contrast { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the volume level (0-100).
|
||||
/// </summary>
|
||||
[JsonPropertyName("volume")]
|
||||
public int Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the raw capabilities string from DDC/CI.
|
||||
/// </summary>
|
||||
[JsonPropertyName("capabilitiesRaw")]
|
||||
public string? CapabilitiesRaw { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets when this entry was last updated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("lastUpdated")]
|
||||
public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 System.Text.Json.Serialization;
|
||||
|
||||
namespace PowerDisplay.Common.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Monitor state file structure for JSON persistence.
|
||||
/// Contains all monitor states indexed by HardwareId.
|
||||
/// </summary>
|
||||
public sealed class MonitorStateFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the monitor states dictionary.
|
||||
/// Key is the monitor's HardwareId.
|
||||
/// </summary>
|
||||
[JsonPropertyName("monitors")]
|
||||
public Dictionary<string, MonitorStateEntry> Monitors { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets when the file was last updated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("lastUpdated")]
|
||||
public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,12 @@ namespace PowerDisplay.Common.Models
|
||||
[JsonPropertyName("volume")]
|
||||
public int? Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color temperature VCP preset value.
|
||||
/// JSON property name kept as "colorTemperature" for backward compatibility.
|
||||
/// </summary>
|
||||
[JsonPropertyName("colorTemperature")]
|
||||
public int? ColorTemperature { get; set; }
|
||||
public int? ColorTemperatureVcp { get; set; }
|
||||
|
||||
[JsonPropertyName("monitorInternalName")]
|
||||
public string MonitorInternalName { get; set; }
|
||||
@@ -35,11 +39,11 @@ namespace PowerDisplay.Common.Models
|
||||
MonitorInternalName = string.Empty;
|
||||
}
|
||||
|
||||
public ProfileMonitorSetting(string hardwareId, int? brightness = null, int? colorTemperature = null, int? contrast = null, int? volume = null, string monitorInternalName = "")
|
||||
public ProfileMonitorSetting(string hardwareId, int? brightness = null, int? colorTemperatureVcp = null, int? contrast = null, int? volume = null, string monitorInternalName = "")
|
||||
{
|
||||
HardwareId = hardwareId;
|
||||
Brightness = brightness;
|
||||
ColorTemperature = colorTemperature;
|
||||
ColorTemperatureVcp = colorTemperatureVcp;
|
||||
Contrast = contrast;
|
||||
Volume = volume;
|
||||
MonitorInternalName = monitorInternalName;
|
||||
|
||||
@@ -65,6 +65,17 @@ namespace PowerDisplay.Common
|
||||
/// </summary>
|
||||
public const string SettingsFileName = "settings.json";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the monitor state file.
|
||||
/// </summary>
|
||||
public const string MonitorStateFileName = "monitor_state.json";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the monitor state file path.
|
||||
/// Example: C:\Users\{User}\AppData\Local\Microsoft\PowerToys\PowerDisplay\monitor_state.json
|
||||
/// </summary>
|
||||
public static string MonitorStateFilePath => Path.Combine(PowerDisplayFolderPath, MonitorStateFileName);
|
||||
|
||||
/// <summary>
|
||||
/// Event name for LightSwitch theme change notifications.
|
||||
/// </summary>
|
||||
|
||||
@@ -27,6 +27,11 @@ namespace PowerDisplay.Common.Serialization
|
||||
[JsonSerializable(typeof(List<ProfileOperation>))]
|
||||
[JsonSerializable(typeof(ColorTemperatureOperation))]
|
||||
[JsonSerializable(typeof(List<ColorTemperatureOperation>))]
|
||||
|
||||
// Monitor State Types
|
||||
[JsonSerializable(typeof(MonitorStateEntry))]
|
||||
[JsonSerializable(typeof(MonitorStateFile))]
|
||||
[JsonSerializable(typeof(Dictionary<string, MonitorStateEntry>))]
|
||||
public partial class ProfileSerializationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
@@ -8,12 +8,11 @@ using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using PowerDisplay.Common;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Serialization;
|
||||
using PowerDisplay.Common.Utils;
|
||||
using PowerDisplay.Configuration;
|
||||
using PowerDisplay.Serialization;
|
||||
|
||||
namespace PowerDisplay.Helpers
|
||||
namespace PowerDisplay.Common.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages monitor parameter state in a separate file from main settings.
|
||||
@@ -39,7 +38,7 @@ namespace PowerDisplay.Helpers
|
||||
{
|
||||
public int Brightness { get; set; }
|
||||
|
||||
public int ColorTemperature { get; set; }
|
||||
public int ColorTemperatureVcp { get; set; }
|
||||
|
||||
public int Contrast { get; set; }
|
||||
|
||||
@@ -48,11 +47,15 @@ namespace PowerDisplay.Helpers
|
||||
public string? CapabilitiesRaw { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MonitorStateManager"/> class.
|
||||
/// Uses PathConstants for consistent path management.
|
||||
/// </summary>
|
||||
public MonitorStateManager()
|
||||
{
|
||||
// Use PathConstants for consistent path management
|
||||
PathConstants.EnsurePowerDisplayFolderExists();
|
||||
_stateFilePath = Path.Combine(PathConstants.PowerDisplayFolderPath, AppConstants.State.StateFileName);
|
||||
_stateFilePath = PathConstants.MonitorStateFilePath;
|
||||
|
||||
// Initialize debouncer for batching rapid updates (e.g., slider drag)
|
||||
_saveDebouncer = new SimpleDebouncer(SaveDebounceMs);
|
||||
@@ -68,6 +71,9 @@ namespace PowerDisplay.Helpers
|
||||
/// Uses HardwareId as the stable key.
|
||||
/// Debounced-save strategy reduces disk I/O by batching rapid updates (e.g., during slider drag).
|
||||
/// </summary>
|
||||
/// <param name="hardwareId">The monitor's hardware ID.</param>
|
||||
/// <param name="property">The property name to update (Brightness, ColorTemperature, Contrast, or Volume).</param>
|
||||
/// <param name="value">The new value.</param>
|
||||
public void UpdateMonitorParameter(string hardwareId, string property, int value)
|
||||
{
|
||||
try
|
||||
@@ -94,7 +100,7 @@ namespace PowerDisplay.Helpers
|
||||
state.Brightness = value;
|
||||
break;
|
||||
case "ColorTemperature":
|
||||
state.ColorTemperature = value;
|
||||
state.ColorTemperatureVcp = value;
|
||||
break;
|
||||
case "Contrast":
|
||||
state.Contrast = value;
|
||||
@@ -121,9 +127,11 @@ namespace PowerDisplay.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get saved parameters for a monitor using HardwareId
|
||||
/// Get saved parameters for a monitor using HardwareId.
|
||||
/// </summary>
|
||||
public (int Brightness, int ColorTemperature, int Contrast, int Volume)? GetMonitorParameters(string hardwareId)
|
||||
/// <param name="hardwareId">The monitor's hardware ID.</param>
|
||||
/// <returns>A tuple of (Brightness, ColorTemperatureVcp, Contrast, Volume) or null if not found.</returns>
|
||||
public (int Brightness, int ColorTemperatureVcp, int Contrast, int Volume)? GetMonitorParameters(string hardwareId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hardwareId))
|
||||
{
|
||||
@@ -134,7 +142,7 @@ namespace PowerDisplay.Helpers
|
||||
{
|
||||
if (_states.TryGetValue(hardwareId, out var state))
|
||||
{
|
||||
return (state.Brightness, state.ColorTemperature, state.Contrast, state.Volume);
|
||||
return (state.Brightness, state.ColorTemperatureVcp, state.Contrast, state.Volume);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +150,7 @@ namespace PowerDisplay.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load state from disk
|
||||
/// Load state from disk.
|
||||
/// </summary>
|
||||
private void LoadStateFromDisk()
|
||||
{
|
||||
@@ -155,7 +163,7 @@ namespace PowerDisplay.Helpers
|
||||
}
|
||||
|
||||
var json = File.ReadAllText(_stateFilePath);
|
||||
var stateFile = JsonSerializer.Deserialize(json, AppJsonContext.Default.MonitorStateFile);
|
||||
var stateFile = JsonSerializer.Deserialize(json, ProfileSerializationContext.Default.MonitorStateFile);
|
||||
|
||||
if (stateFile?.Monitors != null)
|
||||
{
|
||||
@@ -169,7 +177,7 @@ namespace PowerDisplay.Helpers
|
||||
_states[monitorKey] = new MonitorState
|
||||
{
|
||||
Brightness = entry.Brightness,
|
||||
ColorTemperature = entry.ColorTemperature,
|
||||
ColorTemperatureVcp = entry.ColorTemperatureVcp,
|
||||
Contrast = entry.Contrast,
|
||||
Volume = entry.Volume,
|
||||
CapabilitiesRaw = entry.CapabilitiesRaw,
|
||||
@@ -217,7 +225,7 @@ namespace PowerDisplay.Helpers
|
||||
stateFile.Monitors[monitorId] = new MonitorStateEntry
|
||||
{
|
||||
Brightness = state.Brightness,
|
||||
ColorTemperature = state.ColorTemperature,
|
||||
ColorTemperatureVcp = state.ColorTemperatureVcp,
|
||||
Contrast = state.Contrast,
|
||||
Volume = state.Volume,
|
||||
CapabilitiesRaw = state.CapabilitiesRaw,
|
||||
@@ -227,7 +235,7 @@ namespace PowerDisplay.Helpers
|
||||
}
|
||||
|
||||
// Write to disk asynchronously
|
||||
var json = JsonSerializer.Serialize(stateFile, AppJsonContext.Default.MonitorStateFile);
|
||||
var json = JsonSerializer.Serialize(stateFile, ProfileSerializationContext.Default.MonitorStateFile);
|
||||
await File.WriteAllTextAsync(_stateFilePath, json);
|
||||
|
||||
// Clear dirty flag after successful save
|
||||
@@ -244,6 +252,9 @@ namespace PowerDisplay.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the MonitorStateManager, flushing any pending state changes.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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 PowerDisplay.Common.Interfaces;
|
||||
|
||||
namespace PowerDisplay.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for monitor matching and identification.
|
||||
/// Provides consistent logic for matching monitors across different data sources.
|
||||
/// </summary>
|
||||
public static class MonitorMatchingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate a unique key for monitor matching based on hardware ID and internal name.
|
||||
/// Uses HardwareId if available, otherwise falls back to Id (InternalName) or Name.
|
||||
/// </summary>
|
||||
/// <param name="monitor">The monitor data to generate a key for.</param>
|
||||
/// <returns>A unique string key for the monitor.</returns>
|
||||
public static string GetMonitorKey(IMonitorData monitor)
|
||||
{
|
||||
if (monitor == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Use hardware ID if available (most stable identifier)
|
||||
if (!string.IsNullOrEmpty(monitor.HardwareId))
|
||||
{
|
||||
return monitor.HardwareId;
|
||||
}
|
||||
|
||||
// Fall back to Id (InternalName in MonitorInfo)
|
||||
if (!string.IsNullOrEmpty(monitor.Id))
|
||||
{
|
||||
return monitor.Id;
|
||||
}
|
||||
|
||||
// Last resort: use display name
|
||||
return monitor.Name ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a unique key for monitor matching using explicit values.
|
||||
/// Useful when you don't have an IMonitorData object.
|
||||
/// </summary>
|
||||
/// <param name="hardwareId">The monitor's hardware ID.</param>
|
||||
/// <param name="internalName">The monitor's internal name (optional fallback).</param>
|
||||
/// <param name="name">The monitor's display name (optional fallback).</param>
|
||||
/// <returns>A unique string key for the monitor.</returns>
|
||||
public static string GetMonitorKey(string? hardwareId, string? internalName = null, string? name = null)
|
||||
{
|
||||
// Use hardware ID if available (most stable identifier)
|
||||
if (!string.IsNullOrEmpty(hardwareId))
|
||||
{
|
||||
return hardwareId;
|
||||
}
|
||||
|
||||
// Fall back to internal name
|
||||
if (!string.IsNullOrEmpty(internalName))
|
||||
{
|
||||
return internalName;
|
||||
}
|
||||
|
||||
// Last resort: use display name
|
||||
return name ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two monitors are considered the same based on their keys.
|
||||
/// </summary>
|
||||
/// <param name="monitor1">First monitor.</param>
|
||||
/// <param name="monitor2">Second monitor.</param>
|
||||
/// <returns>True if the monitors have the same key.</returns>
|
||||
public static bool AreMonitorsSame(IMonitorData monitor1, IMonitorData monitor2)
|
||||
{
|
||||
if (monitor1 == null || monitor2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return GetMonitorKey(monitor1) == GetMonitorKey(monitor2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// 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.Collections.Generic;
|
||||
|
||||
namespace PowerDisplay.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides conversion utilities for monitor hardware values.
|
||||
/// Use this class to convert between raw hardware values and display-friendly formats.
|
||||
/// </summary>
|
||||
public static class MonitorValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard VCP color temperature preset to Kelvin value mapping.
|
||||
/// Based on MCCS (Monitor Control Command Set) standard.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<int, int> VcpToKelvinMap = new()
|
||||
{
|
||||
[0x03] = 4000,
|
||||
[0x04] = 5000,
|
||||
[0x05] = 6500,
|
||||
[0x06] = 7500,
|
||||
[0x08] = 9300,
|
||||
[0x09] = 10000,
|
||||
[0x0A] = 11500,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Reverse mapping from Kelvin to VCP value.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<int, int> KelvinToVcpMap = new()
|
||||
{
|
||||
[4000] = 0x03,
|
||||
[5000] = 0x04,
|
||||
[6500] = 0x05,
|
||||
[7500] = 0x06,
|
||||
[9300] = 0x08,
|
||||
[10000] = 0x09,
|
||||
[11500] = 0x0A,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts a VCP color temperature preset value to approximate Kelvin temperature.
|
||||
/// </summary>
|
||||
/// <param name="vcpValue">The VCP preset value (e.g., 0x05).</param>
|
||||
/// <returns>The Kelvin temperature (e.g., 6500), or 0 if unknown.</returns>
|
||||
public static int VcpToKelvin(int vcpValue)
|
||||
{
|
||||
return VcpToKelvinMap.TryGetValue(vcpValue, out var kelvin) ? kelvin : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Kelvin temperature to VCP color temperature preset value.
|
||||
/// </summary>
|
||||
/// <param name="kelvin">The Kelvin temperature (e.g., 6500).</param>
|
||||
/// <returns>The VCP preset value (e.g., 0x05), or 0x05 (6500K) as default if unknown.</returns>
|
||||
public static int KelvinToVcp(int kelvin)
|
||||
{
|
||||
return KelvinToVcpMap.TryGetValue(kelvin, out var vcpValue) ? vcpValue : 0x05;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a VCP color temperature value as a Kelvin string for display.
|
||||
/// </summary>
|
||||
/// <param name="vcpValue">The VCP preset value (e.g., 0x05).</param>
|
||||
/// <returns>Formatted string like "6500K" or "Unknown (0x05)" if not a standard preset.</returns>
|
||||
public static string FormatVcpAsKelvin(int vcpValue)
|
||||
{
|
||||
var kelvin = VcpToKelvin(vcpValue);
|
||||
if (kelvin > 0)
|
||||
{
|
||||
return $"{kelvin}K";
|
||||
}
|
||||
|
||||
// Use VcpValueNames for special presets like sRGB, User 1, etc.
|
||||
var name = VcpValueNames.GetName(ColorTemperatureHelper.ColorTemperatureVcpCode, vcpValue);
|
||||
return name ?? $"Unknown (0x{vcpValue:X2})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a VCP color temperature value as a display name with preset name.
|
||||
/// </summary>
|
||||
/// <param name="vcpValue">The VCP preset value (e.g., 0x05).</param>
|
||||
/// <returns>Formatted string like "6500K (0x05)" or "sRGB (0x01)".</returns>
|
||||
public static string FormatColorTemperatureDisplay(int vcpValue)
|
||||
{
|
||||
return ColorTemperatureHelper.FormatColorTemperatureDisplayName(vcpValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the preset name for a VCP color temperature value.
|
||||
/// </summary>
|
||||
/// <param name="vcpValue">The VCP preset value (e.g., 0x05).</param>
|
||||
/// <returns>Preset name like "6500K", "sRGB", or null if unknown.</returns>
|
||||
public static string? GetColorTemperaturePresetName(int vcpValue)
|
||||
{
|
||||
return VcpValueNames.GetName(ColorTemperatureHelper.ColorTemperatureVcpCode, vcpValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a brightness value for display.
|
||||
/// </summary>
|
||||
/// <param name="brightness">Brightness value (0-100).</param>
|
||||
/// <returns>Formatted string like "50%".</returns>
|
||||
public static string FormatBrightness(int brightness)
|
||||
{
|
||||
return $"{brightness}%";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a contrast value for display.
|
||||
/// </summary>
|
||||
/// <param name="contrast">Contrast value (0-100).</param>
|
||||
/// <returns>Formatted string like "50%".</returns>
|
||||
public static string FormatContrast(int contrast)
|
||||
{
|
||||
return $"{contrast}%";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a volume value for display.
|
||||
/// </summary>
|
||||
/// <param name="volume">Volume value (0-100).</param>
|
||||
/// <returns>Formatted string like "50%".</returns>
|
||||
public static string FormatVolume(int volume)
|
||||
{
|
||||
return $"{volume}%";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a VCP value represents a known color temperature preset.
|
||||
/// </summary>
|
||||
/// <param name="vcpValue">The VCP preset value.</param>
|
||||
/// <returns>True if the value is a known preset.</returns>
|
||||
public static bool IsKnownColorTemperaturePreset(int vcpValue)
|
||||
{
|
||||
return VcpToKelvinMap.ContainsKey(vcpValue) ||
|
||||
VcpValueNames.GetName(ColorTemperatureHelper.ColorTemperatureVcpCode, vcpValue) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
src/modules/powerdisplay/PowerDisplay.Lib/Utils/ProfileHelper.cs
Normal file
125
src/modules/powerdisplay/PowerDisplay.Lib/Utils/ProfileHelper.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PowerDisplay.Common.Models;
|
||||
|
||||
namespace PowerDisplay.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for profile management operations.
|
||||
/// Provides utilities for profile name generation and validation.
|
||||
/// </summary>
|
||||
public static class ProfileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Default base name for new profiles.
|
||||
/// </summary>
|
||||
public const string DefaultProfileBaseName = "Profile";
|
||||
|
||||
/// <summary>
|
||||
/// Generate a unique profile name that doesn't conflict with existing profiles.
|
||||
/// </summary>
|
||||
/// <param name="existingProfiles">The collection of existing profiles.</param>
|
||||
/// <param name="baseName">The base name to use (default: "Profile").</param>
|
||||
/// <returns>A unique profile name like "Profile 1", "Profile 2", etc.</returns>
|
||||
public static string GenerateUniqueProfileName(PowerDisplayProfiles existingProfiles, string baseName = DefaultProfileBaseName)
|
||||
{
|
||||
var existingNames = new HashSet<string>();
|
||||
|
||||
if (existingProfiles?.Profiles != null)
|
||||
{
|
||||
foreach (var profile in existingProfiles.Profiles)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(profile.Name))
|
||||
{
|
||||
existingNames.Add(profile.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GenerateUniqueProfileName(existingNames, baseName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a unique profile name that doesn't conflict with existing names.
|
||||
/// </summary>
|
||||
/// <param name="existingNames">Set of existing profile names.</param>
|
||||
/// <param name="baseName">The base name to use (default: "Profile").</param>
|
||||
/// <returns>A unique profile name like "Profile 1", "Profile 2", etc.</returns>
|
||||
public static string GenerateUniqueProfileName(ISet<string> existingNames, string baseName = DefaultProfileBaseName)
|
||||
{
|
||||
if (existingNames == null)
|
||||
{
|
||||
return $"{baseName} 1";
|
||||
}
|
||||
|
||||
int counter = 1;
|
||||
string name;
|
||||
do
|
||||
{
|
||||
name = $"{baseName} {counter}";
|
||||
counter++;
|
||||
}
|
||||
while (existingNames.Contains(name));
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a unique profile name from a collection of profile names.
|
||||
/// </summary>
|
||||
/// <param name="existingNames">Enumerable of existing profile names.</param>
|
||||
/// <param name="baseName">The base name to use (default: "Profile").</param>
|
||||
/// <returns>A unique profile name like "Profile 1", "Profile 2", etc.</returns>
|
||||
public static string GenerateUniqueProfileName(IEnumerable<string> existingNames, string baseName = DefaultProfileBaseName)
|
||||
{
|
||||
var nameSet = new HashSet<string>(existingNames ?? Enumerable.Empty<string>());
|
||||
return GenerateUniqueProfileName(nameSet, baseName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that a profile has at least one monitor with at least one setting.
|
||||
/// </summary>
|
||||
/// <param name="profile">The profile to validate.</param>
|
||||
/// <returns>True if the profile has valid settings.</returns>
|
||||
public static bool HasValidSettings(PowerDisplayProfile profile)
|
||||
{
|
||||
if (profile == null || profile.MonitorSettings == null || profile.MonitorSettings.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that at least one monitor has at least one setting
|
||||
return profile.MonitorSettings.Any(m =>
|
||||
m.Brightness.HasValue ||
|
||||
m.Contrast.HasValue ||
|
||||
m.Volume.HasValue ||
|
||||
m.ColorTemperatureVcp.HasValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a profile name is available (not already in use).
|
||||
/// </summary>
|
||||
/// <param name="name">The name to check.</param>
|
||||
/// <param name="existingProfiles">The collection of existing profiles.</param>
|
||||
/// <returns>True if the name is available.</returns>
|
||||
public static bool IsNameAvailable(string name, PowerDisplayProfiles existingProfiles)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (existingProfiles?.Profiles == null || existingProfiles.Profiles.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !existingProfiles.Profiles.Any(p =>
|
||||
string.Equals(p.Name, name, System.StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,13 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using PowerDisplay.Common.Drivers;
|
||||
using PowerDisplay.Common.Drivers.DDC;
|
||||
using PowerDisplay.Common.Drivers.WMI;
|
||||
using PowerDisplay.Common.Interfaces;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Utils;
|
||||
using PowerDisplay.Core.Interfaces;
|
||||
using PowerDisplay.Native;
|
||||
using PowerDisplay.Native.DDC;
|
||||
using PowerDisplay.Native.WMI;
|
||||
using Monitor = PowerDisplay.Common.Models.Monitor;
|
||||
|
||||
namespace PowerDisplay.Core
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using static PowerDisplay.Native.PInvoke;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PowerDisplay.Native
|
||||
namespace PowerDisplay.Helpers
|
||||
{
|
||||
internal static class WindowHelper
|
||||
internal static partial class WindowHelper
|
||||
{
|
||||
// Window Styles
|
||||
private const int GwlStyle = -16;
|
||||
@@ -42,6 +42,50 @@ namespace PowerDisplay.Native
|
||||
private const int SwMinimize = 6;
|
||||
private const int SwRestore = 9;
|
||||
|
||||
// P/Invoke declarations
|
||||
#if WIN64
|
||||
[LibraryImport("user32.dll", EntryPoint = "GetWindowLongPtrW")]
|
||||
private static partial IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
#else
|
||||
[LibraryImport("user32.dll", EntryPoint = "GetWindowLongW")]
|
||||
private static partial int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
#endif
|
||||
|
||||
#if WIN64
|
||||
[LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
|
||||
private static partial IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
#else
|
||||
[LibraryImport("user32.dll", EntryPoint = "SetWindowLongW")]
|
||||
private static partial int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
#endif
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool SetWindowPos(
|
||||
IntPtr hWnd,
|
||||
IntPtr hWndInsertAfter,
|
||||
int x,
|
||||
int y,
|
||||
int cx,
|
||||
int cy,
|
||||
uint uFlags);
|
||||
|
||||
[LibraryImport("user32.dll", EntryPoint = "ShowWindow")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool ShowWindowNative(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[LibraryImport("user32.dll", EntryPoint = "IsWindowVisible")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool IsWindowVisibleNative(IntPtr hWnd);
|
||||
|
||||
/// <summary>
|
||||
/// Check if window is visible
|
||||
/// </summary>
|
||||
public static bool IsWindowVisible(IntPtr hWnd)
|
||||
{
|
||||
return IsWindowVisibleNative(hWnd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable window moving and resizing functionality
|
||||
/// </summary>
|
||||
@@ -115,7 +159,7 @@ namespace PowerDisplay.Native
|
||||
/// </summary>
|
||||
public static void ShowWindow(IntPtr hWnd, bool show)
|
||||
{
|
||||
PInvoke.ShowWindow(hWnd, show ? SwShow : SwHide);
|
||||
ShowWindowNative(hWnd, show ? SwShow : SwHide);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,7 +167,7 @@ namespace PowerDisplay.Native
|
||||
/// </summary>
|
||||
public static void MinimizeWindow(IntPtr hWnd)
|
||||
{
|
||||
PInvoke.ShowWindow(hWnd, SwMinimize);
|
||||
ShowWindowNative(hWnd, SwMinimize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -131,7 +175,7 @@ namespace PowerDisplay.Native
|
||||
/// </summary>
|
||||
public static void RestoreWindow(IntPtr hWnd)
|
||||
{
|
||||
PInvoke.ShowWindow(hWnd, SwRestore);
|
||||
ShowWindowNative(hWnd, SwRestore);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -20,11 +20,9 @@ using PowerDisplay.Configuration;
|
||||
using PowerDisplay.Core;
|
||||
using PowerDisplay.Core.Interfaces;
|
||||
using PowerDisplay.Helpers;
|
||||
using PowerDisplay.Native;
|
||||
using PowerDisplay.ViewModels;
|
||||
using Windows.Graphics;
|
||||
using WinRT.Interop;
|
||||
using static PowerDisplay.Native.PInvoke;
|
||||
using Monitor = PowerDisplay.Common.Models.Monitor;
|
||||
|
||||
namespace PowerDisplay
|
||||
@@ -308,7 +306,7 @@ namespace PowerDisplay
|
||||
public bool IsWindowVisible()
|
||||
{
|
||||
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
return PInvoke.IsWindowVisible(hWnd);
|
||||
return WindowHelper.IsWindowVisible(hWnd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -319,15 +317,20 @@ namespace PowerDisplay
|
||||
try
|
||||
{
|
||||
bool isVisible = IsWindowVisible();
|
||||
Logger.LogInfo($"[ToggleWindow] IsWindowVisible returned: {isVisible}");
|
||||
|
||||
if (isVisible)
|
||||
{
|
||||
Logger.LogInfo("[ToggleWindow] Window is visible, calling HideWindow");
|
||||
HideWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo("[ToggleWindow] Window is hidden, calling ShowWindow");
|
||||
ShowWindow();
|
||||
}
|
||||
|
||||
Logger.LogInfo("[ToggleWindow] Toggle completed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// 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 System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -16,11 +15,11 @@ namespace PowerDisplay.Serialization
|
||||
/// <summary>
|
||||
/// JSON source generation context for AOT compatibility.
|
||||
/// Eliminates reflection-based JSON serialization.
|
||||
/// Note: MonitorStateFile and MonitorStateEntry are now in PowerDisplay.Lib
|
||||
/// and should be serialized using ProfileSerializationContext from the Lib.
|
||||
/// </summary>
|
||||
[JsonSerializable(typeof(MonitorInfoData))]
|
||||
[JsonSerializable(typeof(IPCMessageAction))]
|
||||
[JsonSerializable(typeof(MonitorStateFile))]
|
||||
[JsonSerializable(typeof(MonitorStateEntry))]
|
||||
[JsonSerializable(typeof(PowerDisplaySettings))]
|
||||
[JsonSerializable(typeof(ColorTemperatureOperation))]
|
||||
[JsonSerializable(typeof(ProfileOperation))]
|
||||
@@ -58,42 +57,4 @@ namespace PowerDisplay.Serialization
|
||||
[JsonPropertyName("action")]
|
||||
public string? Action { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monitor state file structure for JSON persistence.
|
||||
/// Made internal (from private) to support source generation.
|
||||
/// </summary>
|
||||
internal sealed class MonitorStateFile
|
||||
{
|
||||
[JsonPropertyName("monitors")]
|
||||
public Dictionary<string, MonitorStateEntry> Monitors { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("lastUpdated")]
|
||||
public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual monitor state entry.
|
||||
/// Made internal (from private) to support source generation.
|
||||
/// </summary>
|
||||
internal sealed class MonitorStateEntry
|
||||
{
|
||||
[JsonPropertyName("brightness")]
|
||||
public int Brightness { get; set; }
|
||||
|
||||
[JsonPropertyName("colorTemperature")]
|
||||
public int ColorTemperature { get; set; }
|
||||
|
||||
[JsonPropertyName("contrast")]
|
||||
public int Contrast { get; set; }
|
||||
|
||||
[JsonPropertyName("volume")]
|
||||
public int Volume { get; set; }
|
||||
|
||||
[JsonPropertyName("capabilitiesRaw")]
|
||||
public string? CapabilitiesRaw { get; set; }
|
||||
|
||||
[JsonPropertyName("lastUpdated")]
|
||||
public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public partial class MainViewModel
|
||||
|
||||
if (pendingOp != null && !string.IsNullOrEmpty(pendingOp.MonitorId))
|
||||
{
|
||||
Logger.LogInfo($"[Settings] Processing pending color temperature operation: Monitor '{pendingOp.MonitorId}' -> 0x{pendingOp.ColorTemperature:X2}");
|
||||
Logger.LogInfo($"[Settings] Processing pending color temperature operation: Monitor '{pendingOp.MonitorId}' -> 0x{pendingOp.ColorTemperatureVcp:X2}");
|
||||
|
||||
// Find the monitor by internal name (ID)
|
||||
var monitorVm = Monitors.FirstOrDefault(m => m.Id == pendingOp.MonitorId);
|
||||
@@ -109,7 +109,7 @@ public partial class MainViewModel
|
||||
if (monitorVm != null)
|
||||
{
|
||||
// Apply color temperature directly
|
||||
await ApplyColorTemperatureAsync(monitorVm, pendingOp.ColorTemperature);
|
||||
await ApplyColorTemperatureAsync(monitorVm, pendingOp.ColorTemperatureVcp);
|
||||
Logger.LogInfo($"[Settings] Successfully applied color temperature to monitor '{pendingOp.MonitorId}'");
|
||||
}
|
||||
else
|
||||
@@ -277,9 +277,9 @@ public partial class MainViewModel
|
||||
}
|
||||
|
||||
// Apply color temperature if included in profile
|
||||
if (setting.ColorTemperature.HasValue && setting.ColorTemperature.Value > 0)
|
||||
if (setting.ColorTemperatureVcp.HasValue && setting.ColorTemperatureVcp.Value > 0)
|
||||
{
|
||||
updateTasks.Add(monitorVm.SetColorTemperatureAsync(setting.ColorTemperature.Value, fromProfile: true));
|
||||
updateTasks.Add(monitorVm.SetColorTemperatureAsync(setting.ColorTemperatureVcp.Value, fromProfile: true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,10 +358,10 @@ public partial class MainViewModel
|
||||
|
||||
// Color temperature: VCP 0x14 preset value (discrete values, no range check needed)
|
||||
// Note: ColorTemperature is now read-only in flyout UI, controlled via Settings UI
|
||||
if (savedState.Value.ColorTemperature > 0)
|
||||
if (savedState.Value.ColorTemperatureVcp > 0)
|
||||
{
|
||||
// Validation will happen in Settings UI when applying preset values
|
||||
monitorVm.UpdatePropertySilently(nameof(monitorVm.ColorTemperature), savedState.Value.ColorTemperature);
|
||||
monitorVm.UpdatePropertySilently(nameof(monitorVm.ColorTemperature), savedState.Value.ColorTemperatureVcp);
|
||||
}
|
||||
|
||||
// Contrast validation - only apply if hardware supports it
|
||||
@@ -540,7 +540,7 @@ public partial class MainViewModel
|
||||
hardwareId: vm.HardwareId,
|
||||
communicationMethod: vm.CommunicationMethod,
|
||||
currentBrightness: vm.Brightness,
|
||||
colorTemperature: vm.ColorTemperature)
|
||||
colorTemperatureVcp: vm.ColorTemperature)
|
||||
{
|
||||
CapabilitiesRaw = vm.CapabilitiesRaw,
|
||||
VcpCodes = BuildVcpCodesList(vm),
|
||||
|
||||
@@ -7,19 +7,22 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using PowerDisplay.Common.Interfaces;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Utils;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class MonitorInfo : Observable
|
||||
public class MonitorInfo : Observable, IMonitorData
|
||||
{
|
||||
private string _name = string.Empty;
|
||||
private string _internalName = string.Empty;
|
||||
private string _hardwareId = string.Empty;
|
||||
private string _communicationMethod = string.Empty;
|
||||
private int _currentBrightness;
|
||||
private int _colorTemperature = 6500;
|
||||
private int _colorTemperatureVcp = 0x05; // Default to 6500K preset (VCP 0x14 value)
|
||||
private int _contrast;
|
||||
private int _volume;
|
||||
private bool _isHidden;
|
||||
private bool _enableContrast;
|
||||
private bool _enableVolume;
|
||||
@@ -48,14 +51,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
CommunicationMethod = communicationMethod;
|
||||
}
|
||||
|
||||
public MonitorInfo(string name, string internalName, string hardwareId, string communicationMethod, int currentBrightness, int colorTemperature)
|
||||
public MonitorInfo(string name, string internalName, string hardwareId, string communicationMethod, int currentBrightness, int colorTemperatureVcp)
|
||||
{
|
||||
Name = name;
|
||||
InternalName = internalName;
|
||||
HardwareId = hardwareId;
|
||||
CommunicationMethod = communicationMethod;
|
||||
CurrentBrightness = currentBrightness;
|
||||
ColorTemperature = colorTemperature;
|
||||
ColorTemperatureVcp = colorTemperatureVcp;
|
||||
}
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
@@ -128,21 +131,68 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("colorTemperature")]
|
||||
public int ColorTemperature
|
||||
/// <summary>
|
||||
/// Gets or sets the color temperature VCP preset value (raw DDC/CI value from VCP code 0x14).
|
||||
/// This stores the raw VCP value (e.g., 0x05 for 6500K preset), not the Kelvin temperature.
|
||||
/// Use MonitorValueConverter to convert to human-readable Kelvin values for display.
|
||||
/// </summary>
|
||||
[JsonPropertyName("colorTemperatureVcp")]
|
||||
public int ColorTemperatureVcp
|
||||
{
|
||||
get => _colorTemperature;
|
||||
get => _colorTemperatureVcp;
|
||||
set
|
||||
{
|
||||
if (_colorTemperature != value)
|
||||
if (_colorTemperatureVcp != value)
|
||||
{
|
||||
_colorTemperature = value;
|
||||
_colorTemperatureVcp = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ColorTemperatureDisplay));
|
||||
OnPropertyChanged(nameof(ColorPresetsForDisplay)); // Update display list when current value changes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color temperature as a human-readable display string.
|
||||
/// Converts the VCP value to a Kelvin temperature display.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string ColorTemperatureDisplay => MonitorValueConverter.FormatColorTemperatureDisplay(ColorTemperatureVcp);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current contrast value (0-100).
|
||||
/// </summary>
|
||||
[JsonPropertyName("contrast")]
|
||||
public int Contrast
|
||||
{
|
||||
get => _contrast;
|
||||
set
|
||||
{
|
||||
if (_contrast != value)
|
||||
{
|
||||
_contrast = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current volume value (0-100).
|
||||
/// </summary>
|
||||
[JsonPropertyName("volume")]
|
||||
public int Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if (_volume != value)
|
||||
{
|
||||
_volume = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("isHidden")]
|
||||
public bool IsHidden
|
||||
{
|
||||
@@ -407,7 +457,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
|
||||
// Check if current value is in the preset list
|
||||
var currentValueInList = presets.Any(p => p.VcpValue == _colorTemperature);
|
||||
var currentValueInList = presets.Any(p => p.VcpValue == _colorTemperatureVcp);
|
||||
|
||||
if (currentValueInList)
|
||||
{
|
||||
@@ -419,8 +469,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
var displayList = new List<ColorPresetItem>();
|
||||
|
||||
// Add current value with "Custom" indicator using shared helper
|
||||
var displayName = ColorTemperatureHelper.FormatCustomColorTemperatureDisplayName(_colorTemperature);
|
||||
displayList.Add(new ColorPresetItem(_colorTemperature, displayName));
|
||||
var displayName = ColorTemperatureHelper.FormatCustomColorTemperatureDisplayName(_colorTemperatureVcp);
|
||||
displayList.Add(new ColorPresetItem(_colorTemperatureVcp, displayName));
|
||||
|
||||
// Add all supported presets
|
||||
displayList.AddRange(presets);
|
||||
@@ -502,7 +552,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
HardwareId = other.HardwareId;
|
||||
CommunicationMethod = other.CommunicationMethod;
|
||||
CurrentBrightness = other.CurrentBrightness;
|
||||
ColorTemperature = other.ColorTemperature;
|
||||
Contrast = other.Contrast;
|
||||
Volume = other.Volume;
|
||||
ColorTemperatureVcp = other.ColorTemperatureVcp;
|
||||
IsHidden = other.IsHidden;
|
||||
EnableContrast = other.EnableContrast;
|
||||
EnableVolume = other.EnableVolume;
|
||||
@@ -516,6 +568,41 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
CapabilitiesStatus = other.CapabilitiesStatus;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
string IMonitorData.Id
|
||||
{
|
||||
get => InternalName;
|
||||
set => InternalName = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int IMonitorData.Brightness
|
||||
{
|
||||
get => CurrentBrightness;
|
||||
set => CurrentBrightness = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int IMonitorData.Contrast
|
||||
{
|
||||
get => Contrast;
|
||||
set => Contrast = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int IMonitorData.Volume
|
||||
{
|
||||
get => Volume;
|
||||
set => Volume = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
int IMonitorData.ColorTemperatureVcp
|
||||
{
|
||||
get => ColorTemperatureVcp;
|
||||
set => ColorTemperatureVcp = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type alias for ColorPresetItem to maintain backward compatibility with XAML bindings.
|
||||
/// Inherits from PowerDisplay.Common.Models.ColorPresetItem.
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
x:Name="ColorTemperatureComboBox"
|
||||
MinWidth="200"
|
||||
ItemsSource="{Binding ColorPresetsForDisplay, Mode=OneWay}"
|
||||
SelectedValue="{Binding ColorTemperature, Mode=TwoWay}"
|
||||
SelectedValue="{Binding ColorTemperatureVcp, Mode=TwoWay}"
|
||||
SelectedValuePath="VcpValue"
|
||||
DisplayMemberPath="DisplayName"
|
||||
PlaceholderText="Not available"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
@@ -12,6 +13,7 @@ using Microsoft.PowerToys.Settings.UI.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Utils;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
@@ -70,7 +72,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
// Store the initial value
|
||||
if (!_previousColorTemperatureValues.ContainsKey(monitor.HardwareId))
|
||||
{
|
||||
_previousColorTemperatureValues[monitor.HardwareId] = monitor.ColorTemperature;
|
||||
_previousColorTemperatureValues[monitor.HardwareId] = monitor.ColorTemperatureVcp;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -87,7 +89,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
int previousValue;
|
||||
if (!_previousColorTemperatureValues.TryGetValue(monitor.HardwareId, out previousValue))
|
||||
{
|
||||
previousValue = monitor.ColorTemperature;
|
||||
previousValue = monitor.ColorTemperatureVcp;
|
||||
}
|
||||
|
||||
// Show confirmation dialog
|
||||
@@ -137,7 +139,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
// User confirmed, apply the change
|
||||
// Setting the property will trigger save to settings file via OnPropertyChanged
|
||||
monitor.ColorTemperature = newValue.Value;
|
||||
monitor.ColorTemperatureVcp = newValue.Value;
|
||||
_previousColorTemperatureValues[monitor.HardwareId] = newValue.Value;
|
||||
|
||||
// Send IPC message to PowerDisplay with monitor ID and new color temperature value
|
||||
@@ -235,22 +237,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private string GenerateDefaultProfileName()
|
||||
{
|
||||
var existingNames = new HashSet<string>();
|
||||
foreach (var profile in ViewModel.Profiles)
|
||||
{
|
||||
existingNames.Add(profile.Name);
|
||||
}
|
||||
|
||||
int counter = 1;
|
||||
string name;
|
||||
do
|
||||
{
|
||||
name = $"Profile {counter}";
|
||||
counter++;
|
||||
}
|
||||
while (existingNames.Contains(name));
|
||||
|
||||
return name;
|
||||
// Use shared ProfileHelper for consistent profile name generation
|
||||
var existingNames = ViewModel.Profiles.Select(p => p.Name);
|
||||
return ProfileHelper.GenerateUniqueProfileName(existingNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,10 +71,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
}
|
||||
|
||||
// Set color temperature if included in profile
|
||||
if (monitorSetting.ColorTemperature.HasValue)
|
||||
if (monitorSetting.ColorTemperatureVcp.HasValue)
|
||||
{
|
||||
monitorItem.IncludeColorTemperature = true;
|
||||
monitorItem.ColorTemperature = monitorSetting.ColorTemperature.Value;
|
||||
monitorItem.ColorTemperature = monitorSetting.ColorTemperatureVcp.Value;
|
||||
}
|
||||
|
||||
// Set contrast if included in profile
|
||||
|
||||
@@ -25,6 +25,7 @@ using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
using PowerDisplay.Common.Models;
|
||||
using PowerDisplay.Common.Services;
|
||||
using PowerDisplay.Common.Utils;
|
||||
using PowerToys.Interop;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
@@ -216,17 +217,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a unique key for monitor matching based on hardware ID and internal name
|
||||
/// Generate a unique key for monitor matching based on hardware ID and internal name.
|
||||
/// Uses shared MonitorMatchingHelper from PowerDisplay.Lib for consistency.
|
||||
/// </summary>
|
||||
private string GetMonitorKey(MonitorInfo monitor)
|
||||
private static string GetMonitorKey(MonitorInfo monitor)
|
||||
{
|
||||
// Use hardware ID if available, otherwise fall back to internal name
|
||||
if (!string.IsNullOrEmpty(monitor.HardwareId))
|
||||
{
|
||||
return monitor.HardwareId;
|
||||
}
|
||||
|
||||
return monitor.InternalName ?? monitor.Name ?? string.Empty;
|
||||
// Use shared helper for consistent monitor matching logic
|
||||
return MonitorMatchingHelper.GetMonitorKey(monitor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -344,7 +341,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
_settings.Properties.PendingColorTemperatureOperation = new ColorTemperatureOperation
|
||||
{
|
||||
MonitorId = monitorInternalName,
|
||||
ColorTemperature = colorTemperature,
|
||||
ColorTemperatureVcp = colorTemperature,
|
||||
};
|
||||
|
||||
// Save settings to persist the operation
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
Brightness = monitor.CurrentBrightness,
|
||||
Contrast = 50, // Default value (MonitorInfo doesn't store contrast)
|
||||
Volume = 50, // Default value (MonitorInfo doesn't store volume)
|
||||
ColorTemperature = monitor.ColorTemperature,
|
||||
ColorTemperature = monitor.ColorTemperatureVcp,
|
||||
};
|
||||
|
||||
item.SuppressAutoSelection = false;
|
||||
|
||||
Reference in New Issue
Block a user