mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
Expand and reorganize design.md for clarity and depth
- Majorly restructured Table of Contents, splitting "Goals" and "Future Considerations" and adding new technical sub-sections. - Added rationale sections: "Why WmiLight Instead of System.Management" (AOT, memory, API) and "Why We Need an MCCS Capabilities String Parser" (recursive parsing, regex limitations). - Rewrote Settings UI ↔ PowerDisplay architecture diagram for clarity; summarized UI data models in a table. - Reformatted Windows Events for IPC section with a new table and event name explanation. - Overhauled Monitor Discovery Flow with step-by-step breakdowns, new Mermaid diagrams, and a DDC/CI vs. WMI comparison. - Removed the inlined Data Models class diagrams for brevity. - Split "Future Considerations" into "Already Implemented" and a focused "Potential Future Enhancements" list. - Improved formatting, terminology, and explanations throughout.
This commit is contained in:
@@ -4,15 +4,37 @@
|
|||||||
|
|
||||||
1. [Background](#background)
|
1. [Background](#background)
|
||||||
2. [Problem Statement](#problem-statement)
|
2. [Problem Statement](#problem-statement)
|
||||||
3. [Goals and Non-Goals](#goals-and-non-goals)
|
3. [Goals](#goals)
|
||||||
4. [Technical Terminology](#technical-terminology)
|
4. [Technical Terminology](#technical-terminology)
|
||||||
|
- [DDC/CI (Display Data Channel Command Interface)](#ddcci-display-data-channel-command-interface)
|
||||||
|
- [WMI (Windows Management Instrumentation)](#wmi-windows-management-instrumentation)
|
||||||
5. [Architecture Overview](#architecture-overview)
|
5. [Architecture Overview](#architecture-overview)
|
||||||
|
- [High-Level Component Architecture](#high-level-component-architecture)
|
||||||
|
- [Project Structure](#project-structure)
|
||||||
6. [Component Design](#component-design)
|
6. [Component Design](#component-design)
|
||||||
|
- [PowerDisplay Module Internal Structure](#powerdisplay-module-internal-structure)
|
||||||
|
- [DisplayChangeWatcher - Monitor Hot-Plug Detection](#displaychangewatcher---monitor-hot-plug-detection)
|
||||||
|
- [DDC/CI and WMI Interaction Architecture](#ddcci-and-wmi-interaction-architecture)
|
||||||
|
- [IMonitorController Interface Methods](#imonitorcontroller-interface-methods)
|
||||||
|
- [Why WmiLight Instead of System.Management](#why-wmilight-instead-of-systemmanagement)
|
||||||
|
- [Why We Need an MCCS Capabilities String Parser](#why-we-need-an-mccs-capabilities-string-parser)
|
||||||
- [Monitor Identification: Handles, IDs, and Names](#monitor-identification-handles-ids-and-names)
|
- [Monitor Identification: Handles, IDs, and Names](#monitor-identification-handles-ids-and-names)
|
||||||
|
- [Settings UI and PowerDisplay Interaction Architecture](#settings-ui-and-powerdisplay-interaction-architecture)
|
||||||
|
- [Windows Events for IPC](#windows-events-for-ipc)
|
||||||
|
- [LightSwitch Profile Integration Architecture](#lightswitch-profile-integration-architecture)
|
||||||
|
- [LightSwitch Settings JSON Structure](#lightswitch-settings-json-structure)
|
||||||
7. [Data Flow and Communication](#data-flow-and-communication)
|
7. [Data Flow and Communication](#data-flow-and-communication)
|
||||||
|
- [Monitor Discovery Flow](#monitor-discovery-flow)
|
||||||
8. [Sequence Diagrams](#sequence-diagrams)
|
8. [Sequence Diagrams](#sequence-diagrams)
|
||||||
9. [Data Models](#data-models)
|
- [Sequence: Modifying Monitor Settings in Settings UI](#sequence-modifying-monitor-settings-in-settings-ui)
|
||||||
10. [Future Considerations](#future-considerations)
|
- [Sequence: Creating and Saving a Profile](#sequence-creating-and-saving-a-profile)
|
||||||
|
- [Sequence: Applying Profile via LightSwitch Theme Change](#sequence-applying-profile-via-lightswitch-theme-change)
|
||||||
|
- [Sequence: UI Slider Adjustment (Brightness)](#sequence-ui-slider-adjustment-brightness)
|
||||||
|
- [Sequence: Module Enable/Disable Lifecycle](#sequence-module-enabledisable-lifecycle)
|
||||||
|
9. [Future Considerations](#future-considerations)
|
||||||
|
- [Already Implemented](#already-implemented)
|
||||||
|
- [Potential Future Enhancements](#potential-future-enhancements)
|
||||||
|
10. [References](#references)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -88,10 +110,6 @@ PowerDisplay relies on the monitor-reported capabilities string to determine sup
|
|||||||
| `0x60` | Input Source | Active video input (HDMI, DP, USB-C, etc.) |
|
| `0x60` | Input Source | Active video input (HDMI, DP, USB-C, etc.) |
|
||||||
| `0x62` | Volume | Speaker/headphone volume (0-100) |
|
| `0x62` | Volume | Speaker/headphone volume (0-100) |
|
||||||
|
|
||||||
**Official Documentation:**
|
|
||||||
- [VESA DDC/CI Standard](https://vesa.org/vesa-standards/)
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### WMI (Windows Management Instrumentation)
|
### WMI (Windows Management Instrumentation)
|
||||||
@@ -101,10 +119,6 @@ providing a standardized interface for accessing management information in Windo
|
|||||||
For display control, WMI is primarily used for laptop internal displays that may not
|
For display control, WMI is primarily used for laptop internal displays that may not
|
||||||
support DDC/CI.
|
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
|
## Architecture Overview
|
||||||
@@ -525,6 +539,146 @@ classDiagram
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Why WmiLight Instead of System.Management
|
||||||
|
|
||||||
|
PowerDisplay uses the [WmiLight](https://github.com/MartinKuschnik/WmiLight) NuGet package
|
||||||
|
for WMI operations instead of the built-in `System.Management` namespace. This decision was
|
||||||
|
driven by several technical requirements:
|
||||||
|
|
||||||
|
#### Native AOT Compatibility
|
||||||
|
|
||||||
|
PowerDisplay is built with Native AOT (Ahead-of-Time compilation) enabled for improved startup
|
||||||
|
performance and reduced memory footprint. The standard `System.Management` namespace is **not
|
||||||
|
compatible with Native AOT** because it relies heavily on runtime reflection and COM interop
|
||||||
|
patterns that cannot be statically analyzed.
|
||||||
|
|
||||||
|
WmiLight provides Native AOT support since version 5.0.0, making it the appropriate choice for
|
||||||
|
AOT-compiled applications.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- PowerDisplay.Lib.csproj -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<IsAotCompatible>true</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="WmiLight" />
|
||||||
|
</ItemGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Memory Leak Prevention
|
||||||
|
|
||||||
|
The `System.Management` implementation has a known issue where it leaks memory on each WMI
|
||||||
|
operation. While this might be acceptable for short-lived applications, PowerDisplay runs as
|
||||||
|
a long-running background process that may perform frequent WMI queries (e.g., polling
|
||||||
|
brightness levels, responding to theme changes). WmiLight addresses this memory leak issue.
|
||||||
|
|
||||||
|
#### Lightweight API
|
||||||
|
|
||||||
|
WmiLight provides a simpler, more lightweight API compared to `System.Management`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// WmiLight - Simple and direct
|
||||||
|
using (var connection = new WmiConnection(@"root\WMI"))
|
||||||
|
{
|
||||||
|
var results = connection.CreateQuery("SELECT * FROM WmiMonitorBrightness");
|
||||||
|
foreach (var obj in results)
|
||||||
|
{
|
||||||
|
var brightness = obj.GetPropertyValue<byte>("CurrentBrightness");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.Management - More verbose
|
||||||
|
using (var searcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM WmiMonitorBrightness"))
|
||||||
|
{
|
||||||
|
foreach (ManagementObject obj in searcher.Get())
|
||||||
|
{
|
||||||
|
var brightness = (byte)obj["CurrentBrightness"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Comparison Summary
|
||||||
|
|
||||||
|
| Aspect | System.Management | WmiLight |
|
||||||
|
|--------|-------------------|----------|
|
||||||
|
| **Native AOT Support** | ❌ Not supported | ✅ Supported (v5.0.0+) |
|
||||||
|
| **Memory Leaks** | ⚠️ Leaks on remote operations | ✅ No known leaks |
|
||||||
|
| **API Complexity** | More verbose | Simpler, lighter |
|
||||||
|
| **Long-running Services** | Not recommended | ✅ Recommended |
|
||||||
|
| **Static Linking** | ❌ Not available | ✅ Optional (`PublishWmiLightStaticallyLinked`) |
|
||||||
|
|
||||||
|
#### References
|
||||||
|
|
||||||
|
- [WmiLight GitHub Repository](https://github.com/MartinKuschnik/WmiLight)
|
||||||
|
- [WmiLight NuGet Package](https://www.nuget.org/packages/WmiLight)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Why We Need an MCCS Capabilities String Parser
|
||||||
|
|
||||||
|
DDC/CI monitors report their supported features via a **capabilities string** - a structured
|
||||||
|
text format defined by the VESA MCCS (Monitor Control Command Set) standard. This string
|
||||||
|
tells PowerDisplay which VCP codes the monitor supports and what values are valid for each.
|
||||||
|
|
||||||
|
#### Example Capabilities String
|
||||||
|
|
||||||
|
```
|
||||||
|
(prot(monitor)type(lcd)model(PD3220U)cmds(01 02 03 07)vcp(10 12 14(04 05 06) 60(11 12 0F))mccs_ver(2.2))
|
||||||
|
```
|
||||||
|
|
||||||
|
This string encodes:
|
||||||
|
- **Protocol**: monitor
|
||||||
|
- **Type**: LCD display
|
||||||
|
- **Model**: PD3220U
|
||||||
|
- **Supported commands**: 0x01, 0x02, 0x03, 0x07
|
||||||
|
- **VCP codes**: 0x10 (brightness), 0x12 (contrast), 0x14 (color preset with values 4,5,6), 0x60 (input source with values 0x11, 0x12, 0x0F)
|
||||||
|
- **MCCS version**: 2.2
|
||||||
|
|
||||||
|
#### Why Parse It?
|
||||||
|
|
||||||
|
| Use Case | How Parser Helps |
|
||||||
|
|----------|------------------|
|
||||||
|
| **Feature Detection** | Determine if monitor supports contrast, volume, color temperature, input switching |
|
||||||
|
| **Input Source Dropdown** | Extract valid input source values (e.g., HDMI-1=0x11, DP=0x0F) for UI dropdown |
|
||||||
|
| **Color Preset List** | Extract supported color presets (e.g., sRGB, 5000K, 6500K) |
|
||||||
|
| **Diagnostics** | Display raw VCP codes in Settings UI for troubleshooting |
|
||||||
|
| **PIP/PBP Support** | Parse window capabilities for Picture-in-Picture features |
|
||||||
|
|
||||||
|
#### Why Not Use Regex?
|
||||||
|
|
||||||
|
The MCCS capabilities string format has **nested parentheses** that regex cannot reliably handle:
|
||||||
|
|
||||||
|
```
|
||||||
|
vcp(10 12 14(04 05 06) 60(11 12 0F))
|
||||||
|
^^^^^^^^^^^^ nested values
|
||||||
|
```
|
||||||
|
|
||||||
|
A recursive descent parser properly handles:
|
||||||
|
- Nested parentheses at arbitrary depth
|
||||||
|
- Variable whitespace (some monitors use `01 02 03`, others use `010203`)
|
||||||
|
- Optional outer parentheses (some monitors omit them)
|
||||||
|
- Unknown segments (graceful skip without failing)
|
||||||
|
|
||||||
|
#### Implementation
|
||||||
|
|
||||||
|
PowerDisplay implements a **zero-allocation recursive descent parser** using `ref struct` and
|
||||||
|
`ReadOnlySpan<char>` for optimal performance during monitor discovery.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Usage in DdcCiController
|
||||||
|
var result = MccsCapabilitiesParser.Parse(capabilitiesString);
|
||||||
|
if (result.IsValid)
|
||||||
|
{
|
||||||
|
monitor.VcpCapabilitiesInfo = result.Capabilities;
|
||||||
|
// Now we know which features this monitor supports
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Detailed Design:** See [MCCS_PARSER_DESIGN.md](./MCCS_PARSER_DESIGN.md) for the complete
|
||||||
|
> parser architecture, grammar definition, and implementation details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Monitor Identification: Handles, IDs, and Names
|
### Monitor Identification: Handles, IDs, and Names
|
||||||
|
|
||||||
Understanding how Windows identifies monitors is critical for PowerDisplay's operation.
|
Understanding how Windows identifies monitors is critical for PowerDisplay's operation.
|
||||||
@@ -838,83 +992,78 @@ PowerDisplay stores `GdiDeviceName` in each `Monitor` object specifically for ro
|
|||||||
### Settings UI and PowerDisplay Interaction Architecture
|
### Settings UI and PowerDisplay Interaction Architecture
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart LR
|
||||||
subgraph SettingsProcess["Settings UI Process"]
|
subgraph SettingsUI["Settings UI Process"]
|
||||||
SettingsPage["PowerDisplayPage.xaml"]
|
direction TB
|
||||||
ViewModel["PowerDisplayViewModel"]
|
Page["PowerDisplayPage.xaml"]
|
||||||
SettingsLib["Settings.UI.Library"]
|
VM["PowerDisplayViewModel"]
|
||||||
|
Page --> VM
|
||||||
subgraph DataModels["Data Models"]
|
|
||||||
PowerDisplaySettings["PowerDisplaySettings"]
|
|
||||||
MonitorInfo["MonitorInfo"]
|
|
||||||
ProfileOperation["ProfileOperation"]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph RunnerProcess["Runner Process"]
|
subgraph Runner["Runner Process"]
|
||||||
Runner["PowerToys.exe"]
|
direction TB
|
||||||
NamedPipe["Named Pipe IPC"]
|
Exe["PowerToys.exe"]
|
||||||
ModuleInterface["PowerDisplayModuleInterface.dll"]
|
Pipe["Named Pipe IPC"]
|
||||||
|
Module["PowerDisplayModuleInterface.dll"]
|
||||||
|
Pipe --> Exe --> Module
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph PowerDisplayProcess["PowerDisplay Process"]
|
subgraph PDApp["PowerDisplay Process"]
|
||||||
App["PowerToys.PowerDisplay.exe"]
|
direction TB
|
||||||
MainVM["MainViewModel"]
|
MainVM["MainViewModel"]
|
||||||
|
Events["Event Listeners<br/>Refresh / ColorTemp / Profile"]
|
||||||
subgraph EventListeners["Event Listeners"]
|
Events --> MainVM
|
||||||
RefreshEvent["RefreshMonitors Event"]
|
|
||||||
ApplyColorTempEvent["ApplyColorTemp Event"]
|
|
||||||
ApplyProfileEvent["ApplyProfile Event"]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph FileSystem["File System"]
|
subgraph Storage["File System"]
|
||||||
SettingsJson["PowerDisplay/settings.json"]
|
direction TB
|
||||||
ProfilesJson["PowerDisplay/profiles.json"]
|
Settings[("settings.json")]
|
||||||
|
Profiles[("profiles.json")]
|
||||||
end
|
end
|
||||||
|
|
||||||
%% Settings UI to Runner
|
%% Main flow: Settings UI → Runner → PowerDisplay
|
||||||
SettingsPage --> ViewModel
|
VM -->|"IPC Message"| Pipe
|
||||||
ViewModel --> SettingsLib
|
Module -->|"SetEvent()"| Events
|
||||||
ViewModel -->|"SendDefaultIPCMessage()"| NamedPipe
|
|
||||||
NamedPipe --> Runner
|
|
||||||
Runner -->|"set_config()"| ModuleInterface
|
|
||||||
Runner -->|"call_custom_action()"| ModuleInterface
|
|
||||||
|
|
||||||
%% Settings persistence
|
%% File access
|
||||||
ViewModel <-->|"Read/Write"| SettingsJson
|
VM <-.->|"Read/Write"| Settings
|
||||||
ViewModel <-->|"Read/Write"| ProfilesJson
|
VM <-.->|"Read/Write"| Profiles
|
||||||
|
MainVM <-.->|"Read"| Settings
|
||||||
|
MainVM <-.->|"Read/Write"| Profiles
|
||||||
|
|
||||||
%% Module Interface to PowerDisplay App
|
style SettingsUI fill:#e3f2fd
|
||||||
ModuleInterface -->|"SetEvent()"| RefreshEvent
|
style Runner fill:#fff3e0
|
||||||
ModuleInterface -->|"SetEvent()"| ApplyColorTempEvent
|
style PDApp fill:#e8f5e9
|
||||||
ModuleInterface -->|"SetEvent()"| ApplyProfileEvent
|
style Storage fill:#fffde7
|
||||||
|
|
||||||
%% 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Data Models (in Settings.UI.Library):**
|
||||||
|
|
||||||
|
| Model | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `PowerDisplaySettings` | Main settings container with properties and pending operations |
|
||||||
|
| `MonitorInfo` | Per-monitor settings displayed in Settings UI |
|
||||||
|
| `ProfileOperation` | Pending profile apply operation |
|
||||||
|
| `ColorTemperatureOperation` | Pending color temperature change |
|
||||||
|
|
||||||
### Windows Events for IPC
|
### Windows Events for IPC
|
||||||
|
|
||||||
| Event Name | Constant | Direction | Purpose |
|
Event names use fixed GUID suffixes to ensure uniqueness (defined in `shared_constants.h`).
|
||||||
|------------|----------|-----------|---------|
|
|
||||||
| `Local\PowerToysPowerDisplay-ShowEvent-*` | `SHOW_POWER_DISPLAY_EVENT` | Runner → App | Show window |
|
| Constant | Direction | Purpose |
|
||||||
| `Local\PowerToysPowerDisplay-ToggleEvent-*` | `TOGGLE_POWER_DISPLAY_EVENT` | Runner → App | Toggle visibility |
|
|----------|-----------|---------|
|
||||||
| `Local\PowerToysPowerDisplay-TerminateEvent-*` | `TERMINATE_POWER_DISPLAY_EVENT` | Runner → App | Terminate process |
|
| `SHOW_POWER_DISPLAY_EVENT` | Runner → App | Show window |
|
||||||
| `Local\PowerToysPowerDisplay-RefreshMonitorsEvent-*` | `REFRESH_POWER_DISPLAY_MONITORS_EVENT` | Settings → App | Refresh monitor list |
|
| `TOGGLE_POWER_DISPLAY_EVENT` | Runner → App | Toggle visibility |
|
||||||
| `Local\PowerToysPowerDisplay-ApplyColorTemperatureEvent-*` | `APPLY_COLOR_TEMPERATURE_POWER_DISPLAY_EVENT` | Settings → App | Apply color temp |
|
| `TERMINATE_POWER_DISPLAY_EVENT` | Runner → App | Terminate process |
|
||||||
| `Local\PowerToysPowerDisplay-ApplyProfileEvent-*` | `APPLY_PROFILE_POWER_DISPLAY_EVENT` | Settings → App | Apply profile |
|
| `REFRESH_POWER_DISPLAY_MONITORS_EVENT` | Settings → App | Refresh monitor list |
|
||||||
| `Local\PowerToys_LightSwitch_LightTheme` | `LightSwitchLightThemeEventName` | LightSwitch → App | Apply light mode profile |
|
| `APPLY_COLOR_TEMPERATURE_POWER_DISPLAY_EVENT` | Settings → App | Apply color temp |
|
||||||
| `Local\PowerToys_LightSwitch_DarkTheme` | `LightSwitchDarkThemeEventName` | LightSwitch → App | Apply dark mode profile |
|
| `APPLY_PROFILE_POWER_DISPLAY_EVENT` | Settings → App | Apply profile |
|
||||||
|
| `LightSwitchLightThemeEventName` | LightSwitch → App | Apply light mode profile |
|
||||||
|
| `LightSwitchDarkThemeEventName` | LightSwitch → App | Apply dark mode profile |
|
||||||
|
|
||||||
|
**Event Name Format:** `Local\PowerToysPowerDisplay-{EventType}-{GUID}`
|
||||||
|
|
||||||
|
Example: `Local\PowerToysPowerDisplay-ShowEvent-d8a4e0e3-2c5b-4a1c-9e7f-8b3d6c1a2f4e`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1003,65 +1152,88 @@ flowchart TB
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
Start([Start Discovery]) --> Init["MonitorManager.DiscoverMonitorsAsync()"]
|
Start([Start Discovery])
|
||||||
|
Start --> MM["MonitorManager.DiscoverMonitorsAsync()"]
|
||||||
|
|
||||||
Init --> ParallelDiscover["Parallel Discovery"]
|
MM --> DDC["DdcCiController.DiscoverMonitorsAsync()"]
|
||||||
|
MM --> WMI["WmiController.DiscoverMonitorsAsync()"]
|
||||||
|
|
||||||
subgraph ParallelDiscover["Parallel Controller Discovery"]
|
DDC --> Merge["Merge Results"]
|
||||||
DDCDiscover["DdcCiController.DiscoverMonitorsAsync()"]
|
WMI --> Merge
|
||||||
WMIDiscover["WmiController.DiscoverMonitorsAsync()"]
|
|
||||||
end
|
|
||||||
|
|
||||||
DDCDiscover --> DDCEnum["EnumDisplayMonitors()"]
|
Merge --> Sort["Sort by MonitorNumber"]
|
||||||
DDCEnum --> DDCPhysical["GetPhysicalMonitorsFromHMONITOR()"]
|
Sort --> Update["Update _monitors Collection"]
|
||||||
DDCPhysical --> DDCCheck["Quick DDC/CI connection check"]
|
Update --> Done([Discovery Complete])
|
||||||
|
|
||||||
WMIDiscover --> WMIQuery["Query WmiMonitorBrightness"]
|
style Start fill:#e8f5e9
|
||||||
WMIQuery --> WMIFilter["Filter responsive displays"]
|
style Done fill:#e8f5e9
|
||||||
|
style DDC fill:#e3f2fd
|
||||||
DDCCheck --> Merge["Merge Results"]
|
style WMI fill:#fff3e0
|
||||||
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 --> StartWatcher["Start DisplayChangeWatcher"]
|
|
||||||
StartWatcher --> End([Discovery Complete])
|
|
||||||
|
|
||||||
style ParallelDiscover fill:#e3f2fd
|
|
||||||
style InitLoop fill:#e8f5e9
|
|
||||||
style CheckType fill:#fff3e0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** WMI monitors skip VCP capabilities fetching because:
|
> **Note:** DDC/CI and WMI discovery run in parallel via `Task.WhenAll`.
|
||||||
1. WMI uses a different abstraction layer (`WmiMonitorBrightness` class)
|
|
||||||
2. `WmiController.GetCapabilitiesStringAsync()` returns an empty string
|
#### DDC/CI Discovery (Three-Phase Approach)
|
||||||
3. VCP codes are DDC/CI-specific and not applicable to WMI-controlled displays
|
|
||||||
|
**Phase 1: Collect Candidates**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
QDC["QueryDisplayConfig"] --> Match["Match by GDI Name"]
|
||||||
|
Enum["EnumDisplayMonitors"] --> GetPhys["GetPhysicalMonitors"] --> Match
|
||||||
|
Match --> Candidates["CandidateMonitor List"]
|
||||||
|
|
||||||
|
style QDC fill:#e3f2fd
|
||||||
|
style Enum fill:#e3f2fd
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 2: Fetch Capabilities (Parallel)**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Candidates["CandidateMonitor List"] --> Fetch["Task.WhenAll:<br/>FetchCapabilities<br/>~4s per monitor via I2C"]
|
||||||
|
Fetch --> Results["DdcCiValidationResult Array"]
|
||||||
|
|
||||||
|
style Fetch fill:#fff3e0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 3: Create Monitors**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Results["Validation Results"] --> Check{"IsValid?"}
|
||||||
|
Check -->|Yes| Create["Create Monitor"]
|
||||||
|
Create --> Init["Initialize VCP Values:<br/>Brightness, ColorTemp, InputSource"]
|
||||||
|
Init --> Add["Add to List"]
|
||||||
|
Check -->|No| Skip([Skip])
|
||||||
|
|
||||||
|
style Create fill:#e8f5e9
|
||||||
|
style Init fill:#e8f5e9
|
||||||
|
```
|
||||||
|
|
||||||
|
#### WMI Discovery
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Query["Query WmiMonitorBrightness"] --> Extract["Extract HardwareId<br/>from InstanceName"]
|
||||||
|
QDC["QueryDisplayConfig"] --> Match["Match by HardwareId"]
|
||||||
|
Extract --> Match
|
||||||
|
Match --> Name["Get Display Name<br/>via PnpIdHelper"]
|
||||||
|
Name --> Create["Create Monitor<br/>Brightness + WMI"]
|
||||||
|
|
||||||
|
style Query fill:#fff3e0
|
||||||
|
style Create fill:#fff3e0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Key Differences
|
||||||
|
|
||||||
|
| Aspect | DDC/CI | WMI |
|
||||||
|
|--------|--------|-----|
|
||||||
|
| **Target** | External monitors | Internal laptop displays |
|
||||||
|
| **Capabilities** | Full VCP support (brightness, contrast, volume, color temp, input) | Brightness only |
|
||||||
|
| **Discovery** | Three-phase with parallel I2C fetching | Single WMI query |
|
||||||
|
| **Initialization** | Reads current values for all supported VCP codes | Brightness from query result |
|
||||||
|
| **Performance** | ~4s per monitor (I2C), parallelized | Fast (~100ms total) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1359,251 +1531,9 @@ sequenceDiagram
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Data Models
|
|
||||||
|
|
||||||
### Core Models
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
classDiagram
|
|
||||||
class Monitor {
|
|
||||||
+string Id
|
|
||||||
+string Name
|
|
||||||
+string CommunicationMethod
|
|
||||||
+string InstanceName
|
|
||||||
+string GdiDeviceName
|
|
||||||
+int MonitorNumber
|
|
||||||
+int CurrentBrightness
|
|
||||||
+int MinBrightness
|
|
||||||
+int MaxBrightness
|
|
||||||
+int CurrentContrast
|
|
||||||
+int MinContrast
|
|
||||||
+int MaxContrast
|
|
||||||
+int CurrentVolume
|
|
||||||
+int MinVolume
|
|
||||||
+int MaxVolume
|
|
||||||
+int CurrentColorTemperature
|
|
||||||
+string ColorTemperaturePresetName
|
|
||||||
+int CurrentInputSource
|
|
||||||
+string InputSourceName
|
|
||||||
+IReadOnlyList~int~ SupportedInputSources
|
|
||||||
+int Orientation
|
|
||||||
+bool IsAvailable
|
|
||||||
+bool SupportsContrast
|
|
||||||
+bool SupportsVolume
|
|
||||||
+bool SupportsColorTemperature
|
|
||||||
+bool SupportsInputSource
|
|
||||||
+MonitorCapabilities Capabilities
|
|
||||||
+VcpCapabilities VcpCapabilitiesInfo
|
|
||||||
+string CapabilitiesRaw
|
|
||||||
+IntPtr Handle
|
|
||||||
+DateTime LastUpdate
|
|
||||||
+UpdateStatus(brightness, isAvailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
class VcpCapabilities {
|
|
||||||
+string Raw
|
|
||||||
+string Model
|
|
||||||
+string Type
|
|
||||||
+string Protocol
|
|
||||||
+string MccsVersion
|
|
||||||
+List~byte~ SupportedCommands
|
|
||||||
+Dictionary~byte, VcpCodeInfo~ SupportedVcpCodes
|
|
||||||
+List~WindowCapability~ Windows
|
|
||||||
+bool HasWindowSupport
|
|
||||||
+static VcpCapabilities Empty$
|
|
||||||
+SupportsVcpCode(code) bool
|
|
||||||
+GetVcpCodeInfo(code) VcpCodeInfo
|
|
||||||
+HasDiscreteValues(code) bool
|
|
||||||
+GetSupportedValues(code) IReadOnlyList~int~
|
|
||||||
+GetVcpCodesAsHexStrings() List~string~
|
|
||||||
+GetSortedVcpCodes() IEnumerable~VcpCodeInfo~
|
|
||||||
}
|
|
||||||
|
|
||||||
class VcpCodeInfo {
|
|
||||||
+byte Code
|
|
||||||
+string Name
|
|
||||||
+IReadOnlyList~int~ SupportedValues
|
|
||||||
+bool HasDiscreteValues
|
|
||||||
+bool IsContinuous
|
|
||||||
+string FormattedCode
|
|
||||||
+string FormattedTitle
|
|
||||||
}
|
|
||||||
|
|
||||||
class WindowCapability {
|
|
||||||
<<struct>>
|
|
||||||
+int WindowNumber
|
|
||||||
+string Type
|
|
||||||
+WindowArea Area
|
|
||||||
+WindowSize MaxSize
|
|
||||||
+WindowSize MinSize
|
|
||||||
+int WindowId
|
|
||||||
}
|
|
||||||
|
|
||||||
class WindowSize {
|
|
||||||
<<struct>>
|
|
||||||
+int Width
|
|
||||||
+int Height
|
|
||||||
}
|
|
||||||
|
|
||||||
class WindowArea {
|
|
||||||
<<struct>>
|
|
||||||
+int X1
|
|
||||||
+int Y1
|
|
||||||
+int X2
|
|
||||||
+int Y2
|
|
||||||
+int Width
|
|
||||||
+int Height
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
+string Name
|
|
||||||
+DateTime CreatedDate
|
|
||||||
+DateTime LastModified
|
|
||||||
+List~ProfileMonitorSetting~ MonitorSettings
|
|
||||||
+IsValid() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProfileMonitorSetting {
|
|
||||||
+string MonitorInternalName
|
|
||||||
+int MonitorNumber
|
|
||||||
+int? Brightness
|
|
||||||
+int? Contrast
|
|
||||||
+int? Volume
|
|
||||||
+int? ColorTemperatureVcp
|
|
||||||
+int? Orientation
|
|
||||||
}
|
|
||||||
|
|
||||||
class PowerDisplayProfiles {
|
|
||||||
+List~PowerDisplayProfile~ Profiles
|
|
||||||
+DateTime LastUpdated
|
|
||||||
}
|
|
||||||
|
|
||||||
Monitor "1" --> "0..1" VcpCapabilities
|
|
||||||
Monitor "1" --> "1" MonitorCapabilities
|
|
||||||
VcpCapabilities "1" --> "*" VcpCodeInfo
|
|
||||||
VcpCapabilities "1" --> "*" WindowCapability
|
|
||||||
WindowCapability "1" --> "1" WindowArea
|
|
||||||
WindowCapability "1" --> "1" WindowSize : MaxSize
|
|
||||||
WindowCapability "1" --> "1" WindowSize : MinSize
|
|
||||||
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 MonitorNumber
|
|
||||||
+int TotalMonitorCount
|
|
||||||
+string DisplayName
|
|
||||||
+string MonitorIconGlyph
|
|
||||||
+int CurrentBrightness
|
|
||||||
+int Contrast
|
|
||||||
+int Volume
|
|
||||||
+int ColorTemperatureVcp
|
|
||||||
+int Orientation
|
|
||||||
+bool SupportsBrightness
|
|
||||||
+bool SupportsContrast
|
|
||||||
+bool SupportsColorTemperature
|
|
||||||
+bool SupportsVolume
|
|
||||||
+bool SupportsInputSource
|
|
||||||
+bool EnableContrast
|
|
||||||
+bool EnableVolume
|
|
||||||
+bool EnableInputSource
|
|
||||||
+bool EnableRotation
|
|
||||||
+bool IsHidden
|
|
||||||
+string CapabilitiesRaw
|
|
||||||
+List~string~ VcpCodes
|
|
||||||
+List~VcpCodeDisplayInfo~ VcpCodesFormatted
|
|
||||||
+ObservableCollection~ColorPresetItem~ AvailableColorPresets
|
|
||||||
+ObservableCollection~ColorPresetItem~ ColorPresetsForDisplay
|
|
||||||
+bool HasCapabilities
|
|
||||||
+bool ShowCapabilitiesWarning
|
|
||||||
+GetVcpCodesAsText() string
|
|
||||||
+UpdateFrom(other)
|
|
||||||
}
|
|
||||||
|
|
||||||
class VcpCodeDisplayInfo {
|
|
||||||
+string Code
|
|
||||||
+string Title
|
|
||||||
+string Values
|
|
||||||
+bool HasValues
|
|
||||||
+List~VcpValueInfo~ ValueList
|
|
||||||
}
|
|
||||||
|
|
||||||
class VcpValueInfo {
|
|
||||||
+string Value
|
|
||||||
+string Name
|
|
||||||
}
|
|
||||||
|
|
||||||
class ColorTemperatureOperation {
|
|
||||||
+string MonitorId
|
|
||||||
+int ColorTemperatureVcp
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProfileOperation {
|
|
||||||
+string ProfileName
|
|
||||||
+List~ProfileMonitorSetting~ MonitorSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
MonitorInfo "1" --> "*" VcpCodeDisplayInfo
|
|
||||||
VcpCodeDisplayInfo "1" --> "*" VcpValueInfo
|
|
||||||
PowerDisplaySettings "1" --> "1" PowerDisplayProperties
|
|
||||||
PowerDisplayProperties "1" --> "*" MonitorInfo
|
|
||||||
PowerDisplayProperties "1" --> "0..1" ColorTemperatureOperation
|
|
||||||
PowerDisplayProperties "1" --> "0..1" ProfileOperation
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Future Considerations
|
## Future Considerations
|
||||||
|
|
||||||
### Already Implemented (removed from backlog)
|
### Already Implemented
|
||||||
|
|
||||||
- **Monitor Hot-Plug**: `DisplayChangeWatcher` uses WinRT DeviceWatcher + DisplayMonitor API with 1-second debouncing
|
- **Monitor Hot-Plug**: `DisplayChangeWatcher` uses WinRT DeviceWatcher + DisplayMonitor API with 1-second debouncing
|
||||||
- **Display Rotation**: `DisplayRotationService` uses Windows ChangeDisplaySettingsEx API
|
- **Display Rotation**: `DisplayRotationService` uses Windows ChangeDisplaySettingsEx API
|
||||||
@@ -1613,15 +1543,9 @@ classDiagram
|
|||||||
|
|
||||||
### Potential Future Enhancements
|
### Potential Future Enhancements
|
||||||
|
|
||||||
1. **Hardware Cursor Brightness**: Support for displays with hardware cursor brightness
|
1. **Advanced Color Management**: Integration with Windows Color Management APIs (HDR, ICC profiles)
|
||||||
2. **Multi-GPU Support**: Better handling of monitors across different GPUs
|
2. **PIP/PBP Control**: Picture-in-Picture and Picture-by-Picture configuration (VcpCapabilities already parses window capabilities)
|
||||||
3. **Advanced Color Management**: Integration with Windows Color Management APIs (HDR, ICC profiles)
|
3. **Power State Control**: Monitor power on/off via VCP code 0xD6
|
||||||
4. **Scheduled Profiles**: Time-based automatic profile switching (beyond LightSwitch integration)
|
|
||||||
5. **Monitor Groups**: Ability to control multiple monitors as a single entity
|
|
||||||
6. **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. **Power State Control**: Monitor power on/off via VCP code 0xD6
|
|
||||||
9. **Input Source Scheduling**: Automatic input switching based on time or application
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user