mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
1077 lines
36 KiB
Markdown
1077 lines
36 KiB
Markdown
|
|
# PowerDisplay Module Design Document
|
||
|
|
|
||
|
|
## Table of Contents
|
||
|
|
|
||
|
|
1. [Background](#background)
|
||
|
|
2. [Problem Statement](#problem-statement)
|
||
|
|
3. [Goals and Non-Goals](#goals-and-non-goals)
|
||
|
|
4. [Technical Terminology](#technical-terminology)
|
||
|
|
5. [Architecture Overview](#architecture-overview)
|
||
|
|
6. [Component Design](#component-design)
|
||
|
|
7. [Data Flow and Communication](#data-flow-and-communication)
|
||
|
|
8. [Sequence Diagrams](#sequence-diagrams)
|
||
|
|
9. [Data Models](#data-models)
|
||
|
|
10. [Future Considerations](#future-considerations)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Background
|
||
|
|
|
||
|
|
PowerDisplay is a PowerToys module designed to provide unified control over display
|
||
|
|
settings across multiple monitors. Users often work with multiple displays (external monitors or laptop screens) and need a
|
||
|
|
convenient way to adjust display parameters such as brightness, contrast, color
|
||
|
|
temperature, volume, and input source without navigating through individual monitor
|
||
|
|
OSD menus.
|
||
|
|
|
||
|
|
The module leverages two primary technologies for monitor control:
|
||
|
|
|
||
|
|
1. **DDC/CI (Display Data Channel Command Interface)** - For external monitors
|
||
|
|
2. **WMI (Windows Management Instrumentation)** - For internal(laptop) displays
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Problem Statement
|
||
|
|
|
||
|
|
Users with multiple monitors face several challenges:
|
||
|
|
|
||
|
|
1. **Fragmented Control**: Each monitor requires separate OSD navigation
|
||
|
|
2. **Inconsistent Brightness**: Difficult to maintain uniform brightness across displays
|
||
|
|
3. **No Profile Support**: Cannot quickly switch display configurations for different
|
||
|
|
scenarios (gaming, productivity, movie watching)
|
||
|
|
4. **Theme Integration Gap**: No automatic display adjustment when switching between
|
||
|
|
light and dark themes
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Goals
|
||
|
|
|
||
|
|
- Provide unified control for brightness, contrast, volume, color temperature, and
|
||
|
|
input source across all connected monitors
|
||
|
|
- Support both DDC/CI (external monitors) and WMI (laptop displays)
|
||
|
|
- Integrate with PowerToys Settings UI for configuration
|
||
|
|
- Integrate with LightSwitch module for automatic profile application on theme changes
|
||
|
|
- Support user-defined profiles for quick configuration switching
|
||
|
|
- Support global hotkey activation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Technical Terminology
|
||
|
|
|
||
|
|
### DDC/CI (Display Data Channel Command Interface)
|
||
|
|
|
||
|
|
**DDC/CI** is a VESA standard (defined in the DDC specification) that allows
|
||
|
|
bidirectional communication between a computer and a display over the I2C bus
|
||
|
|
embedded in display cables.
|
||
|
|
|
||
|
|
Most monitors (external monitors) support DDC/CI, allowing applications to read and modify settings
|
||
|
|
like brightness and contrast programmatically. But unfortunately, even if a monitor supports DDC/CI,
|
||
|
|
they may only support a limited subset of VCP codes, or have buggy implementations. PowerDisplay relys on
|
||
|
|
the monitor-reported capabilities string to determine supported features. But if your monitor's manufacturer
|
||
|
|
has a poor DDC/CI implementation, some features may not work as expected. And we can do nothing about it.
|
||
|
|
|
||
|
|
**Key Concepts:**
|
||
|
|
|
||
|
|
| Term | Description |
|
||
|
|
|------|-------------|
|
||
|
|
| **VCP (Virtual Control Panel)** | Standardized codes for monitor settings |
|
||
|
|
| **MCCS (Monitor Command Control Set)** | VESA standard defining VCP codes |
|
||
|
|
| **Capabilities String** | Monitor-reported string describing supported features |
|
||
|
|
|
||
|
|
**Common VCP Codes Used:**
|
||
|
|
|
||
|
|
| VCP Code | Name | Description |
|
||
|
|
|----------|------|-------------|
|
||
|
|
| `0x10` | Brightness | Display luminance (0-100) |
|
||
|
|
| `0x12` | Contrast | Display contrast ratio (0-100) |
|
||
|
|
| `0x14` | Select Color Preset | Color temperature presets (sRGB, 5000K, 6500K, etc.) |
|
||
|
|
| `0x60` | Input Source | Active video input (HDMI, DP, USB-C, etc.) |
|
||
|
|
| `0x62` | Volume | Speaker/headphone volume (0-100) |
|
||
|
|
|
||
|
|
**Official Documentation:**
|
||
|
|
- [VESA DDC/CI Standard](https://vesa.org/vesa-standards/)
|
||
|
|
- [MCCS (Monitor Control Command Set) Specification](https://vesa.org/vesa-standards/)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### WMI (Windows Management Instrumentation)
|
||
|
|
|
||
|
|
**WMI** is Microsoft's implementation of Web-Based Enterprise Management (WBEM),
|
||
|
|
providing a standardized interface for accessing management information in Windows.
|
||
|
|
For display control, WMI is primarily used for laptop internal displays that may not
|
||
|
|
support DDC/CI.
|
||
|
|
|
||
|
|
**Official Documentation:**
|
||
|
|
- [WMI Reference](https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-reference)
|
||
|
|
- [WmiMonitorBrightness](https://learn.microsoft.com/en-us/windows/win32/wmicoreprov/wmimonitorbrightness)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Architecture Overview
|
||
|
|
|
||
|
|
### High-Level Component Architecture
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
flowchart TB
|
||
|
|
subgraph PowerToys["PowerToys Ecosystem"]
|
||
|
|
Runner["Runner (PowerToys.exe)"]
|
||
|
|
SettingsUI["Settings UI (WinUI 3)"]
|
||
|
|
LightSwitch["LightSwitch Module"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph PowerDisplayModule["PowerDisplay Module"]
|
||
|
|
ModuleInterface["Module Interface (C++ DLL)"]
|
||
|
|
PowerDisplayApp["PowerDisplay App (WinUI 3)"]
|
||
|
|
PowerDisplayLib["PowerDisplay.Lib"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph External["External"]
|
||
|
|
Hardware["Display Hardware"]
|
||
|
|
Storage["Persistent Storage (JSON)"]
|
||
|
|
end
|
||
|
|
|
||
|
|
Runner -->|"Loads DLL"| ModuleInterface
|
||
|
|
Runner -->|"Hotkey Events"| ModuleInterface
|
||
|
|
SettingsUI <-->|"Named Pipes"| Runner
|
||
|
|
SettingsUI -->|"Custom Actions"| ModuleInterface
|
||
|
|
|
||
|
|
ModuleInterface <-->|"Windows Events"| PowerDisplayApp
|
||
|
|
LightSwitch -->|"Theme Changed Event"| PowerDisplayApp
|
||
|
|
|
||
|
|
PowerDisplayApp --> PowerDisplayLib
|
||
|
|
PowerDisplayLib -->|"DDC/CI + WMI"| Hardware
|
||
|
|
PowerDisplayApp <--> Storage
|
||
|
|
|
||
|
|
style Runner fill:#e1f5fe
|
||
|
|
style SettingsUI fill:#e1f5fe
|
||
|
|
style LightSwitch fill:#e1f5fe
|
||
|
|
style ModuleInterface fill:#fff3e0
|
||
|
|
style PowerDisplayApp fill:#fff3e0
|
||
|
|
style PowerDisplayLib fill:#e8f5e9
|
||
|
|
style Hardware fill:#f3e5f5
|
||
|
|
style Storage fill:#fffde7
|
||
|
|
```
|
||
|
|
|
||
|
|
This high-level view shows the module boundaries. See [Component Design](#component-design)
|
||
|
|
for internal structure details.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Project Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
src/modules/powerdisplay/
|
||
|
|
├── PowerDisplay.Lib/ # Core library (shared)
|
||
|
|
│ ├── Drivers/
|
||
|
|
│ │ ├── DDC/
|
||
|
|
│ │ │ ├── DdcCiController.cs # DDC/CI implementation
|
||
|
|
│ │ │ ├── DdcCiNative.cs # P/Invoke declarations
|
||
|
|
│ │ │ ├── MonitorDiscoveryHelper.cs
|
||
|
|
│ │ │ └── PhysicalMonitorHandleManager.cs
|
||
|
|
│ │ └── WMI/
|
||
|
|
│ │ └── WmiController.cs # WMI implementation
|
||
|
|
│ ├── Interfaces/
|
||
|
|
│ │ └── IMonitorController.cs # Controller abstraction
|
||
|
|
│ ├── Models/
|
||
|
|
│ │ ├── Monitor.cs # Runtime monitor data
|
||
|
|
│ │ ├── PowerDisplayProfile.cs # Profile definition
|
||
|
|
│ │ └── ProfileMonitorSetting.cs # Per-monitor settings
|
||
|
|
│ ├── Services/
|
||
|
|
│ │ ├── ProfileService.cs # Profile persistence
|
||
|
|
│ │ ├── MonitorStateManager.cs # State persistence
|
||
|
|
│ │ └── LightSwitchListener.cs # Theme change listener
|
||
|
|
│ └── Utils/
|
||
|
|
│ ├── MccsCapabilitiesParser.cs # DDC/CI capabilities parser
|
||
|
|
│ └── ColorTemperatureHelper.cs
|
||
|
|
│
|
||
|
|
├── PowerDisplay/ # WinUI 3 application
|
||
|
|
│ ├── Core/
|
||
|
|
│ │ └── MonitorManager.cs # Discovery orchestrator
|
||
|
|
│ ├── ViewModels/
|
||
|
|
│ │ ├── MainViewModel.cs
|
||
|
|
│ │ └── MonitorViewModel.cs
|
||
|
|
│ └── Views/
|
||
|
|
│ └── MainWindow.xaml
|
||
|
|
│
|
||
|
|
└── PowerDisplayModuleInterface/ # C++ DLL (module interface)
|
||
|
|
├── dllmain.cpp # PowertoyModuleIface impl
|
||
|
|
└── Constants.h # Module constants
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Component Design
|
||
|
|
|
||
|
|
### PowerDisplay Module Internal Structure
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
flowchart TB
|
||
|
|
subgraph ExternalInputs["External Inputs"]
|
||
|
|
ModuleInterface["Module Interface<br/>(C++ DLL)"]
|
||
|
|
LightSwitch["LightSwitch Module"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph WindowsEvents["Windows Events (IPC)"]
|
||
|
|
direction LR
|
||
|
|
ShowToggleEvents["Show/Toggle/Terminate<br/>Events"]
|
||
|
|
ThemeChangedEvent["ThemeChanged<br/>Event"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph PowerDisplayModule["PowerDisplay Module"]
|
||
|
|
subgraph PowerDisplayApp["PowerDisplay App (WinUI 3)"]
|
||
|
|
MainViewModel
|
||
|
|
MonitorViewModel
|
||
|
|
MonitorManager
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph PowerDisplayLib["PowerDisplay.Lib"]
|
||
|
|
subgraph Services
|
||
|
|
LightSwitchListener
|
||
|
|
ProfileService
|
||
|
|
MonitorStateManager
|
||
|
|
end
|
||
|
|
subgraph Drivers
|
||
|
|
DdcCiController
|
||
|
|
WmiController
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph Storage["Persistent Storage"]
|
||
|
|
SettingsJson[("settings.json")]
|
||
|
|
ProfilesJson[("profiles.json")]
|
||
|
|
MonitorStateJson[("monitor_state.json")]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph Hardware["Display Hardware"]
|
||
|
|
ExternalMonitor["External Monitor"]
|
||
|
|
LaptopDisplay["Laptop Display"]
|
||
|
|
end
|
||
|
|
|
||
|
|
%% External to Windows Events
|
||
|
|
ModuleInterface -->|"SetEvent()"| ShowToggleEvents
|
||
|
|
LightSwitch -->|"SetEvent()"| ThemeChangedEvent
|
||
|
|
|
||
|
|
%% Windows Events to App
|
||
|
|
ShowToggleEvents --> MainViewModel
|
||
|
|
ThemeChangedEvent --> LightSwitchListener
|
||
|
|
|
||
|
|
%% App internal (MainViewModel owns LightSwitchListener)
|
||
|
|
LightSwitchListener -.->|"ThemeChanged event"| MainViewModel
|
||
|
|
MainViewModel --> MonitorViewModel
|
||
|
|
MonitorViewModel --> MonitorManager
|
||
|
|
|
||
|
|
%% App to Lib services
|
||
|
|
MainViewModel --> ProfileService
|
||
|
|
MonitorViewModel --> MonitorStateManager
|
||
|
|
MonitorManager --> Drivers
|
||
|
|
|
||
|
|
%% Services to Storage
|
||
|
|
ProfileService --> ProfilesJson
|
||
|
|
MonitorStateManager --> MonitorStateJson
|
||
|
|
|
||
|
|
%% Drivers to Hardware
|
||
|
|
DdcCiController -->|"DDC/CI"| ExternalMonitor
|
||
|
|
WmiController -->|"WMI"| LaptopDisplay
|
||
|
|
|
||
|
|
%% Styling
|
||
|
|
style ExternalInputs fill:#e3f2fd,stroke:#1976d2
|
||
|
|
style WindowsEvents fill:#fce4ec,stroke:#c2185b
|
||
|
|
style PowerDisplayModule fill:#fff8e1,stroke:#f57c00,stroke-width:2px
|
||
|
|
style PowerDisplayApp fill:#ffe0b2,stroke:#ef6c00
|
||
|
|
style PowerDisplayLib fill:#c8e6c9,stroke:#388e3c
|
||
|
|
style Services fill:#a5d6a7,stroke:#2e7d32
|
||
|
|
style Drivers fill:#ffccbc,stroke:#e64a19
|
||
|
|
style Storage fill:#e1bee7,stroke:#8e24aa
|
||
|
|
style Hardware fill:#b2dfdb,stroke:#00897b
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### DDC/CI and WMI Interaction Architecture
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
flowchart TB
|
||
|
|
subgraph Application["Application Layer"]
|
||
|
|
MM["MonitorManager"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph Abstraction["Abstraction Layer"]
|
||
|
|
IMC["IMonitorController Interface"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph Controllers["Controller Implementations"]
|
||
|
|
DDC["DdcCiController"]
|
||
|
|
WMI["WmiController"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph DDCStack["DDC/CI Stack"]
|
||
|
|
DDCNative["DdcCiNative<br/>(P/Invoke)"]
|
||
|
|
PhysicalMonitorMgr["PhysicalMonitorHandleManager"]
|
||
|
|
MonitorDiscovery["MonitorDiscoveryHelper"]
|
||
|
|
CapParser["MccsCapabilitiesParser"]
|
||
|
|
|
||
|
|
subgraph Win32["Win32 APIs"]
|
||
|
|
User32["User32.dll<br/>EnumDisplayMonitors<br/>GetMonitorInfo"]
|
||
|
|
Dxva2["Dxva2.dll<br/>GetVCPFeature<br/>SetVCPFeature<br/>Capabilities"]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph WMIStack["WMI Stack"]
|
||
|
|
WmiLight["WmiLight Library<br/>(Native AOT compatible)"]
|
||
|
|
|
||
|
|
subgraph WMIClasses["WMI Classes (root\\WMI)"]
|
||
|
|
WmiMonBright["WmiMonitorBrightness"]
|
||
|
|
WmiMonBrightMethods["WmiMonitorBrightnessMethods"]
|
||
|
|
WmiMonID["WmiMonitorID"]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph Hardware["Hardware Layer"]
|
||
|
|
ExtMon["External Monitor<br/>(DDC/CI capable)"]
|
||
|
|
LaptopMon["Laptop Display<br/>(WMI only)"]
|
||
|
|
end
|
||
|
|
|
||
|
|
MM --> IMC
|
||
|
|
IMC -.-> DDC
|
||
|
|
IMC -.-> WMI
|
||
|
|
|
||
|
|
DDC --> DDCNative
|
||
|
|
DDC --> PhysicalMonitorMgr
|
||
|
|
DDC --> MonitorDiscovery
|
||
|
|
DDC --> CapParser
|
||
|
|
|
||
|
|
DDCNative --> User32
|
||
|
|
DDCNative --> Dxva2
|
||
|
|
MonitorDiscovery --> User32
|
||
|
|
PhysicalMonitorMgr --> Dxva2
|
||
|
|
|
||
|
|
Dxva2 -->|"I2C/DDC"| ExtMon
|
||
|
|
|
||
|
|
WMI --> WmiLight
|
||
|
|
WmiLight --> WmiMonBright
|
||
|
|
WmiLight --> WmiMonBrightMethods
|
||
|
|
WmiLight --> WmiMonID
|
||
|
|
|
||
|
|
WmiMonBrightMethods -->|"WMI Provider"| LaptopMon
|
||
|
|
|
||
|
|
style IMC fill:#bbdefb
|
||
|
|
style DDC fill:#c8e6c9
|
||
|
|
style WMI fill:#ffccbc
|
||
|
|
```
|
||
|
|
|
||
|
|
### IMonitorController Interface Methods
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
classDiagram
|
||
|
|
class IMonitorController {
|
||
|
|
<<interface>>
|
||
|
|
+Name: string
|
||
|
|
+DiscoverMonitorsAsync() IEnumerable~Monitor~
|
||
|
|
+CanControlMonitorAsync(monitor) bool
|
||
|
|
+GetBrightnessAsync(monitor) BrightnessInfo
|
||
|
|
+SetBrightnessAsync(monitor, brightness) MonitorOperationResult
|
||
|
|
+SetContrastAsync(monitor, contrast) MonitorOperationResult
|
||
|
|
+SetVolumeAsync(monitor, volume) MonitorOperationResult
|
||
|
|
+GetColorTemperatureAsync(monitor) BrightnessInfo
|
||
|
|
+SetColorTemperatureAsync(monitor, vcpValue) MonitorOperationResult
|
||
|
|
+GetInputSourceAsync(monitor) BrightnessInfo
|
||
|
|
+SetInputSourceAsync(monitor, inputSource) MonitorOperationResult
|
||
|
|
+GetCapabilitiesStringAsync(monitor) string
|
||
|
|
+Dispose()
|
||
|
|
}
|
||
|
|
|
||
|
|
class DdcCiController {
|
||
|
|
-_handleManager: PhysicalMonitorHandleManager
|
||
|
|
+Name: "DDC/CI"
|
||
|
|
+DiscoverMonitorsAsync()
|
||
|
|
+SetBrightnessAsync()
|
||
|
|
-GetVcpFeatureAsync()
|
||
|
|
-SetVcpFeatureAsync()
|
||
|
|
-QuickConnectionCheck()
|
||
|
|
}
|
||
|
|
|
||
|
|
class WmiController {
|
||
|
|
+Name: "WMI"
|
||
|
|
+DiscoverMonitorsAsync()
|
||
|
|
+SetBrightnessAsync()
|
||
|
|
-GetWmiMonitorBrightness()
|
||
|
|
-SetWmiMonitorBrightness()
|
||
|
|
}
|
||
|
|
|
||
|
|
IMonitorController <|.. DdcCiController
|
||
|
|
IMonitorController <|.. WmiController
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Settings UI and PowerDisplay Interaction Architecture
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
flowchart TB
|
||
|
|
subgraph SettingsProcess["Settings UI Process"]
|
||
|
|
SettingsPage["PowerDisplayPage.xaml"]
|
||
|
|
ViewModel["PowerDisplayViewModel"]
|
||
|
|
SettingsLib["Settings.UI.Library"]
|
||
|
|
|
||
|
|
subgraph DataModels["Data Models"]
|
||
|
|
PowerDisplaySettings["PowerDisplaySettings"]
|
||
|
|
MonitorInfo["MonitorInfo"]
|
||
|
|
ProfileOperation["ProfileOperation"]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph RunnerProcess["Runner Process"]
|
||
|
|
Runner["PowerToys.exe"]
|
||
|
|
NamedPipe["Named Pipe IPC"]
|
||
|
|
ModuleInterface["PowerDisplayModuleInterface.dll"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph PowerDisplayProcess["PowerDisplay Process"]
|
||
|
|
App["PowerToys.PowerDisplay.exe"]
|
||
|
|
MainVM["MainViewModel"]
|
||
|
|
|
||
|
|
subgraph EventListeners["Event Listeners"]
|
||
|
|
RefreshEvent["RefreshMonitors Event"]
|
||
|
|
ApplyColorTempEvent["ApplyColorTemp Event"]
|
||
|
|
ApplyProfileEvent["ApplyProfile Event"]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph FileSystem["File System"]
|
||
|
|
SettingsJson["PowerDisplay/settings.json"]
|
||
|
|
ProfilesJson["PowerDisplay/profiles.json"]
|
||
|
|
end
|
||
|
|
|
||
|
|
%% Settings UI to Runner
|
||
|
|
SettingsPage --> ViewModel
|
||
|
|
ViewModel --> SettingsLib
|
||
|
|
ViewModel -->|"SendDefaultIPCMessage()"| NamedPipe
|
||
|
|
NamedPipe --> Runner
|
||
|
|
Runner -->|"set_config()"| ModuleInterface
|
||
|
|
Runner -->|"call_custom_action()"| ModuleInterface
|
||
|
|
|
||
|
|
%% Settings persistence
|
||
|
|
ViewModel <-->|"Read/Write"| SettingsJson
|
||
|
|
ViewModel <-->|"Read/Write"| ProfilesJson
|
||
|
|
|
||
|
|
%% Module Interface to PowerDisplay App
|
||
|
|
ModuleInterface -->|"SetEvent()"| RefreshEvent
|
||
|
|
ModuleInterface -->|"SetEvent()"| ApplyColorTempEvent
|
||
|
|
ModuleInterface -->|"SetEvent()"| ApplyProfileEvent
|
||
|
|
|
||
|
|
%% PowerDisplay App event handling
|
||
|
|
RefreshEvent --> MainVM
|
||
|
|
ApplyColorTempEvent --> MainVM
|
||
|
|
ApplyProfileEvent --> MainVM
|
||
|
|
MainVM <-->|"Read Settings"| SettingsJson
|
||
|
|
MainVM <-->|"Read/Write Profiles"| ProfilesJson
|
||
|
|
|
||
|
|
style SettingsProcess fill:#e3f2fd
|
||
|
|
style RunnerProcess fill:#fff3e0
|
||
|
|
style PowerDisplayProcess fill:#e8f5e9
|
||
|
|
style FileSystem fill:#fffde7
|
||
|
|
```
|
||
|
|
|
||
|
|
### Windows Events for IPC
|
||
|
|
|
||
|
|
| Event Name | Constant | Direction | Purpose |
|
||
|
|
|------------|----------|-----------|---------|
|
||
|
|
| `Local\PowerToysPowerDisplay-ShowEvent-*` | `SHOW_POWER_DISPLAY_EVENT` | Runner → App | Show window |
|
||
|
|
| `Local\PowerToysPowerDisplay-ToggleEvent-*` | `TOGGLE_POWER_DISPLAY_EVENT` | Runner → App | Toggle visibility |
|
||
|
|
| `Local\PowerToysPowerDisplay-TerminateEvent-*` | `TERMINATE_POWER_DISPLAY_EVENT` | Runner → App | Terminate process |
|
||
|
|
| `Local\PowerToysPowerDisplay-RefreshMonitorsEvent-*` | `REFRESH_POWER_DISPLAY_MONITORS_EVENT` | Settings → App | Refresh monitor list |
|
||
|
|
| `Local\PowerToysPowerDisplay-ApplyColorTemperatureEvent-*` | `APPLY_COLOR_TEMPERATURE_POWER_DISPLAY_EVENT` | Settings → App | Apply color temp |
|
||
|
|
| `Local\PowerToysPowerDisplay-ApplyProfileEvent-*` | `APPLY_PROFILE_POWER_DISPLAY_EVENT` | Settings → App | Apply profile |
|
||
|
|
| `Local\PowerToys_LightSwitch_LightTheme` | `LightSwitchLightThemeEventName` | LightSwitch → App | Apply light mode profile |
|
||
|
|
| `Local\PowerToys_LightSwitch_DarkTheme` | `LightSwitchDarkThemeEventName` | LightSwitch → App | Apply dark mode profile |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### LightSwitch Profile Integration Architecture
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
flowchart TB
|
||
|
|
subgraph LightSwitchModule["LightSwitch Module (C++)"]
|
||
|
|
StateManager["LightSwitchStateManager"]
|
||
|
|
ThemeEval["Theme Evaluation<br/>(Time/System)"]
|
||
|
|
LightSwitchSettings["LightSwitchSettings"]
|
||
|
|
NotifyPD["NotifyPowerDisplay(isLight)"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph PowerDisplayModule["PowerDisplay Module (C#)"]
|
||
|
|
subgraph Listener["LightSwitchListener Service"]
|
||
|
|
EventWait["WaitAny([lightEvent, darkEvent])<br/>(Background Thread)"]
|
||
|
|
ReadSettings["ReadProfileFromLightSwitchSettings(isLightMode)"]
|
||
|
|
ThemeChangedEvent["ThemeChanged Event"]
|
||
|
|
end
|
||
|
|
|
||
|
|
MainViewModel["MainViewModel"]
|
||
|
|
ProfileService["ProfileService"]
|
||
|
|
MonitorVMs["MonitorViewModels"]
|
||
|
|
Controllers["IMonitorController"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph WindowsEvents["Windows Events"]
|
||
|
|
LightEvent["Local\\PowerToys_LightSwitch_LightTheme"]
|
||
|
|
DarkEvent["Local\\PowerToys_LightSwitch_DarkTheme"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph FileSystem["File System"]
|
||
|
|
LSSettingsJson["LightSwitch/settings.json<br/>{lightProfile, darkProfile}"]
|
||
|
|
PDProfilesJson["PowerDisplay/profiles.json<br/>{profiles: [...]}"]
|
||
|
|
end
|
||
|
|
|
||
|
|
subgraph Hardware["Hardware"]
|
||
|
|
Monitors["Connected Monitors"]
|
||
|
|
end
|
||
|
|
|
||
|
|
%% LightSwitch flow
|
||
|
|
ThemeEval -->|"Time boundary<br/>or manual"| StateManager
|
||
|
|
StateManager --> LightSwitchSettings
|
||
|
|
StateManager --> NotifyPD
|
||
|
|
NotifyPD -->|"isLight=true"| LightEvent
|
||
|
|
NotifyPD -->|"isLight=false"| DarkEvent
|
||
|
|
|
||
|
|
%% PowerDisplay flow - theme determined from event, not registry
|
||
|
|
LightEvent -->|"WaitAny index=0"| EventWait
|
||
|
|
DarkEvent -->|"WaitAny index=1"| EventWait
|
||
|
|
EventWait -->|"isLightMode from event"| ReadSettings
|
||
|
|
ReadSettings -->|"Get profile for theme"| LSSettingsJson
|
||
|
|
ReadSettings --> ThemeChangedEvent
|
||
|
|
ThemeChangedEvent --> MainViewModel
|
||
|
|
MainViewModel -->|"LoadProfiles()"| ProfileService
|
||
|
|
ProfileService <--> PDProfilesJson
|
||
|
|
MainViewModel -->|"ApplyProfileAsync()"| MonitorVMs
|
||
|
|
MonitorVMs --> Controllers
|
||
|
|
Controllers --> Monitors
|
||
|
|
|
||
|
|
style LightSwitchModule fill:#ffccbc
|
||
|
|
style PowerDisplayModule fill:#c8e6c9
|
||
|
|
style WindowsEvents fill:#e3f2fd
|
||
|
|
style FileSystem fill:#fffde7
|
||
|
|
```
|
||
|
|
|
||
|
|
### LightSwitch Settings JSON Structure
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"properties": {
|
||
|
|
"apply_monitor_settings": { "value": true },
|
||
|
|
"enable_light_mode_profile": { "value": true },
|
||
|
|
"light_mode_profile": { "value": "Productivity" },
|
||
|
|
"enable_dark_mode_profile": { "value": true },
|
||
|
|
"dark_mode_profile": { "value": "Night Mode" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Data Flow and Communication
|
||
|
|
|
||
|
|
### Monitor Discovery Flow
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
flowchart TB
|
||
|
|
Start([Start Discovery]) --> Init["MonitorManager.DiscoverMonitorsAsync()"]
|
||
|
|
|
||
|
|
Init --> ParallelDiscover["Parallel Discovery"]
|
||
|
|
|
||
|
|
subgraph ParallelDiscover["Parallel Controller Discovery"]
|
||
|
|
DDCDiscover["DdcCiController.DiscoverMonitorsAsync()"]
|
||
|
|
WMIDiscover["WmiController.DiscoverMonitorsAsync()"]
|
||
|
|
end
|
||
|
|
|
||
|
|
DDCDiscover --> DDCEnum["EnumDisplayMonitors()"]
|
||
|
|
DDCEnum --> DDCPhysical["GetPhysicalMonitorsFromHMONITOR()"]
|
||
|
|
DDCPhysical --> DDCCheck["Quick DDC/CI connection check"]
|
||
|
|
|
||
|
|
WMIDiscover --> WMIQuery["Query WmiMonitorBrightness"]
|
||
|
|
WMIQuery --> WMIFilter["Filter responsive displays"]
|
||
|
|
|
||
|
|
DDCCheck --> Merge["Merge Results"]
|
||
|
|
WMIFilter --> Merge
|
||
|
|
|
||
|
|
Merge --> InitLoop["For Each Monitor"]
|
||
|
|
|
||
|
|
subgraph InitLoop["Initialize Single Monitor"]
|
||
|
|
direction TB
|
||
|
|
VerifyControl["Verify Controller Access"]
|
||
|
|
GetBrightness["Get Current Brightness"]
|
||
|
|
CheckType{"CommunicationMethod<br/>contains 'DDC'?"}
|
||
|
|
|
||
|
|
subgraph DDCPath[" "]
|
||
|
|
direction TB
|
||
|
|
GetCaps["Fetch VCP Capabilities"]
|
||
|
|
ParseCaps["Parse MCCS Capabilities String"]
|
||
|
|
InitInputSource["Get Current Input Source"]
|
||
|
|
end
|
||
|
|
|
||
|
|
Done["Initialization Complete"]
|
||
|
|
end
|
||
|
|
|
||
|
|
VerifyControl --> GetBrightness
|
||
|
|
GetBrightness --> CheckType
|
||
|
|
CheckType -->|"Yes (DDC/CI)"| GetCaps
|
||
|
|
GetCaps --> ParseCaps
|
||
|
|
ParseCaps --> InitInputSource
|
||
|
|
InitInputSource --> Done
|
||
|
|
CheckType -->|"No (WMI)"| Done
|
||
|
|
|
||
|
|
InitLoop --> UpdateCollection["Update _monitors Collection"]
|
||
|
|
UpdateCollection --> FireEvent["Fire MonitorsChanged Event"]
|
||
|
|
FireEvent --> End([Discovery Complete])
|
||
|
|
|
||
|
|
style ParallelDiscover fill:#e3f2fd
|
||
|
|
style InitLoop fill:#e8f5e9
|
||
|
|
style CheckType fill:#fff3e0
|
||
|
|
```
|
||
|
|
|
||
|
|
**Note:** WMI monitors skip VCP capabilities fetching because:
|
||
|
|
1. WMI uses a different abstraction layer (`WmiMonitorBrightness` class)
|
||
|
|
2. `WmiController.GetCapabilitiesStringAsync()` returns an empty string
|
||
|
|
3. VCP codes are DDC/CI-specific and not applicable to WMI-controlled displays
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sequence Diagrams
|
||
|
|
|
||
|
|
### Sequence: Modifying Monitor Settings in Settings UI
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
sequenceDiagram
|
||
|
|
participant User
|
||
|
|
participant SettingsPage as PowerDisplayPage
|
||
|
|
participant ViewModel as PowerDisplayViewModel
|
||
|
|
participant SettingsUtils
|
||
|
|
participant Runner
|
||
|
|
participant ModuleInterface as PowerDisplayModule (C++)
|
||
|
|
participant PowerDisplayApp as PowerDisplay.exe
|
||
|
|
participant MonitorManager
|
||
|
|
participant Controller as IMonitorController
|
||
|
|
participant Monitor as Physical Monitor
|
||
|
|
|
||
|
|
User->>SettingsPage: Selects color temperature<br/>from dropdown
|
||
|
|
SettingsPage->>SettingsPage: Show confirmation dialog
|
||
|
|
User->>SettingsPage: Confirms change
|
||
|
|
|
||
|
|
SettingsPage->>ViewModel: ApplyColorTemperatureToMonitor(monitorId, vcpValue)
|
||
|
|
|
||
|
|
Note over ViewModel: Store pending operation
|
||
|
|
ViewModel->>ViewModel: _settings.Properties.PendingColorTemperatureOperation = {...}
|
||
|
|
ViewModel->>SettingsUtils: SaveSettings(settings.json)
|
||
|
|
SettingsUtils-->>ViewModel: Success
|
||
|
|
|
||
|
|
Note over ViewModel: Send IPC message
|
||
|
|
ViewModel->>Runner: SendDefaultIPCMessage(CustomAction: ApplyColorTemperature)
|
||
|
|
Runner->>ModuleInterface: call_custom_action("ApplyColorTemperature")
|
||
|
|
|
||
|
|
Note over ModuleInterface: Ensure process running
|
||
|
|
ModuleInterface->>ModuleInterface: is_process_running()
|
||
|
|
alt Process not running
|
||
|
|
ModuleInterface->>PowerDisplayApp: launch_process()
|
||
|
|
ModuleInterface->>ModuleInterface: wait_for_process_ready()
|
||
|
|
end
|
||
|
|
|
||
|
|
ModuleInterface->>PowerDisplayApp: SetEvent(ApplyColorTemperatureEvent)
|
||
|
|
|
||
|
|
Note over PowerDisplayApp: Event listener triggers
|
||
|
|
PowerDisplayApp->>PowerDisplayApp: ApplyColorTemperatureFromSettings()
|
||
|
|
PowerDisplayApp->>SettingsUtils: Read settings.json
|
||
|
|
SettingsUtils-->>PowerDisplayApp: PendingColorTemperatureOperation
|
||
|
|
|
||
|
|
PowerDisplayApp->>MonitorManager: Find monitor by ID
|
||
|
|
MonitorManager-->>PowerDisplayApp: Monitor found
|
||
|
|
|
||
|
|
PowerDisplayApp->>Controller: SetColorTemperatureAsync(monitor, vcpValue)
|
||
|
|
Controller->>Monitor: SetVCPFeature(0x14, value)
|
||
|
|
Monitor-->>Controller: Success
|
||
|
|
Controller-->>PowerDisplayApp: MonitorOperationResult.Success
|
||
|
|
|
||
|
|
Note over PowerDisplayApp: Clear pending operation
|
||
|
|
PowerDisplayApp->>SettingsUtils: Update settings.json<br/>(clear pending, update monitor value)
|
||
|
|
|
||
|
|
Note over SettingsPage: Monitor property change<br/>notification refreshes UI
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Sequence: Creating and Saving a Profile
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
sequenceDiagram
|
||
|
|
participant User
|
||
|
|
participant SettingsPage as PowerDisplayPage
|
||
|
|
participant ViewModel as PowerDisplayViewModel
|
||
|
|
participant ProfileDialog as ProfileEditorDialog
|
||
|
|
participant ProfileService
|
||
|
|
participant FileSystem as profiles.json
|
||
|
|
|
||
|
|
User->>SettingsPage: Clicks "Add Profile" button
|
||
|
|
SettingsPage->>ViewModel: ShowProfileEditor()
|
||
|
|
|
||
|
|
ViewModel->>ProfileDialog: Show(monitors, existingProfiles)
|
||
|
|
ProfileDialog->>ProfileDialog: Display monitor selection UI
|
||
|
|
|
||
|
|
User->>ProfileDialog: Enters profile name
|
||
|
|
User->>ProfileDialog: Selects monitors to include
|
||
|
|
User->>ProfileDialog: Configures settings per monitor<br/>(brightness, contrast, etc.)
|
||
|
|
User->>ProfileDialog: Clicks "Save"
|
||
|
|
|
||
|
|
ProfileDialog->>ProfileDialog: Validate inputs
|
||
|
|
Note over ProfileDialog: Check name unique,<br/>at least one monitor selected
|
||
|
|
|
||
|
|
ProfileDialog-->>ViewModel: ResultProfile (PowerDisplayProfile)
|
||
|
|
|
||
|
|
ViewModel->>ProfileService: AddOrUpdateProfile(profile)
|
||
|
|
|
||
|
|
ProfileService->>ProfileService: lock(_lock)
|
||
|
|
ProfileService->>FileSystem: Read profiles.json
|
||
|
|
FileSystem-->>ProfileService: Existing profiles
|
||
|
|
ProfileService->>ProfileService: Add/update profile in collection
|
||
|
|
ProfileService->>ProfileService: Set LastUpdated = DateTime.Now
|
||
|
|
ProfileService->>FileSystem: Write profiles.json
|
||
|
|
FileSystem-->>ProfileService: Success
|
||
|
|
ProfileService-->>ViewModel: true
|
||
|
|
|
||
|
|
ViewModel->>ViewModel: RefreshProfilesList()
|
||
|
|
ViewModel-->>SettingsPage: PropertyChanged(Profiles)
|
||
|
|
SettingsPage->>SettingsPage: Update UI with new profile
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Sequence: Applying Profile via LightSwitch Theme Change
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
sequenceDiagram
|
||
|
|
participant System as Windows System
|
||
|
|
participant LightSwitch as LightSwitchStateManager (C++)
|
||
|
|
participant WinEvent as Windows Events
|
||
|
|
participant Listener as LightSwitchListener
|
||
|
|
participant MainVM as MainViewModel
|
||
|
|
participant ProfileService
|
||
|
|
participant MonitorVM as MonitorViewModel
|
||
|
|
participant Controller as IMonitorController
|
||
|
|
participant Monitor as Physical Monitor
|
||
|
|
|
||
|
|
Note over System: Time reaches threshold<br/>or user changes theme
|
||
|
|
System->>LightSwitch: Theme change detected
|
||
|
|
|
||
|
|
LightSwitch->>LightSwitch: EvaluateAndApplyIfNeeded()
|
||
|
|
LightSwitch->>LightSwitch: ApplyTheme(isLight)
|
||
|
|
|
||
|
|
LightSwitch->>LightSwitch: NotifyPowerDisplay(isLight)
|
||
|
|
Note over LightSwitch: Check if profile enabled
|
||
|
|
|
||
|
|
alt isLight == true
|
||
|
|
LightSwitch->>WinEvent: SetEvent("Local\\PowerToys_LightSwitch_LightTheme")
|
||
|
|
else isLight == false
|
||
|
|
LightSwitch->>WinEvent: SetEvent("Local\\PowerToys_LightSwitch_DarkTheme")
|
||
|
|
end
|
||
|
|
|
||
|
|
Note over Listener: Background thread waiting<br/>on both Light and Dark events
|
||
|
|
Listener->>WinEvent: WaitAny([lightEvent, darkEvent]) returns index
|
||
|
|
|
||
|
|
Note over Listener: Theme determined from event:<br/>index 0 = Light, index 1 = Dark
|
||
|
|
Listener->>Listener: ProcessThemeChange(isLightMode)
|
||
|
|
Listener->>Listener: ReadProfileFromLightSwitchSettings(isLightMode)
|
||
|
|
Note over Listener: Read LightSwitch/settings.json<br/>Get profile for known theme
|
||
|
|
|
||
|
|
Listener->>MainVM: ThemeChanged?.Invoke(ThemeChangedEventArgs)
|
||
|
|
|
||
|
|
MainVM->>MainVM: OnLightSwitchThemeChanged()
|
||
|
|
MainVM->>ProfileService: LoadProfiles()
|
||
|
|
ProfileService-->>MainVM: PowerDisplayProfiles
|
||
|
|
|
||
|
|
MainVM->>ProfileService: GetProfile(profileName)
|
||
|
|
ProfileService-->>MainVM: PowerDisplayProfile
|
||
|
|
|
||
|
|
MainVM->>MainVM: ApplyProfileAsync(profile)
|
||
|
|
|
||
|
|
loop For each ProfileMonitorSetting
|
||
|
|
MainVM->>MainVM: Find MonitorViewModel by InternalName
|
||
|
|
|
||
|
|
alt Brightness specified
|
||
|
|
MainVM->>MonitorVM: SetBrightnessAsync(value, immediate=true)
|
||
|
|
MonitorVM->>Controller: SetBrightnessAsync(monitor, value)
|
||
|
|
Controller->>Monitor: DDC/CI or WMI call
|
||
|
|
Monitor-->>Controller: Success
|
||
|
|
end
|
||
|
|
|
||
|
|
alt Contrast specified
|
||
|
|
MainVM->>MonitorVM: SetContrastAsync(value, immediate=true)
|
||
|
|
MonitorVM->>Controller: SetContrastAsync(monitor, value)
|
||
|
|
Controller->>Monitor: SetVCPFeature(0x12, value)
|
||
|
|
end
|
||
|
|
|
||
|
|
alt Volume specified
|
||
|
|
MainVM->>MonitorVM: SetVolumeAsync(value, immediate=true)
|
||
|
|
MonitorVM->>Controller: SetVolumeAsync(monitor, value)
|
||
|
|
Controller->>Monitor: SetVCPFeature(0x62, value)
|
||
|
|
end
|
||
|
|
|
||
|
|
alt ColorTemperature specified
|
||
|
|
MainVM->>MonitorVM: SetColorTemperatureAsync(vcpValue)
|
||
|
|
MonitorVM->>Controller: SetColorTemperatureAsync(monitor, vcpValue)
|
||
|
|
Controller->>Monitor: SetVCPFeature(0x14, vcpValue)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
Note over MainVM: await Task.WhenAll(updateTasks)
|
||
|
|
MainVM->>MainVM: Log profile application complete
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Sequence: UI Slider Adjustment (Brightness)
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
sequenceDiagram
|
||
|
|
participant User
|
||
|
|
participant Slider as Brightness Slider
|
||
|
|
participant MonitorVM as MonitorViewModel
|
||
|
|
participant Debouncer as SimpleDebouncer
|
||
|
|
participant MonitorManager
|
||
|
|
participant Controller as DdcCiController
|
||
|
|
participant StateManager as MonitorStateManager
|
||
|
|
participant Monitor as Physical Monitor
|
||
|
|
|
||
|
|
User->>Slider: Drags slider (continuous)
|
||
|
|
|
||
|
|
loop During drag (multiple events)
|
||
|
|
Slider->>MonitorVM: CurrentBrightness = value
|
||
|
|
MonitorVM->>MonitorVM: SetBrightnessAsync(value, immediate=false)
|
||
|
|
MonitorVM->>Debouncer: Debounce(300ms)
|
||
|
|
Note over Debouncer: Resets timer on each call
|
||
|
|
end
|
||
|
|
|
||
|
|
User->>Slider: Releases slider
|
||
|
|
|
||
|
|
Note over Debouncer: 300ms elapsed, no new input
|
||
|
|
Debouncer->>MonitorVM: Execute debounced action
|
||
|
|
|
||
|
|
MonitorVM->>MonitorVM: ApplyBrightnessToHardwareAsync()
|
||
|
|
MonitorVM->>MonitorManager: SetBrightnessAsync(monitor, finalValue)
|
||
|
|
|
||
|
|
MonitorManager->>Controller: SetBrightnessAsync(monitor, value)
|
||
|
|
|
||
|
|
Controller->>Controller: SetVcpFeatureAsync(VcpCodeBrightness)
|
||
|
|
Controller->>Monitor: SetVCPFeature(0x10, value)
|
||
|
|
Monitor-->>Controller: OK
|
||
|
|
|
||
|
|
Controller-->>MonitorManager: MonitorOperationResult
|
||
|
|
MonitorManager-->>MonitorVM: Success/Failure
|
||
|
|
|
||
|
|
MonitorVM->>StateManager: UpdateMonitorParameter("Brightness", value)
|
||
|
|
|
||
|
|
Note over StateManager: Debounced save (2 seconds)
|
||
|
|
StateManager->>StateManager: Schedule file write
|
||
|
|
|
||
|
|
Note over StateManager: After 2s idle
|
||
|
|
StateManager->>StateManager: SaveToFile(monitor_state.json)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Sequence: Module Enable/Disable Lifecycle
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
sequenceDiagram
|
||
|
|
participant Runner as PowerToys Runner
|
||
|
|
participant ModuleInterface as PowerDisplayModule (C++)
|
||
|
|
participant PowerDisplayApp as PowerDisplay.exe
|
||
|
|
participant MonitorManager
|
||
|
|
participant EventHandles as Windows Events
|
||
|
|
|
||
|
|
Note over Runner: User enables PowerDisplay
|
||
|
|
Runner->>ModuleInterface: enable()
|
||
|
|
|
||
|
|
ModuleInterface->>ModuleInterface: m_enabled = true
|
||
|
|
ModuleInterface->>ModuleInterface: Trace::EnablePowerDisplay(true)
|
||
|
|
|
||
|
|
ModuleInterface->>ModuleInterface: is_process_running()
|
||
|
|
alt Process not running
|
||
|
|
ModuleInterface->>PowerDisplayApp: ShellExecuteExW("PowerToys.PowerDisplay.exe", pid)
|
||
|
|
PowerDisplayApp->>PowerDisplayApp: Initialize WinUI 3 App
|
||
|
|
PowerDisplayApp->>PowerDisplayApp: RegisterSingletonInstance()
|
||
|
|
PowerDisplayApp->>MonitorManager: DiscoverMonitorsAsync()
|
||
|
|
PowerDisplayApp->>PowerDisplayApp: Start event listeners
|
||
|
|
PowerDisplayApp->>EventHandles: SetEvent("Ready")
|
||
|
|
end
|
||
|
|
|
||
|
|
ModuleInterface->>ModuleInterface: m_hProcess = sei.hProcess
|
||
|
|
|
||
|
|
Note over Runner: User presses hotkey
|
||
|
|
Runner->>ModuleInterface: on_hotkey()
|
||
|
|
ModuleInterface->>EventHandles: SetEvent(ToggleEvent)
|
||
|
|
EventHandles->>PowerDisplayApp: Toggle visibility
|
||
|
|
|
||
|
|
Note over Runner: User disables PowerDisplay
|
||
|
|
Runner->>ModuleInterface: disable()
|
||
|
|
|
||
|
|
ModuleInterface->>EventHandles: ResetEvent(InvokeEvent)
|
||
|
|
ModuleInterface->>EventHandles: SetEvent(TerminateEvent)
|
||
|
|
|
||
|
|
PowerDisplayApp->>PowerDisplayApp: Receive terminate signal
|
||
|
|
PowerDisplayApp->>MonitorManager: Dispose()
|
||
|
|
PowerDisplayApp->>PowerDisplayApp: Application.Exit()
|
||
|
|
|
||
|
|
ModuleInterface->>ModuleInterface: CloseHandle(m_hProcess)
|
||
|
|
ModuleInterface->>ModuleInterface: m_enabled = false
|
||
|
|
ModuleInterface->>ModuleInterface: Trace::EnablePowerDisplay(false)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Data Models
|
||
|
|
|
||
|
|
### Core Models
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
classDiagram
|
||
|
|
class Monitor {
|
||
|
|
+string Id
|
||
|
|
+string Name
|
||
|
|
+string HardwareId
|
||
|
|
+string DeviceKey
|
||
|
|
+string CommunicationMethod
|
||
|
|
+int CurrentBrightness
|
||
|
|
+int CurrentContrast
|
||
|
|
+int CurrentVolume
|
||
|
|
+int CurrentColorTemperature
|
||
|
|
+int CurrentInputSource
|
||
|
|
+VcpCapabilities VcpCapabilitiesInfo
|
||
|
|
+IntPtr PhysicalMonitorHandle
|
||
|
|
+PropertyChanged event
|
||
|
|
}
|
||
|
|
|
||
|
|
class VcpCapabilities {
|
||
|
|
+Dictionary~int, VcpCodeInfo~ SupportedVcpCodes
|
||
|
|
+string RawCapabilitiesString
|
||
|
|
+SupportsVcpCode(code) bool
|
||
|
|
+GetSupportedValues(code) List~int~
|
||
|
|
}
|
||
|
|
|
||
|
|
class VcpCodeInfo {
|
||
|
|
+int Code
|
||
|
|
+string Name
|
||
|
|
+List~int~ SupportedValues
|
||
|
|
+bool IsReadOnly
|
||
|
|
}
|
||
|
|
|
||
|
|
class PowerDisplayProfile {
|
||
|
|
+string Name
|
||
|
|
+DateTime CreatedDate
|
||
|
|
+DateTime LastModified
|
||
|
|
+List~ProfileMonitorSetting~ MonitorSettings
|
||
|
|
+IsValid() bool
|
||
|
|
}
|
||
|
|
|
||
|
|
class ProfileMonitorSetting {
|
||
|
|
+string HardwareId
|
||
|
|
+string MonitorInternalName
|
||
|
|
+int? Brightness
|
||
|
|
+int? Contrast
|
||
|
|
+int? Volume
|
||
|
|
+int? ColorTemperatureVcp
|
||
|
|
}
|
||
|
|
|
||
|
|
class PowerDisplayProfiles {
|
||
|
|
+List~PowerDisplayProfile~ Profiles
|
||
|
|
+DateTime LastUpdated
|
||
|
|
+GetProfile(name) PowerDisplayProfile
|
||
|
|
+SetProfile(profile)
|
||
|
|
+RemoveProfile(name)
|
||
|
|
}
|
||
|
|
|
||
|
|
Monitor "1" --> "0..1" VcpCapabilities
|
||
|
|
VcpCapabilities "1" --> "*" VcpCodeInfo
|
||
|
|
PowerDisplayProfiles "1" --> "*" PowerDisplayProfile
|
||
|
|
PowerDisplayProfile "1" --> "*" ProfileMonitorSetting
|
||
|
|
```
|
||
|
|
|
||
|
|
### Settings Models
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
classDiagram
|
||
|
|
class PowerDisplaySettings {
|
||
|
|
+string Name
|
||
|
|
+PowerDisplayProperties Properties
|
||
|
|
+string Version
|
||
|
|
+ToJsonString() string
|
||
|
|
}
|
||
|
|
|
||
|
|
class PowerDisplayProperties {
|
||
|
|
+bool Enabled
|
||
|
|
+bool HotkeyEnabled
|
||
|
|
+HotkeySettings ActivationShortcut
|
||
|
|
+string BrightnessUpdateRate
|
||
|
|
+bool RestoreSettingsOnStartup
|
||
|
|
+bool ShowSystemTrayIcon
|
||
|
|
+List~MonitorInfo~ Monitors
|
||
|
|
+ColorTemperatureOperation PendingColorTemperatureOperation
|
||
|
|
+ProfileOperation PendingProfileOperation
|
||
|
|
}
|
||
|
|
|
||
|
|
class MonitorInfo {
|
||
|
|
+string Name
|
||
|
|
+string InternalName
|
||
|
|
+string HardwareId
|
||
|
|
+string CommunicationMethod
|
||
|
|
+int CurrentBrightness
|
||
|
|
+int Contrast
|
||
|
|
+int Volume
|
||
|
|
+int ColorTemperatureVcp
|
||
|
|
+bool SupportsBrightness
|
||
|
|
+bool SupportsContrast
|
||
|
|
+bool SupportsColorTemperature
|
||
|
|
+bool SupportsVolume
|
||
|
|
+bool SupportsInputSource
|
||
|
|
+bool EnableContrast
|
||
|
|
+bool EnableVolume
|
||
|
|
+bool EnableInputSource
|
||
|
|
+bool IsHidden
|
||
|
|
+string CapabilitiesRaw
|
||
|
|
+List~string~ VcpCodes
|
||
|
|
}
|
||
|
|
|
||
|
|
class ColorTemperatureOperation {
|
||
|
|
+string MonitorId
|
||
|
|
+int ColorTemperatureVcp
|
||
|
|
}
|
||
|
|
|
||
|
|
class ProfileOperation {
|
||
|
|
+string ProfileName
|
||
|
|
+List~ProfileMonitorSetting~ MonitorSettings
|
||
|
|
}
|
||
|
|
|
||
|
|
PowerDisplaySettings "1" --> "1" PowerDisplayProperties
|
||
|
|
PowerDisplayProperties "1" --> "*" MonitorInfo
|
||
|
|
PowerDisplayProperties "1" --> "0..1" ColorTemperatureOperation
|
||
|
|
PowerDisplayProperties "1" --> "0..1" ProfileOperation
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Future Considerations
|
||
|
|
|
||
|
|
1. **Hardware Cursor Brightness**: Support for displays with hardware cursor brightness
|
||
|
|
2. **Multi-GPU Support**: Better handling of monitors across different GPUs
|
||
|
|
3. **Monitor Hot-Plug**: Improved detection and recovery for monitor connect/disconnect
|
||
|
|
4. **Advanced Color Management**: Integration with Windows Color Management
|
||
|
|
5. **Scheduled Profiles**: Time-based automatic profile switching (beyond LightSwitch)
|
||
|
|
6. **Monitor Groups**: Ability to control multiple monitors as a single entity
|
||
|
|
7. **Remote Control**: Network-based control for multi-system setups
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- [VESA DDC/CI Standard](https://vesa.org/vesa-standards/)
|
||
|
|
- [MCCS (Monitor Control Command Set) Specification](https://vesa.org/vesa-standards/)
|
||
|
|
- [Microsoft High-Level Monitor Configuration API](https://learn.microsoft.com/en-us/windows/win32/monitor/high-level-monitor-configuration-api)
|
||
|
|
- [WMI Reference](https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-reference)
|
||
|
|
- [WmiMonitorBrightness Class](https://learn.microsoft.com/en-us/windows/win32/wmicoreprov/wmimonitorbrightness)
|
||
|
|
- [PowerToys Architecture Documentation](../../core/architecture.md)
|