mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
PowerDisplay: overhaul design docs, remove obsolete IPC code
Expanded and clarified PowerDisplay design documentation: - Added detailed section on monitor identification (handles, IDs, MST, clone mode, etc.) with diagrams and tables. - Updated architecture, flowcharts, and class diagrams to reflect new helpers, services, and improved data flows. - Expanded internal structure details for PowerDisplay.Lib and app, listing new helpers, models, and utilities. - Updated sequence diagrams for LightSwitch integration and Settings UI interaction. - Revised future considerations, marking hot-plug and rotation as implemented, and adding new feature ideas. Removed all obsolete PowerDisplay IPC forwarding code from settings_window.cpp/h, including the send_powerdisplay_message_to_settings_ui function and related JSON dispatch logic. This reflects a refactor or deprecation of the previous custom IPC mechanism.
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
4. [Technical Terminology](#technical-terminology)
|
4. [Technical Terminology](#technical-terminology)
|
||||||
5. [Architecture Overview](#architecture-overview)
|
5. [Architecture Overview](#architecture-overview)
|
||||||
6. [Component Design](#component-design)
|
6. [Component Design](#component-design)
|
||||||
|
- [Monitor Identification: Handles, IDs, and Names](#monitor-identification-handles-ids-and-names)
|
||||||
7. [Data Flow and Communication](#data-flow-and-communication)
|
7. [Data Flow and Communication](#data-flow-and-communication)
|
||||||
8. [Sequence Diagrams](#sequence-diagrams)
|
8. [Sequence Diagrams](#sequence-diagrams)
|
||||||
9. [Data Models](#data-models)
|
9. [Data Models](#data-models)
|
||||||
@@ -119,26 +120,28 @@ flowchart TB
|
|||||||
end
|
end
|
||||||
|
|
||||||
subgraph PowerDisplayModule["PowerDisplay Module"]
|
subgraph PowerDisplayModule["PowerDisplay Module"]
|
||||||
ModuleInterface["Module Interface (C++ DLL)"]
|
ModuleInterface["Module Interface<br/>(PowerDisplayModuleInterface.dll)"]
|
||||||
PowerDisplayApp["PowerDisplay App (WinUI 3)"]
|
PowerDisplayApp["PowerDisplay App<br/>(PowerToys.PowerDisplay.exe)"]
|
||||||
PowerDisplayLib["PowerDisplay.Lib"]
|
PowerDisplayLib["PowerDisplay.Lib<br/>(Shared Library)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph External["External"]
|
subgraph External["External"]
|
||||||
Hardware["Display Hardware"]
|
Hardware["Display Hardware<br/>(External + Internal)"]
|
||||||
Storage["Persistent Storage (JSON)"]
|
Storage["Persistent Storage<br/>(settings.json, profiles.json)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
Runner -->|"Loads DLL"| ModuleInterface
|
Runner -->|"Loads DLL"| ModuleInterface
|
||||||
Runner -->|"Hotkey Events"| ModuleInterface
|
Runner -->|"Hotkey Events"| ModuleInterface
|
||||||
SettingsUI <-->|"Named Pipes"| Runner
|
SettingsUI <-->|"Named Pipes"| Runner
|
||||||
SettingsUI -->|"Custom Actions"| ModuleInterface
|
SettingsUI -->|"Custom Actions<br/>(Launch, RefreshMonitors,<br/>ApplyColorTemperature, ApplyProfile)"| ModuleInterface
|
||||||
|
|
||||||
ModuleInterface <-->|"Windows Events"| PowerDisplayApp
|
ModuleInterface <-->|"Windows Events<br/>(Show/Toggle/Terminate/Refresh)"| PowerDisplayApp
|
||||||
LightSwitch -->|"Theme Changed Event"| PowerDisplayApp
|
LightSwitch -->|"Theme Events<br/>(Light/Dark)"| PowerDisplayApp
|
||||||
|
|
||||||
PowerDisplayApp --> PowerDisplayLib
|
PowerDisplayApp --> PowerDisplayLib
|
||||||
PowerDisplayLib -->|"DDC/CI + WMI"| Hardware
|
PowerDisplayLib -->|"DDC/CI (Dxva2.dll)"| Hardware
|
||||||
|
PowerDisplayLib -->|"WMI (WmiLight)"| Hardware
|
||||||
|
PowerDisplayLib -->|"ChangeDisplaySettingsEx"| Hardware
|
||||||
PowerDisplayApp <--> Storage
|
PowerDisplayApp <--> Storage
|
||||||
|
|
||||||
style Runner fill:#e1f5fe
|
style Runner fill:#e1f5fe
|
||||||
@@ -164,60 +167,100 @@ src/modules/powerdisplay/
|
|||||||
│ ├── Drivers/
|
│ ├── Drivers/
|
||||||
│ │ ├── DDC/
|
│ │ ├── DDC/
|
||||||
│ │ │ ├── DdcCiController.cs # DDC/CI implementation
|
│ │ │ ├── DdcCiController.cs # DDC/CI implementation
|
||||||
│ │ │ ├── DdcCiNative.cs # P/Invoke declarations
|
│ │ │ ├── DdcCiNative.cs # P/Invoke declarations & QueryDisplayConfig
|
||||||
│ │ │ ├── MonitorDiscoveryHelper.cs
|
│ │ │ ├── MonitorDiscoveryHelper.cs
|
||||||
│ │ │ └── PhysicalMonitorHandleManager.cs
|
│ │ │ └── PhysicalMonitorHandleManager.cs
|
||||||
│ │ └── WMI/
|
│ │ ├── WMI/
|
||||||
│ │ └── WmiController.cs # WMI implementation
|
│ │ │ └── WmiController.cs # WMI implementation (WmiLight library)
|
||||||
|
│ │ ├── NativeConstants.cs # Win32 constants (VCP codes, etc.)
|
||||||
|
│ │ ├── NativeDelegates.cs # P/Invoke delegate types
|
||||||
|
│ │ ├── NativeStructures.cs # Win32 structures
|
||||||
|
│ │ └── PInvoke.cs # P/Invoke declarations
|
||||||
|
│ ├── Helpers/
|
||||||
|
│ │ └── PnpIdHelper.cs # PnP manufacturer ID lookup
|
||||||
│ ├── Interfaces/
|
│ ├── Interfaces/
|
||||||
│ │ └── IMonitorController.cs # Controller abstraction
|
│ │ ├── IMonitorController.cs # Controller abstraction
|
||||||
|
│ │ ├── IMonitorData.cs # Monitor data interface
|
||||||
|
│ │ └── IProfileService.cs # Profile service interface
|
||||||
│ ├── Models/
|
│ ├── Models/
|
||||||
│ │ ├── Monitor.cs # Runtime monitor data
|
│ │ ├── Monitor.cs # Runtime monitor data
|
||||||
│ │ ├── MonitorOperationResult.cs # Operation result enum
|
│ │ ├── MonitorCapabilities.cs # Monitor capability flags
|
||||||
|
│ │ ├── MonitorOperationResult.cs # Operation result
|
||||||
|
│ │ ├── MonitorStateEntry.cs # Persisted monitor state
|
||||||
|
│ │ ├── MonitorStateFile.cs # State file schema
|
||||||
│ │ ├── PowerDisplayProfile.cs # Profile definition
|
│ │ ├── PowerDisplayProfile.cs # Profile definition
|
||||||
│ │ ├── PowerDisplayProfiles.cs # Profile collection
|
│ │ ├── PowerDisplayProfiles.cs # Profile collection
|
||||||
│ │ └── ProfileMonitorSetting.cs # Per-monitor settings
|
│ │ ├── ProfileMonitorSetting.cs # Per-monitor profile settings
|
||||||
|
│ │ ├── ProfileOperation.cs # Profile operation for IPC
|
||||||
|
│ │ ├── ColorTemperatureOperation.cs # Color temp operation for IPC
|
||||||
|
│ │ ├── ColorPresetItem.cs # Color preset UI item
|
||||||
|
│ │ ├── VcpCapabilities.cs # Parsed VCP capabilities
|
||||||
|
│ │ └── VcpFeatureValue.cs # VCP feature value (current/min/max)
|
||||||
|
│ ├── Serialization/
|
||||||
|
│ │ └── ProfileSerializationContext.cs # JSON source generation
|
||||||
│ ├── Services/
|
│ ├── Services/
|
||||||
│ │ ├── LightSwitchListener.cs # Theme change listener
|
│ │ ├── DisplayRotationService.cs # Display rotation via ChangeDisplaySettingsEx
|
||||||
│ │ ├── MonitorStateManager.cs # State persistence (debounced)
|
│ │ ├── MonitorStateManager.cs # State persistence (debounced)
|
||||||
│ │ └── ProfileService.cs # Profile persistence
|
│ │ └── ProfileService.cs # Profile persistence
|
||||||
│ └── Utils/
|
│ ├── Utils/
|
||||||
│ ├── ColorTemperatureHelper.cs # Color temp utilities
|
│ │ ├── ColorTemperatureHelper.cs # Color temp utilities
|
||||||
│ ├── MccsCapabilitiesParser.cs # DDC/CI capabilities parser
|
│ │ ├── EventHelper.cs # Windows Event utilities
|
||||||
│ └── VcpCapabilities.cs # VCP capabilities model
|
│ │ ├── MccsCapabilitiesParser.cs # DDC/CI capabilities parser
|
||||||
|
│ │ ├── MonitorFeatureHelper.cs # Monitor feature utilities
|
||||||
|
│ │ ├── MonitorMatchingHelper.cs # Profile-to-monitor matching
|
||||||
|
│ │ ├── MonitorValueConverter.cs # Value conversion utilities
|
||||||
|
│ │ ├── ProfileHelper.cs # Profile helper utilities
|
||||||
|
│ │ ├── SimpleDebouncer.cs # Generic debouncer
|
||||||
|
│ │ ├── VcpCodeNames.cs # VCP code name lookup
|
||||||
|
│ │ └── VcpValueNames.cs # VCP value name lookup
|
||||||
|
│ └── PathConstants.cs # File path constants
|
||||||
│
|
│
|
||||||
├── PowerDisplay/ # WinUI 3 application
|
├── PowerDisplay/ # WinUI 3 application
|
||||||
│ ├── Assets/ # App icons and images
|
│ ├── Assets/ # App icons and images
|
||||||
│ ├── Common/
|
│ ├── Configuration/
|
||||||
│ │ ├── Debouncer/
|
│ │ └── AppConstants.cs # Application constants
|
||||||
│ │ │ └── SimpleDebouncer.cs # Slider input debouncing
|
|
||||||
│ │ └── Models/
|
|
||||||
│ │ └── Monitor.cs # UI-layer monitor model
|
|
||||||
│ ├── Converters/ # XAML value converters
|
│ ├── Converters/ # XAML value converters
|
||||||
│ ├── Helpers/
|
│ ├── Helpers/
|
||||||
│ │ ├── DisplayChangeWatcher.cs # Monitor hot-plug detection (WinRT DeviceWatcher)
|
│ │ ├── DisplayChangeWatcher.cs # Monitor hot-plug detection (WinRT DeviceWatcher)
|
||||||
│ │ ├── DisplayRotationService.cs # Display rotation control
|
|
||||||
│ │ ├── MonitorManager.cs # Discovery orchestrator
|
│ │ ├── MonitorManager.cs # Discovery orchestrator
|
||||||
│ │ ├── NativeMethodsHelper.cs # Window positioning
|
│ │ ├── NativeEventWaiter.cs # Windows Event waiting
|
||||||
|
│ │ ├── ResourceLoaderInstance.cs # Resource loader singleton
|
||||||
|
│ │ ├── SettingsDeepLink.cs # Deep link to Settings UI
|
||||||
│ │ ├── TrayIconService.cs # System tray integration
|
│ │ ├── TrayIconService.cs # System tray integration
|
||||||
│ │ └── WindowHelpers.cs # Window utilities
|
│ │ ├── TypePreservation.cs # AOT type preservation
|
||||||
│ ├── Strings/ # Localization resources
|
│ │ └── WindowHelper.cs # Window utilities
|
||||||
|
│ ├── PowerDisplayXAML/
|
||||||
|
│ │ ├── App.xaml / App.xaml.cs # Application entry point
|
||||||
|
│ │ ├── MainWindow.xaml / .cs # Main UI window
|
||||||
|
│ │ ├── IdentifyWindow.xaml / .cs # Monitor identify overlay
|
||||||
|
│ │ └── MonitorIcon.xaml / .cs # Monitor icon control
|
||||||
|
│ ├── Serialization/
|
||||||
|
│ │ └── JsonSourceGenerationContext.cs # JSON source generation
|
||||||
|
│ ├── Services/
|
||||||
|
│ │ └── LightSwitchService.cs # LightSwitch theme change handling
|
||||||
|
│ ├── Strings/ # Localization resources (en-us)
|
||||||
│ ├── Styles/ # Custom control styles
|
│ ├── Styles/ # Custom control styles
|
||||||
|
│ ├── Telemetry/
|
||||||
|
│ │ └── Events/
|
||||||
|
│ │ └── PowerDisplayStartEvent.cs # Telemetry event
|
||||||
│ ├── ViewModels/
|
│ ├── ViewModels/
|
||||||
|
│ │ ├── InputSourceItem.cs # Input source dropdown item
|
||||||
│ │ ├── MainViewModel.cs # Main VM (partial class)
|
│ │ ├── MainViewModel.cs # Main VM (partial class)
|
||||||
│ │ ├── MainViewModel.Monitors.cs # Monitor discovery methods
|
│ │ ├── MainViewModel.Monitors.cs # Monitor discovery methods
|
||||||
│ │ ├── MainViewModel.Profiles.cs # Profile management methods
|
|
||||||
│ │ ├── MainViewModel.Settings.cs # Settings persistence methods
|
│ │ ├── MainViewModel.Settings.cs # Settings persistence methods
|
||||||
│ │ └── MonitorViewModel.cs # Per-monitor VM
|
│ │ └── MonitorViewModel.cs # Per-monitor VM
|
||||||
│ └── Views/
|
│ └── Program.cs # Application entry point
|
||||||
│ ├── MainWindow.xaml # Main UI window
|
│
|
||||||
│ └── MainWindow.xaml.cs
|
├── PowerDisplay.Lib.UnitTests/ # Unit tests
|
||||||
|
│ ├── MccsCapabilitiesParserTests.cs
|
||||||
|
│ └── MonitorMatchingHelperTests.cs
|
||||||
│
|
│
|
||||||
└── PowerDisplayModuleInterface/ # C++ DLL (module interface)
|
└── PowerDisplayModuleInterface/ # C++ DLL (module interface)
|
||||||
├── dllmain.cpp # PowertoyModuleIface impl
|
├── dllmain.cpp # PowertoyModuleIface impl
|
||||||
├── Constants.h # Module constants
|
├── Constants.h # Module constants (event names, timeouts)
|
||||||
|
├── resource.h # Resource definitions
|
||||||
├── pch.h / pch.cpp # Precompiled headers
|
├── pch.h / pch.cpp # Precompiled headers
|
||||||
└── trace.h / trace.cpp # Telemetry tracing
|
└── Trace.h / Trace.cpp # ETW telemetry tracing
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -236,7 +279,7 @@ flowchart TB
|
|||||||
subgraph WindowsEvents["Windows Events (IPC)"]
|
subgraph WindowsEvents["Windows Events (IPC)"]
|
||||||
direction LR
|
direction LR
|
||||||
ShowToggleEvents["Show/Toggle/Terminate<br/>Events"]
|
ShowToggleEvents["Show/Toggle/Terminate<br/>Events"]
|
||||||
ThemeChangedEvent["ThemeChanged<br/>Event"]
|
ThemeChangedEvent["ThemeChanged<br/>Events"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph PowerDisplayModule["PowerDisplay Module"]
|
subgraph PowerDisplayModule["PowerDisplay Module"]
|
||||||
@@ -245,18 +288,22 @@ flowchart TB
|
|||||||
MonitorViewModel
|
MonitorViewModel
|
||||||
MonitorManager
|
MonitorManager
|
||||||
DisplayChangeWatcher["DisplayChangeWatcher<br/>(Hot-Plug Detection)"]
|
DisplayChangeWatcher["DisplayChangeWatcher<br/>(Hot-Plug Detection)"]
|
||||||
|
LightSwitchService["LightSwitchService<br/>(Theme Handler)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph PowerDisplayLib["PowerDisplay.Lib"]
|
subgraph PowerDisplayLib["PowerDisplay.Lib"]
|
||||||
subgraph Services
|
subgraph Services
|
||||||
LightSwitchListener
|
|
||||||
ProfileService
|
ProfileService
|
||||||
MonitorStateManager
|
MonitorStateManager
|
||||||
|
DisplayRotationService
|
||||||
end
|
end
|
||||||
subgraph Drivers
|
subgraph Drivers
|
||||||
DdcCiController
|
DdcCiController
|
||||||
WmiController
|
WmiController
|
||||||
end
|
end
|
||||||
|
subgraph Helpers
|
||||||
|
PnpIdHelper["PnpIdHelper<br/>(Manufacturer Names)"]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -277,10 +324,10 @@ flowchart TB
|
|||||||
|
|
||||||
%% Windows Events to App
|
%% Windows Events to App
|
||||||
ShowToggleEvents --> MainViewModel
|
ShowToggleEvents --> MainViewModel
|
||||||
ThemeChangedEvent --> LightSwitchListener
|
ThemeChangedEvent --> LightSwitchService
|
||||||
|
|
||||||
%% App internal (MainViewModel owns LightSwitchListener)
|
%% App internal
|
||||||
LightSwitchListener -.->|"ThemeChanged event"| MainViewModel
|
LightSwitchService -.->|"Get profile name"| MainViewModel
|
||||||
MainViewModel --> MonitorViewModel
|
MainViewModel --> MonitorViewModel
|
||||||
MonitorViewModel --> MonitorManager
|
MonitorViewModel --> MonitorManager
|
||||||
DisplayChangeWatcher -.->|"DisplayChanged event"| MainViewModel
|
DisplayChangeWatcher -.->|"DisplayChanged event"| MainViewModel
|
||||||
@@ -289,6 +336,10 @@ flowchart TB
|
|||||||
MainViewModel --> ProfileService
|
MainViewModel --> ProfileService
|
||||||
MonitorViewModel --> MonitorStateManager
|
MonitorViewModel --> MonitorStateManager
|
||||||
MonitorManager --> Drivers
|
MonitorManager --> Drivers
|
||||||
|
MonitorManager --> DisplayRotationService
|
||||||
|
|
||||||
|
%% Helpers used during discovery
|
||||||
|
WmiController --> PnpIdHelper
|
||||||
|
|
||||||
%% Services to Storage
|
%% Services to Storage
|
||||||
ProfileService --> ProfilesJson
|
ProfileService --> ProfilesJson
|
||||||
@@ -297,6 +348,8 @@ flowchart TB
|
|||||||
%% Drivers to Hardware
|
%% Drivers to Hardware
|
||||||
DdcCiController -->|"DDC/CI"| ExternalMonitor
|
DdcCiController -->|"DDC/CI"| ExternalMonitor
|
||||||
WmiController -->|"WMI"| LaptopDisplay
|
WmiController -->|"WMI"| LaptopDisplay
|
||||||
|
DisplayRotationService -->|"ChangeDisplaySettingsEx"| ExternalMonitor
|
||||||
|
DisplayRotationService -->|"ChangeDisplaySettingsEx"| LaptopDisplay
|
||||||
|
|
||||||
%% Styling
|
%% Styling
|
||||||
style ExternalInputs fill:#e3f2fd,stroke:#1976d2
|
style ExternalInputs fill:#e3f2fd,stroke:#1976d2
|
||||||
@@ -306,6 +359,7 @@ flowchart TB
|
|||||||
style PowerDisplayLib fill:#c8e6c9,stroke:#388e3c
|
style PowerDisplayLib fill:#c8e6c9,stroke:#388e3c
|
||||||
style Services fill:#a5d6a7,stroke:#2e7d32
|
style Services fill:#a5d6a7,stroke:#2e7d32
|
||||||
style Drivers fill:#ffccbc,stroke:#e64a19
|
style Drivers fill:#ffccbc,stroke:#e64a19
|
||||||
|
style Helpers fill:#dcedc8,stroke:#689f38
|
||||||
style Storage fill:#e1bee7,stroke:#8e24aa
|
style Storage fill:#e1bee7,stroke:#8e24aa
|
||||||
style Hardware fill:#b2dfdb,stroke:#00897b
|
style Hardware fill:#b2dfdb,stroke:#00897b
|
||||||
```
|
```
|
||||||
@@ -371,12 +425,12 @@ flowchart TB
|
|||||||
end
|
end
|
||||||
|
|
||||||
subgraph WMIStack["WMI Stack"]
|
subgraph WMIStack["WMI Stack"]
|
||||||
WmiLight["WmiLight Library<br/>(Native AOT compatible)"]
|
WmiLight["WmiLight Library<br/>(Native AOT compatible,<br/>NuGet package)"]
|
||||||
|
PnpHelper["PnpIdHelper<br/>(Manufacturer name lookup)"]
|
||||||
|
|
||||||
subgraph WMIClasses["WMI Classes (root\\WMI)"]
|
subgraph WMIClasses["WMI Classes (root\\WMI)"]
|
||||||
WmiMonBright["WmiMonitorBrightness"]
|
WmiMonBright["WmiMonitorBrightness"]
|
||||||
WmiMonBrightMethods["WmiMonitorBrightnessMethods"]
|
WmiMonBrightMethods["WmiMonitorBrightnessMethods"]
|
||||||
WmiMonID["WmiMonitorID"]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -402,9 +456,9 @@ flowchart TB
|
|||||||
Dxva2 -->|"I2C/DDC"| ExtMon
|
Dxva2 -->|"I2C/DDC"| ExtMon
|
||||||
|
|
||||||
WMI --> WmiLight
|
WMI --> WmiLight
|
||||||
|
WMI --> PnpHelper
|
||||||
WmiLight --> WmiMonBright
|
WmiLight --> WmiMonBright
|
||||||
WmiLight --> WmiMonBrightMethods
|
WmiLight --> WmiMonBrightMethods
|
||||||
WmiLight --> WmiMonID
|
|
||||||
|
|
||||||
WmiMonBrightMethods -->|"WMI Provider"| LaptopMon
|
WmiMonBrightMethods -->|"WMI Provider"| LaptopMon
|
||||||
|
|
||||||
@@ -420,36 +474,38 @@ classDiagram
|
|||||||
class IMonitorController {
|
class IMonitorController {
|
||||||
<<interface>>
|
<<interface>>
|
||||||
+Name: string
|
+Name: string
|
||||||
+DiscoverMonitorsAsync() IEnumerable~Monitor~
|
+DiscoverMonitorsAsync(cancellationToken) IEnumerable~Monitor~
|
||||||
+CanControlMonitorAsync(monitor) bool
|
+GetBrightnessAsync(monitor, cancellationToken) VcpFeatureValue
|
||||||
+GetBrightnessAsync(monitor) BrightnessInfo
|
+SetBrightnessAsync(monitor, brightness, cancellationToken) MonitorOperationResult
|
||||||
+SetBrightnessAsync(monitor, brightness) MonitorOperationResult
|
+SetContrastAsync(monitor, contrast, cancellationToken) MonitorOperationResult
|
||||||
+SetContrastAsync(monitor, contrast) MonitorOperationResult
|
+SetVolumeAsync(monitor, volume, cancellationToken) MonitorOperationResult
|
||||||
+SetVolumeAsync(monitor, volume) MonitorOperationResult
|
+GetColorTemperatureAsync(monitor, cancellationToken) VcpFeatureValue
|
||||||
+GetColorTemperatureAsync(monitor) BrightnessInfo
|
+SetColorTemperatureAsync(monitor, vcpValue, cancellationToken) MonitorOperationResult
|
||||||
+SetColorTemperatureAsync(monitor, vcpValue) MonitorOperationResult
|
+GetInputSourceAsync(monitor, cancellationToken) VcpFeatureValue
|
||||||
+GetInputSourceAsync(monitor) BrightnessInfo
|
+SetInputSourceAsync(monitor, inputSource, cancellationToken) MonitorOperationResult
|
||||||
+SetInputSourceAsync(monitor, inputSource) MonitorOperationResult
|
|
||||||
+GetCapabilitiesStringAsync(monitor) string
|
|
||||||
+Dispose()
|
+Dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
class DdcCiController {
|
class DdcCiController {
|
||||||
-_handleManager: PhysicalMonitorHandleManager
|
-_handleManager: PhysicalMonitorHandleManager
|
||||||
+Name: "DDC/CI"
|
-_discoveryHelper: MonitorDiscoveryHelper
|
||||||
|
+Name: "DDC/CI Monitor Controller"
|
||||||
+DiscoverMonitorsAsync()
|
+DiscoverMonitorsAsync()
|
||||||
+SetBrightnessAsync()
|
+GetCapabilitiesStringAsync(monitor) string
|
||||||
-GetVcpFeatureAsync()
|
-GetVcpFeatureAsync(monitor, vcpCode, featureName)
|
||||||
-SetVcpFeatureAsync()
|
-SetVcpFeatureAsync(monitor, vcpCode, value)
|
||||||
-QuickConnectionCheck()
|
-CollectCandidateMonitorsAsync()
|
||||||
|
-FetchCapabilitiesInParallelAsync()
|
||||||
|
-CreateValidMonitors()
|
||||||
}
|
}
|
||||||
|
|
||||||
class WmiController {
|
class WmiController {
|
||||||
+Name: "WMI"
|
+Name: "WMI Monitor Controller"
|
||||||
+DiscoverMonitorsAsync()
|
+DiscoverMonitorsAsync()
|
||||||
+SetBrightnessAsync()
|
-BuildInstanceNameQuery()
|
||||||
-GetWmiMonitorBrightness()
|
-ExtractHardwareIdFromInstanceName()
|
||||||
-SetWmiMonitorBrightness()
|
-GetMonitorDisplayInfoByHardwareId()
|
||||||
|
-ClassifyWmiError()
|
||||||
}
|
}
|
||||||
|
|
||||||
IMonitorController <|.. DdcCiController
|
IMonitorController <|.. DdcCiController
|
||||||
@@ -458,6 +514,313 @@ classDiagram
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Monitor Identification: Handles, IDs, and Names
|
||||||
|
|
||||||
|
Understanding how Windows identifies monitors is critical for PowerDisplay's operation.
|
||||||
|
Different Windows APIs use different identifiers, and PowerDisplay must correlate these
|
||||||
|
to provide a unified view across DDC/CI and WMI subsystems.
|
||||||
|
|
||||||
|
#### Windows Display Subsystem Overview
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph WindowsAPIs["Windows Display APIs"]
|
||||||
|
EnumDisplayMonitors["EnumDisplayMonitors<br/>(User32.dll)"]
|
||||||
|
QueryDisplayConfig["QueryDisplayConfig<br/>(User32.dll)"]
|
||||||
|
GetPhysicalMonitors["GetPhysicalMonitorsFromHMONITOR<br/>(Dxva2.dll)"]
|
||||||
|
WmiMonitor["WMI root\\WMI<br/>(WmiLight)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Identifiers["Monitor Identifiers"]
|
||||||
|
HMONITOR["HMONITOR<br/>(Logical Monitor Handle)"]
|
||||||
|
GdiDeviceName["GDI Device Name<br/>(e.g., \\\\.\\DISPLAY1)"]
|
||||||
|
PhysicalHandle["Physical Monitor Handle<br/>(IntPtr for DDC/CI)"]
|
||||||
|
DevicePath["Device Path<br/>(Unique per target)"]
|
||||||
|
HardwareId["Hardware ID<br/>(e.g., DEL41B4)"]
|
||||||
|
InstanceName["WMI Instance Name<br/>(e.g., DISPLAY\\BOE0900\\...)"]
|
||||||
|
MonitorNumber["Monitor Number<br/>(1-based, matches Windows Settings)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
EnumDisplayMonitors --> HMONITOR
|
||||||
|
HMONITOR --> GdiDeviceName
|
||||||
|
GetPhysicalMonitors --> PhysicalHandle
|
||||||
|
|
||||||
|
QueryDisplayConfig --> GdiDeviceName
|
||||||
|
QueryDisplayConfig --> DevicePath
|
||||||
|
QueryDisplayConfig --> HardwareId
|
||||||
|
QueryDisplayConfig --> MonitorNumber
|
||||||
|
|
||||||
|
WmiMonitor --> InstanceName
|
||||||
|
InstanceName --> HardwareId
|
||||||
|
|
||||||
|
style HMONITOR fill:#e3f2fd
|
||||||
|
style GdiDeviceName fill:#fff3e0
|
||||||
|
style PhysicalHandle fill:#c8e6c9
|
||||||
|
style DevicePath fill:#f3e5f5
|
||||||
|
style HardwareId fill:#ffccbc
|
||||||
|
style InstanceName fill:#ffe0b2
|
||||||
|
style MonitorNumber fill:#b2dfdb
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Identifier Definitions
|
||||||
|
|
||||||
|
| Identifier | Source | Format | Example | Scope |
|
||||||
|
|------------|--------|--------|---------|-------|
|
||||||
|
| **HMONITOR** | `EnumDisplayMonitors` | `IntPtr` | `0x00010001` | Logical monitor (may represent multiple physical monitors in clone mode) |
|
||||||
|
| **GDI Device Name** | `GetMonitorInfo` / `QueryDisplayConfig` | String | `\\.\DISPLAY1` | Adapter output; multiple targets can share same GDI name in mirror mode |
|
||||||
|
| **Physical Monitor Handle** | `GetPhysicalMonitorsFromHMONITOR` | `IntPtr` | `0x00000B14` | DDC/CI communication handle; valid for `GetVCPFeature` / `SetVCPFeature` |
|
||||||
|
| **Device Path** | `QueryDisplayConfig` | String | `\\?\DISPLAY#DEL41B4#5&12a3b4c&0&UID123#{...}` | Unique per target; used as primary key in `MonitorDisplayInfo` |
|
||||||
|
| **Hardware ID** | EDID (via `QueryDisplayConfig`) | String | `DEL41B4` | Manufacturer (3-char PnP ID) + Product Code (4-char hex); identifies monitor model |
|
||||||
|
| **WMI Instance Name** | `WmiMonitorBrightness` | String | `DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0` | WMI object identifier; contains hardware ID in second segment |
|
||||||
|
| **Monitor Number** | `QueryDisplayConfig` path index | Integer | `1`, `2`, `3` | 1-based; matches Windows Settings → Display → "Identify" feature |
|
||||||
|
|
||||||
|
#### DDC/CI Monitor Discovery Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant App as PowerDisplay
|
||||||
|
participant Enum as EnumDisplayMonitors
|
||||||
|
participant Info as GetMonitorInfo
|
||||||
|
participant QDC as QueryDisplayConfig
|
||||||
|
participant Phys as GetPhysicalMonitors
|
||||||
|
participant DDC as DDC/CI (I2C)
|
||||||
|
|
||||||
|
App->>Enum: EnumDisplayMonitors(callback)
|
||||||
|
Enum-->>App: HMONITOR handles
|
||||||
|
|
||||||
|
loop For each HMONITOR
|
||||||
|
App->>Info: GetMonitorInfo(hMonitor)
|
||||||
|
Info-->>App: GDI Device Name (e.g., "\\.\DISPLAY1")
|
||||||
|
|
||||||
|
App->>Phys: GetPhysicalMonitorsFromHMONITOR(hMonitor)
|
||||||
|
Phys-->>App: Physical Monitor Handle(s) + Description
|
||||||
|
end
|
||||||
|
|
||||||
|
App->>QDC: QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS)
|
||||||
|
QDC-->>App: MonitorDisplayInfo[] (DevicePath, GdiDeviceName, HardwareId, MonitorNumber)
|
||||||
|
|
||||||
|
Note over App: Match Physical Handles to MonitorDisplayInfo<br/>using GDI Device Name
|
||||||
|
|
||||||
|
loop For each Physical Handle
|
||||||
|
App->>DDC: GetCapabilitiesStringLength(handle)
|
||||||
|
DDC-->>App: Capabilities length
|
||||||
|
App->>DDC: CapabilitiesRequestAndCapabilitiesReply(handle)
|
||||||
|
DDC-->>App: Capabilities string (MCCS format)
|
||||||
|
end
|
||||||
|
|
||||||
|
Note over App: Create Monitor objects with:<br/>- Handle (Physical Monitor Handle)<br/>- MonitorNumber (from QueryDisplayConfig)<br/>- GdiDeviceName (for rotation APIs)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### WMI Monitor Discovery Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant App as PowerDisplay
|
||||||
|
participant WMI as WmiLight
|
||||||
|
participant QDC as QueryDisplayConfig
|
||||||
|
participant PnP as PnpIdHelper
|
||||||
|
|
||||||
|
App->>WMI: Query WmiMonitorBrightness
|
||||||
|
WMI-->>App: InstanceName, CurrentBrightness
|
||||||
|
|
||||||
|
Note over App: Extract HardwareId from InstanceName<br/>"DISPLAY\BOE0900\..." → "BOE0900"
|
||||||
|
|
||||||
|
App->>QDC: GetAllMonitorDisplayInfo()
|
||||||
|
QDC-->>App: MonitorDisplayInfo[] (keyed by DevicePath)
|
||||||
|
|
||||||
|
Note over App: Match WMI monitor to QueryDisplayConfig<br/>by comparing HardwareId
|
||||||
|
|
||||||
|
App->>PnP: GetBuiltInDisplayName("BOE0900")
|
||||||
|
PnP-->>App: "BOE Built-in Display"
|
||||||
|
|
||||||
|
Note over App: Create Monitor objects with:<br/>- InstanceName (for WMI queries)<br/>- MonitorNumber (from QueryDisplayConfig)<br/>- GdiDeviceName (for rotation APIs)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Key Relationships
|
||||||
|
|
||||||
|
##### GDI Device Name ↔ Physical Monitors
|
||||||
|
|
||||||
|
```
|
||||||
|
HMONITOR (Logical)
|
||||||
|
│
|
||||||
|
├── GetMonitorInfo() → GDI Device Name "\\.\DISPLAY1"
|
||||||
|
│
|
||||||
|
└── GetPhysicalMonitorsFromHMONITOR()
|
||||||
|
│
|
||||||
|
├── Physical Monitor 0 (Handle: 0x0B14, Desc: "Dell U2722D")
|
||||||
|
└── Physical Monitor 1 (Handle: 0x0B18, Desc: "Dell U2722D") [Mirror mode]
|
||||||
|
```
|
||||||
|
|
||||||
|
In **mirror/clone mode**, multiple physical monitors share the same GDI device name.
|
||||||
|
QueryDisplayConfig returns multiple paths with the same `GdiDeviceName` but different
|
||||||
|
`DevicePath` values, allowing us to distinguish them.
|
||||||
|
|
||||||
|
##### DisplayPort Daisy Chain (MST - Multi-Stream Transport)
|
||||||
|
|
||||||
|
**Daisy chaining** allows multiple monitors to be connected in series through a single
|
||||||
|
DisplayPort output using MST (Multi-Stream Transport) technology. This creates unique
|
||||||
|
challenges for monitor identification.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ DP ┌─────────────┐ DP ┌─────────────┐
|
||||||
|
│ GPU │─────────▶│ Monitor A │─────────▶│ Monitor B │
|
||||||
|
│ │ │ (MST Hub) │ │ (End) │
|
||||||
|
└─────────────┘ └─────────────┘ └─────────────┘
|
||||||
|
│
|
||||||
|
│ Single physical DP port
|
||||||
|
▼
|
||||||
|
Multiple logical displays (DISPLAY1, DISPLAY2)
|
||||||
|
```
|
||||||
|
|
||||||
|
**How Windows Handles MST:**
|
||||||
|
|
||||||
|
| Aspect | Behavior |
|
||||||
|
|--------|----------|
|
||||||
|
| **HMONITOR** | Each daisy-chained monitor gets its own HMONITOR |
|
||||||
|
| **GDI Device Name** | Each monitor gets a unique GDI name (e.g., `\\.\DISPLAY1`, `\\.\DISPLAY2`) |
|
||||||
|
| **Physical Monitor Handle** | Each monitor has its own physical handle for DDC/CI |
|
||||||
|
| **Device Path** | Unique for each monitor in the chain |
|
||||||
|
| **Hardware ID** | Different if monitors are different models; same if identical models |
|
||||||
|
|
||||||
|
**MST vs Clone Mode Comparison:**
|
||||||
|
|
||||||
|
```
|
||||||
|
MST Daisy Chain (Extended Desktop):
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ HMONITOR_1 │ HMONITOR_2 │ HMONITOR_3 │
|
||||||
|
│ GDI: \\.\DISPLAY1 │ GDI: \\.\DISPLAY2 │ GDI: \\.\DISPLAY3│
|
||||||
|
│ Physical Handle: A │ Physical Handle: B │ Physical Handle: C│
|
||||||
|
│ DevicePath: unique1 │ DevicePath: unique2 │ DevicePath: unique3│
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
|
Each monitor = independent logical display with unique identifiers
|
||||||
|
|
||||||
|
Clone/Mirror Mode:
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ HMONITOR_1 │
|
||||||
|
│ GDI: \\.\DISPLAY1 │
|
||||||
|
│ Physical Handle: A Physical Handle: B │
|
||||||
|
│ DevicePath: unique1 DevicePath: unique2 │
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
|
Multiple monitors share same HMONITOR and GDI name
|
||||||
|
```
|
||||||
|
|
||||||
|
**PowerDisplay Handling of MST:**
|
||||||
|
|
||||||
|
1. **Discovery**: `EnumDisplayMonitors` returns separate HMONITOR for each MST monitor
|
||||||
|
2. **Physical Handles**: `GetPhysicalMonitorsFromHMONITOR` returns one handle per HMONITOR
|
||||||
|
3. **Matching**: QueryDisplayConfig provides unique DevicePath for each MST target
|
||||||
|
4. **DDC/CI**: Each monitor in the chain can be controlled independently via its handle
|
||||||
|
|
||||||
|
**Identifying Same-Model Monitors in Daisy Chain:**
|
||||||
|
|
||||||
|
When multiple identical monitors are daisy-chained (same Hardware ID), PowerDisplay
|
||||||
|
distinguishes them using:
|
||||||
|
|
||||||
|
- **MonitorNumber**: Different path index in QueryDisplayConfig (1, 2, 3...)
|
||||||
|
- **DevicePath**: Unique system-generated path for each target
|
||||||
|
- **Monitor.Id**: Format `DDC_{HardwareId}_{MonitorNumber}` ensures uniqueness
|
||||||
|
|
||||||
|
Example with two identical Dell U2722D monitors:
|
||||||
|
```
|
||||||
|
Monitor 1: Id = "DDC_DEL41B4_1", MonitorNumber = 1
|
||||||
|
Monitor 2: Id = "DDC_DEL41B4_2", MonitorNumber = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Connection Mode Summary
|
||||||
|
|
||||||
|
| Mode | HMONITOR | GDI Device Name | Physical Handles | Use Case |
|
||||||
|
|------|----------|-----------------|------------------|----------|
|
||||||
|
| **Standard** (separate cables) | 1 per monitor | Unique per monitor | 1 per HMONITOR | Most common setup |
|
||||||
|
| **Clone/Mirror** | 1 shared | Shared | Multiple per HMONITOR | Presentation, duplication |
|
||||||
|
| **MST Daisy Chain** | 1 per monitor | Unique per monitor | 1 per HMONITOR | Reduced cable clutter |
|
||||||
|
| **USB-C/Thunderbolt Hub** | 1 per monitor | Unique per monitor | 1 per HMONITOR | Laptop docking |
|
||||||
|
|
||||||
|
**Key Insight**: From PowerDisplay's perspective, MST daisy chain and standard multi-cable
|
||||||
|
setups behave identically - each monitor appears as an independent display with unique
|
||||||
|
identifiers. Only clone/mirror mode requires special handling due to shared HMONITOR/GDI names.
|
||||||
|
|
||||||
|
##### Hardware ID Composition
|
||||||
|
|
||||||
|
```
|
||||||
|
Hardware ID: "DEL41B4"
|
||||||
|
───┬───
|
||||||
|
│
|
||||||
|
┌────────────┴────────────┐
|
||||||
|
│ │
|
||||||
|
"DEL" "41B4"
|
||||||
|
│ │
|
||||||
|
PnP Manufacturer ID Product Code
|
||||||
|
(3 chars, EDID bytes (4 hex chars,
|
||||||
|
8-9, compressed) EDID bytes 10-11)
|
||||||
|
```
|
||||||
|
|
||||||
|
The **PnP Manufacturer ID** is a 3-character code assigned by Microsoft (formerly UEFI Forum).
|
||||||
|
Common laptop display manufacturers:
|
||||||
|
|
||||||
|
| PnP ID | Manufacturer |
|
||||||
|
|--------|--------------|
|
||||||
|
| `BOE` | BOE Technology |
|
||||||
|
| `LGD` | LG Display |
|
||||||
|
| `AUO` | AU Optronics |
|
||||||
|
| `CMN` | Chi Mei Innolux |
|
||||||
|
| `SDC` | Samsung Display |
|
||||||
|
| `SHP` | Sharp |
|
||||||
|
| `LEN` | Lenovo |
|
||||||
|
| `DEL` | Dell |
|
||||||
|
|
||||||
|
##### WMI Instance Name Parsing
|
||||||
|
|
||||||
|
```
|
||||||
|
WMI InstanceName: "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0"
|
||||||
|
───────┬─────── ──────────┬───────────
|
||||||
|
│ │
|
||||||
|
Segment 1: "DISPLAY" Segment 3: Device instance
|
||||||
|
(Constant prefix)
|
||||||
|
│
|
||||||
|
Segment 2: "BOE0900"
|
||||||
|
│
|
||||||
|
Hardware ID
|
||||||
|
(Used for matching with QueryDisplayConfig)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Monitor Number (Windows Display Settings)
|
||||||
|
|
||||||
|
The `MonitorNumber` in PowerDisplay corresponds exactly to the number shown in:
|
||||||
|
- Windows Settings → System → Display → "Identify" button
|
||||||
|
- The number overlay that appears on each display
|
||||||
|
|
||||||
|
This is derived from the **path index** in `QueryDisplayConfig`:
|
||||||
|
- `paths[0]` → Monitor 1
|
||||||
|
- `paths[1]` → Monitor 2
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
#### Display Rotation and GDI Device Name
|
||||||
|
|
||||||
|
The `ChangeDisplaySettingsEx` API requires the **GDI Device Name** to target a specific display:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Correct: Target specific display by GDI name
|
||||||
|
ChangeDisplaySettingsEx("\\.\DISPLAY2", &devMode, NULL, 0, NULL);
|
||||||
|
|
||||||
|
// Wrong: NULL affects primary display only
|
||||||
|
ChangeDisplaySettingsEx(NULL, &devMode, NULL, 0, NULL);
|
||||||
|
```
|
||||||
|
|
||||||
|
PowerDisplay stores `GdiDeviceName` in each `Monitor` object specifically for rotation operations.
|
||||||
|
|
||||||
|
#### Cross-Reference Summary
|
||||||
|
|
||||||
|
| PowerDisplay Property | DDC/CI Source | WMI Source |
|
||||||
|
|-----------------------|---------------|------------|
|
||||||
|
| `Monitor.Id` | `"DDC_{HardwareId}_{MonitorNumber}"` | `"WMI_{HardwareId}_{MonitorNumber}"` |
|
||||||
|
| `Monitor.Handle` | Physical Monitor Handle | N/A (uses InstanceName) |
|
||||||
|
| `Monitor.InstanceName` | N/A | WMI InstanceName |
|
||||||
|
| `Monitor.GdiDeviceName` | QueryDisplayConfig | QueryDisplayConfig |
|
||||||
|
| `Monitor.MonitorNumber` | QueryDisplayConfig path index | QueryDisplayConfig (matched by HardwareId) |
|
||||||
|
| `Monitor.Name` | EDID FriendlyName or Description | PnpIdHelper.GetBuiltInDisplayName() |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Settings UI and PowerDisplay Interaction Architecture
|
### Settings UI and PowerDisplay Interaction Architecture
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -553,13 +916,12 @@ flowchart TB
|
|||||||
end
|
end
|
||||||
|
|
||||||
subgraph PowerDisplayModule["PowerDisplay Module (C#)"]
|
subgraph PowerDisplayModule["PowerDisplay Module (C#)"]
|
||||||
subgraph Listener["LightSwitchListener Service"]
|
subgraph App["PowerDisplay App"]
|
||||||
EventWait["WaitAny([lightEvent, darkEvent])<br/>(Background Thread)"]
|
EventWaiter["NativeEventWaiter<br/>(Background Thread)"]
|
||||||
ReadSettings["ReadProfileFromLightSwitchSettings(isLightMode)"]
|
LightSwitchSvc["LightSwitchService<br/>(Static Helper)"]
|
||||||
ThemeChangedEvent["ThemeChanged Event"]
|
MainViewModel["MainViewModel"]
|
||||||
end
|
end
|
||||||
|
|
||||||
MainViewModel["MainViewModel"]
|
|
||||||
ProfileService["ProfileService"]
|
ProfileService["ProfileService"]
|
||||||
MonitorVMs["MonitorViewModels"]
|
MonitorVMs["MonitorViewModels"]
|
||||||
Controllers["IMonitorController"]
|
Controllers["IMonitorController"]
|
||||||
@@ -586,13 +948,12 @@ flowchart TB
|
|||||||
NotifyPD -->|"isLight=true"| LightEvent
|
NotifyPD -->|"isLight=true"| LightEvent
|
||||||
NotifyPD -->|"isLight=false"| DarkEvent
|
NotifyPD -->|"isLight=false"| DarkEvent
|
||||||
|
|
||||||
%% PowerDisplay flow - theme determined from event, not registry
|
%% PowerDisplay flow - theme determined from event
|
||||||
LightEvent -->|"WaitAny index=0"| EventWait
|
LightEvent -->|"Event signaled"| EventWaiter
|
||||||
DarkEvent -->|"WaitAny index=1"| EventWait
|
DarkEvent -->|"Event signaled"| EventWaiter
|
||||||
EventWait -->|"isLightMode from event"| ReadSettings
|
EventWaiter -->|"isLightMode"| LightSwitchSvc
|
||||||
ReadSettings -->|"Get profile for theme"| LSSettingsJson
|
LightSwitchSvc -->|"GetProfileForTheme()"| LSSettingsJson
|
||||||
ReadSettings --> ThemeChangedEvent
|
LightSwitchSvc -->|"Profile name"| MainViewModel
|
||||||
ThemeChangedEvent --> MainViewModel
|
|
||||||
MainViewModel -->|"LoadProfiles()"| ProfileService
|
MainViewModel -->|"LoadProfiles()"| ProfileService
|
||||||
ProfileService <--> PDProfilesJson
|
ProfileService <--> PDProfilesJson
|
||||||
MainViewModel -->|"ApplyProfileAsync()"| MonitorVMs
|
MainViewModel -->|"ApplyProfileAsync()"| MonitorVMs
|
||||||
@@ -601,6 +962,7 @@ flowchart TB
|
|||||||
|
|
||||||
style LightSwitchModule fill:#ffccbc
|
style LightSwitchModule fill:#ffccbc
|
||||||
style PowerDisplayModule fill:#c8e6c9
|
style PowerDisplayModule fill:#c8e6c9
|
||||||
|
style App fill:#a5d6a7
|
||||||
style WindowsEvents fill:#e3f2fd
|
style WindowsEvents fill:#e3f2fd
|
||||||
style FileSystem fill:#fffde7
|
style FileSystem fill:#fffde7
|
||||||
```
|
```
|
||||||
@@ -803,7 +1165,8 @@ sequenceDiagram
|
|||||||
participant System as Windows System
|
participant System as Windows System
|
||||||
participant LightSwitch as LightSwitchStateManager (C++)
|
participant LightSwitch as LightSwitchStateManager (C++)
|
||||||
participant WinEvent as Windows Events
|
participant WinEvent as Windows Events
|
||||||
participant Listener as LightSwitchListener
|
participant EventWaiter as NativeEventWaiter
|
||||||
|
participant LSSvc as LightSwitchService
|
||||||
participant MainVM as MainViewModel
|
participant MainVM as MainViewModel
|
||||||
participant ProfileService
|
participant ProfileService
|
||||||
participant MonitorVM as MonitorViewModel
|
participant MonitorVM as MonitorViewModel
|
||||||
@@ -825,24 +1188,21 @@ sequenceDiagram
|
|||||||
LightSwitch->>WinEvent: SetEvent("Local\\PowerToys_LightSwitch_DarkTheme")
|
LightSwitch->>WinEvent: SetEvent("Local\\PowerToys_LightSwitch_DarkTheme")
|
||||||
end
|
end
|
||||||
|
|
||||||
Note over Listener: Background thread waiting<br/>on both Light and Dark events
|
Note over EventWaiter: Background thread waiting<br/>on both Light and Dark events
|
||||||
Listener->>WinEvent: WaitAny([lightEvent, darkEvent]) returns index
|
EventWaiter->>WinEvent: WaitAny([lightEvent, darkEvent]) returns index
|
||||||
|
|
||||||
Note over Listener: Theme determined from event:<br/>index 0 = Light, index 1 = Dark
|
Note over EventWaiter: Theme determined from event:<br/>index 0 = Light, index 1 = Dark
|
||||||
Listener->>Listener: ProcessThemeChange(isLightMode)
|
EventWaiter->>LSSvc: GetProfileForTheme(isLightMode)
|
||||||
Listener->>Listener: ReadProfileFromLightSwitchSettings(isLightMode)
|
LSSvc->>LSSvc: Read LightSwitch/settings.json
|
||||||
Note over Listener: Read LightSwitch/settings.json<br/>Get profile for known theme
|
LSSvc-->>EventWaiter: profileName (or null)
|
||||||
|
|
||||||
Listener->>MainVM: ThemeChanged?.Invoke(ThemeChangedEventArgs)
|
EventWaiter->>MainVM: Dispatch to UI thread with profileName
|
||||||
|
|
||||||
MainVM->>MainVM: OnLightSwitchThemeChanged()
|
|
||||||
MainVM->>ProfileService: LoadProfiles()
|
MainVM->>ProfileService: LoadProfiles()
|
||||||
ProfileService-->>MainVM: PowerDisplayProfiles
|
ProfileService-->>MainVM: PowerDisplayProfiles
|
||||||
|
|
||||||
MainVM->>ProfileService: GetProfile(profileName)
|
MainVM->>MainVM: Find profile by name
|
||||||
ProfileService-->>MainVM: PowerDisplayProfile
|
MainVM->>MainVM: ApplyProfileAsync(profile.MonitorSettings)
|
||||||
|
|
||||||
MainVM->>MainVM: ApplyProfileAsync(profile)
|
|
||||||
|
|
||||||
loop For each ProfileMonitorSetting
|
loop For each ProfileMonitorSetting
|
||||||
MainVM->>MainVM: Find MonitorViewModel by InternalName
|
MainVM->>MainVM: Find MonitorViewModel by InternalName
|
||||||
@@ -871,6 +1231,12 @@ sequenceDiagram
|
|||||||
MonitorVM->>Controller: SetColorTemperatureAsync(monitor, vcpValue)
|
MonitorVM->>Controller: SetColorTemperatureAsync(monitor, vcpValue)
|
||||||
Controller->>Monitor: SetVCPFeature(0x14, vcpValue)
|
Controller->>Monitor: SetVCPFeature(0x14, vcpValue)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alt Orientation specified
|
||||||
|
MainVM->>MonitorVM: SetOrientationAsync(orientation)
|
||||||
|
MonitorVM->>Controller: SetRotationAsync(monitor, orientation)
|
||||||
|
Controller->>Monitor: ChangeDisplaySettingsEx
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Note over MainVM: await Task.WhenAll(updateTasks)
|
Note over MainVM: await Task.WhenAll(updateTasks)
|
||||||
@@ -988,31 +1354,76 @@ classDiagram
|
|||||||
class Monitor {
|
class Monitor {
|
||||||
+string Id
|
+string Id
|
||||||
+string Name
|
+string Name
|
||||||
+string HardwareId
|
|
||||||
+string DeviceKey
|
|
||||||
+string CommunicationMethod
|
+string CommunicationMethod
|
||||||
|
+string InstanceName
|
||||||
|
+string GdiDeviceName
|
||||||
|
+int MonitorNumber
|
||||||
+int CurrentBrightness
|
+int CurrentBrightness
|
||||||
|
+int MinBrightness
|
||||||
|
+int MaxBrightness
|
||||||
+int CurrentContrast
|
+int CurrentContrast
|
||||||
+int CurrentVolume
|
+int CurrentVolume
|
||||||
+int CurrentColorTemperature
|
+int CurrentColorTemperature
|
||||||
+int CurrentInputSource
|
+int CurrentInputSource
|
||||||
|
+int Orientation
|
||||||
|
+bool IsAvailable
|
||||||
|
+bool SupportsContrast
|
||||||
|
+bool SupportsVolume
|
||||||
|
+bool SupportsColorTemperature
|
||||||
|
+bool SupportsInputSource
|
||||||
|
+MonitorCapabilities Capabilities
|
||||||
+VcpCapabilities VcpCapabilitiesInfo
|
+VcpCapabilities VcpCapabilitiesInfo
|
||||||
+IntPtr PhysicalMonitorHandle
|
+string CapabilitiesRaw
|
||||||
+PropertyChanged event
|
+IntPtr Handle
|
||||||
|
+DateTime LastUpdate
|
||||||
|
+UpdateStatus(brightness, isAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
class VcpCapabilities {
|
class VcpCapabilities {
|
||||||
+Dictionary~int, VcpCodeInfo~ SupportedVcpCodes
|
+string Raw
|
||||||
+string RawCapabilitiesString
|
+string Model
|
||||||
|
+string Type
|
||||||
|
+string Protocol
|
||||||
|
+string MccsVersion
|
||||||
|
+List~byte~ SupportedCommands
|
||||||
|
+Dictionary~byte, VcpCodeInfo~ SupportedVcpCodes
|
||||||
|
+List~WindowCapability~ Windows
|
||||||
|
+bool HasWindowSupport
|
||||||
+SupportsVcpCode(code) bool
|
+SupportsVcpCode(code) bool
|
||||||
+GetSupportedValues(code) List~int~
|
+GetVcpCodeInfo(code) VcpCodeInfo
|
||||||
|
+GetSupportedValues(code) IReadOnlyList~int~
|
||||||
|
+GetVcpCodesAsHexStrings() List~string~
|
||||||
}
|
}
|
||||||
|
|
||||||
class VcpCodeInfo {
|
class VcpCodeInfo {
|
||||||
+int Code
|
+byte Code
|
||||||
+string Name
|
+string Name
|
||||||
+List~int~ SupportedValues
|
+IReadOnlyList~int~ SupportedValues
|
||||||
+bool IsReadOnly
|
+bool HasDiscreteValues
|
||||||
|
+bool IsContinuous
|
||||||
|
+string FormattedCode
|
||||||
|
+string FormattedTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
class VcpFeatureValue {
|
||||||
|
+int Current
|
||||||
|
+int Minimum
|
||||||
|
+int Maximum
|
||||||
|
+bool IsValid
|
||||||
|
+ToPercentage() int
|
||||||
|
+static Invalid VcpFeatureValue
|
||||||
|
}
|
||||||
|
|
||||||
|
class MonitorCapabilities {
|
||||||
|
<<flags enum>>
|
||||||
|
None
|
||||||
|
Brightness
|
||||||
|
Contrast
|
||||||
|
Volume
|
||||||
|
ColorTemperature
|
||||||
|
InputSource
|
||||||
|
Wmi
|
||||||
|
DdcCi
|
||||||
}
|
}
|
||||||
|
|
||||||
class PowerDisplayProfile {
|
class PowerDisplayProfile {
|
||||||
@@ -1024,23 +1435,22 @@ classDiagram
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ProfileMonitorSetting {
|
class ProfileMonitorSetting {
|
||||||
+string HardwareId
|
|
||||||
+string MonitorInternalName
|
+string MonitorInternalName
|
||||||
|
+int MonitorNumber
|
||||||
+int? Brightness
|
+int? Brightness
|
||||||
+int? Contrast
|
+int? Contrast
|
||||||
+int? Volume
|
+int? Volume
|
||||||
+int? ColorTemperatureVcp
|
+int? ColorTemperatureVcp
|
||||||
|
+int? Orientation
|
||||||
}
|
}
|
||||||
|
|
||||||
class PowerDisplayProfiles {
|
class PowerDisplayProfiles {
|
||||||
+List~PowerDisplayProfile~ Profiles
|
+List~PowerDisplayProfile~ Profiles
|
||||||
+DateTime LastUpdated
|
+DateTime LastUpdated
|
||||||
+GetProfile(name) PowerDisplayProfile
|
|
||||||
+SetProfile(profile)
|
|
||||||
+RemoveProfile(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Monitor "1" --> "0..1" VcpCapabilities
|
Monitor "1" --> "0..1" VcpCapabilities
|
||||||
|
Monitor "1" --> "1" MonitorCapabilities
|
||||||
VcpCapabilities "1" --> "*" VcpCodeInfo
|
VcpCapabilities "1" --> "*" VcpCodeInfo
|
||||||
PowerDisplayProfiles "1" --> "*" PowerDisplayProfile
|
PowerDisplayProfiles "1" --> "*" PowerDisplayProfile
|
||||||
PowerDisplayProfile "1" --> "*" ProfileMonitorSetting
|
PowerDisplayProfile "1" --> "*" ProfileMonitorSetting
|
||||||
@@ -1111,14 +1521,25 @@ classDiagram
|
|||||||
|
|
||||||
## Future Considerations
|
## Future Considerations
|
||||||
|
|
||||||
|
### Already Implemented (removed from backlog)
|
||||||
|
|
||||||
|
- **Monitor Hot-Plug**: `DisplayChangeWatcher` uses WinRT DeviceWatcher + DisplayMonitor API with 1-second debouncing
|
||||||
|
- **Display Rotation**: `DisplayRotationService` uses Windows ChangeDisplaySettingsEx API
|
||||||
|
- **LightSwitch Integration**: Automatic profile application on theme changes via `LightSwitchService`
|
||||||
|
- **Monitor Identification**: Overlay windows showing monitor numbers via `IdentifyWindow`
|
||||||
|
- **Mirror Mode Support**: Correct orientation sync for multiple monitors sharing the same GDI device name
|
||||||
|
|
||||||
|
### Potential Future Enhancements
|
||||||
|
|
||||||
1. **Hardware Cursor Brightness**: Support for displays with hardware cursor brightness
|
1. **Hardware Cursor Brightness**: Support for displays with hardware cursor brightness
|
||||||
2. **Multi-GPU Support**: Better handling of monitors across different GPUs
|
2. **Multi-GPU Support**: Better handling of monitors across different GPUs
|
||||||
3. ~~**Monitor Hot-Plug**: Improved detection and recovery for monitor connect/disconnect~~ **Implemented** - `DisplayChangeWatcher` uses WinRT DeviceWatcher + DisplayMonitor API with 1-second debouncing
|
3. **Advanced Color Management**: Integration with Windows Color Management APIs (HDR, ICC profiles)
|
||||||
4. **Advanced Color Management**: Integration with Windows Color Management
|
4. **Scheduled Profiles**: Time-based automatic profile switching (beyond LightSwitch integration)
|
||||||
5. **Scheduled Profiles**: Time-based automatic profile switching (beyond LightSwitch)
|
5. **Monitor Groups**: Ability to control multiple monitors as a single entity
|
||||||
6. **Monitor Groups**: Ability to control multiple monitors as a single entity
|
6. **Remote Control**: Network-based control for multi-system setups
|
||||||
7. **Remote Control**: Network-based control for multi-system setups
|
7. **PIP/PBP Control**: Picture-in-Picture and Picture-by-Picture configuration (VcpCapabilities already parses window capabilities)
|
||||||
8. ~~**Display Rotation**: Control display orientation~~ **Implemented** - `DisplayRotationService` uses Windows ChangeDisplaySettingsEx API
|
8. **Power State Control**: Monitor power on/off via VCP code 0xD6
|
||||||
|
9. **Input Source Scheduling**: Automatic input switching based on time or application
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -323,48 +323,6 @@ void dispatch_received_json(const std::wstring& json_to_parse)
|
|||||||
Logger::error(L"Failed to process get all hotkey conflicts request");
|
Logger::error(L"Failed to process get all hotkey conflicts request");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (name == L"powerdisplay_response")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Forward PowerDisplay response messages to Settings UI
|
|
||||||
// PowerDisplay sends monitor information via IPC
|
|
||||||
std::unique_lock lock{ ipc_mutex };
|
|
||||||
if (current_settings_ipc)
|
|
||||||
{
|
|
||||||
current_settings_ipc->send(value.Stringify().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
Logger::error(L"Failed to forward PowerDisplay response to Settings");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (name == L"powerdisplay_command")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Forward command from Settings UI to PowerDisplay module
|
|
||||||
Logger::trace(L"Received command from Settings UI to PowerDisplay");
|
|
||||||
|
|
||||||
// Find PowerDisplay module and send the command
|
|
||||||
auto moduleIt = modules().find(L"PowerDisplay");
|
|
||||||
if (moduleIt != modules().end())
|
|
||||||
{
|
|
||||||
// Use call_custom_action to send the command
|
|
||||||
// The command should contain an action field
|
|
||||||
moduleIt->second->call_custom_action(value.Stringify().c_str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::warn(L"PowerDisplay module not found, cannot send command");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
Logger::error(L"Failed to forward command to PowerDisplay");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1004,29 +962,3 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value)
|
|||||||
|
|
||||||
return ESettingsWindowNames::Dashboard;
|
return ESettingsWindowNames::Dashboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global function for PowerDisplay module to send messages to Settings UI
|
|
||||||
void send_powerdisplay_message_to_settings_ui(const wchar_t* message)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Logger::trace(L"Sending PowerDisplay message to Settings UI");
|
|
||||||
|
|
||||||
std::unique_lock lock{ ipc_mutex };
|
|
||||||
if (current_settings_ipc)
|
|
||||||
{
|
|
||||||
// Wrap the message in powerdisplay_response format
|
|
||||||
json::JsonObject wrapper;
|
|
||||||
wrapper.SetNamedValue(L"powerdisplay_response", json::JsonValue::Parse(message));
|
|
||||||
current_settings_ipc->send(wrapper.Stringify().c_str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::warn(L"current_settings_ipc is null, cannot send to Settings UI");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const std::exception&)
|
|
||||||
{
|
|
||||||
Logger::error(L"Exception while sending PowerDisplay message to Settings UI");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -48,6 +48,3 @@ void close_settings_window();
|
|||||||
void open_oobe_window();
|
void open_oobe_window();
|
||||||
void open_scoobe_window();
|
void open_scoobe_window();
|
||||||
void open_flyout();
|
void open_flyout();
|
||||||
|
|
||||||
// PowerDisplay IPC support
|
|
||||||
void send_powerdisplay_message_to_settings_ui(const wchar_t* message);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user