Refactor: update docs, diagrams, and PnpIdHelper namespace

- Improved PowerDisplay design docs for clarity and technical accuracy
- Rewrote DDC/CI section to better explain real-world issues
- Updated architecture diagrams and terminology (Helpers → Utils)
- Replaced ASCII diagrams with Mermaid for better visualization
- Clarified monitor identification and MST/Clone mode handling
- Corrected PnP Manufacturer ID attribution to UEFI Forum
- Moved PnpIdHelper.cs to Utils namespace and updated all references
This commit is contained in:
Yu Leng
2025-12-12 12:40:54 +08:00
parent 2083fcd143
commit 06a72f3c54
3 changed files with 85 additions and 82 deletions

View File

@@ -49,9 +49,8 @@ Users with multiple monitors face several challenges:
- Provide unified control for brightness, contrast, volume, color temperature, and - Provide unified control for brightness, contrast, volume, color temperature, and
input source across all connected monitors input source across all connected monitors
- Support both DDC/CI (external monitors) and WMI (laptop displays) - 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 user-defined profiles for quick configuration switching
- Integrate with LightSwitch module for automatic profile application on theme changes
- Support global hotkey activation - Support global hotkey activation
--- ---
@@ -64,11 +63,12 @@ Users with multiple monitors face several challenges:
bidirectional communication between a computer and a display over the I2C bus bidirectional communication between a computer and a display over the I2C bus
embedded in display cables. embedded in display cables.
Most monitors (external monitors) support DDC/CI, allowing applications to read and modify settings Most 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, like brightness and contrast programmatically. But unfortunately, some manufacturers have poor implementations of their product's driver. They may not support DDC/CI or report itself supports DDC/CI (through capabilities string) when it does not. Even if a monitor supports DDC/CI, they may only support a limited subset of VCP codes, or have buggy implementations.
they may only support a limited subset of VCP codes, or have buggy implementations. PowerDisplay relies on
the monitor-reported capabilities string to determine supported features. But if your monitor's manufacturer And sometimes, users may connect monitor through a KVM switch or docking station that does not pass through DDC/CI commands correctly, and their docking may report it supports (hard code a capabilities string) but in reality, it does not. And will do thing when we try to send DDC/CI commands.
has a poor DDC/CI implementation, some features may not work as expected. And we can do nothing about it.
PowerDisplay relies on the monitor-reported capabilities string to determine supported features. But if your monitor's manufacturer has a poor DDC/CI implementation, or you are connecting through a docking station that does not properly support DDC/CI, some features may not work as expected. And we can do nothing about it.
**Key Concepts:** **Key Concepts:**
@@ -90,7 +90,7 @@ has a poor DDC/CI implementation, some features may not work as expected. And we
**Official Documentation:** **Official Documentation:**
- [VESA DDC/CI Standard](https://vesa.org/vesa-standards/) - [VESA DDC/CI Standard](https://vesa.org/vesa-standards/)
- [MCCS (Monitor Control Command Set) Specification](https://vesa.org/vesa-standards/)
--- ---
@@ -113,7 +113,7 @@ support DDC/CI.
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph PowerToys["PowerToys Ecosystem"] subgraph PowerToys["PowerToys Application"]
Runner["Runner (PowerToys.exe)"] Runner["Runner (PowerToys.exe)"]
SettingsUI["Settings UI (WinUI 3)"] SettingsUI["Settings UI (WinUI 3)"]
LightSwitch["LightSwitch Module"] LightSwitch["LightSwitch Module"]
@@ -133,9 +133,10 @@ flowchart TB
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<br/>(Launch, RefreshMonitors,<br/>ApplyColorTemperature, ApplyProfile)"| ModuleInterface SettingsUI -->|"Custom Actions<br/>(Launch, ApplyColorTemperature,<br/>ApplyProfile)"| ModuleInterface
ModuleInterface <-->|"Windows Events<br/>(Show/Toggle/Terminate/Refresh)"| PowerDisplayApp ModuleInterface <-->|"Windows Events<br/>(Show/Toggle/Terminate)"| PowerDisplayApp
PowerDisplayApp -->|"RefreshMonitors Event"| SettingsUI
LightSwitch -->|"Theme Events<br/>(Light/Dark)"| PowerDisplayApp LightSwitch -->|"Theme Events<br/>(Light/Dark)"| PowerDisplayApp
PowerDisplayApp --> PowerDisplayLib PowerDisplayApp --> PowerDisplayLib
@@ -176,8 +177,6 @@ src/modules/powerdisplay/
│ │ ├── NativeDelegates.cs # P/Invoke delegate types │ │ ├── NativeDelegates.cs # P/Invoke delegate types
│ │ ├── NativeStructures.cs # Win32 structures │ │ ├── NativeStructures.cs # Win32 structures
│ │ └── PInvoke.cs # P/Invoke declarations │ │ └── 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 │ │ ├── IMonitorData.cs # Monitor data interface
@@ -209,6 +208,7 @@ src/modules/powerdisplay/
│ │ ├── MonitorFeatureHelper.cs # Monitor feature utilities │ │ ├── MonitorFeatureHelper.cs # Monitor feature utilities
│ │ ├── MonitorMatchingHelper.cs # Profile-to-monitor matching │ │ ├── MonitorMatchingHelper.cs # Profile-to-monitor matching
│ │ ├── MonitorValueConverter.cs # Value conversion utilities │ │ ├── MonitorValueConverter.cs # Value conversion utilities
│ │ ├── PnpIdHelper.cs # PnP manufacturer ID lookup
│ │ ├── ProfileHelper.cs # Profile helper utilities │ │ ├── ProfileHelper.cs # Profile helper utilities
│ │ ├── SimpleDebouncer.cs # Generic debouncer │ │ ├── SimpleDebouncer.cs # Generic debouncer
│ │ └── VcpNames.cs # VCP code and value name lookup │ │ └── VcpNames.cs # VCP code and value name lookup
@@ -299,7 +299,7 @@ flowchart TB
DdcCiController DdcCiController
WmiController WmiController
end end
subgraph Helpers subgraph Utils
PnpIdHelper["PnpIdHelper<br/>(Manufacturer Names)"] PnpIdHelper["PnpIdHelper<br/>(Manufacturer Names)"]
end end
end end
@@ -336,7 +336,7 @@ flowchart TB
MonitorManager --> Drivers MonitorManager --> Drivers
MonitorManager --> DisplayRotationService MonitorManager --> DisplayRotationService
%% Helpers used during discovery %% Utils used during discovery
WmiController --> PnpIdHelper WmiController --> PnpIdHelper
%% Services to Storage %% Services to Storage
@@ -357,7 +357,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 Utils 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
``` ```
@@ -651,15 +651,19 @@ sequenceDiagram
##### GDI Device Name ↔ Physical Monitors ##### GDI Device Name ↔ Physical Monitors
``` ```mermaid
HMONITOR (Logical) flowchart TB
HMON["HMONITOR (Logical)"]
├── GetMonitorInfo() → GDI Device Name "\\.\DISPLAY1"
HMON --> GDI["GetMonitorInfo()<br/>→ GDI Device Name<br/>\.DISPLAY1"]
└── GetPhysicalMonitorsFromHMONITOR() HMON --> GetPhys["GetPhysicalMonitorsFromHMONITOR()"]
├── Physical Monitor 0 (Handle: 0x0B14, Desc: "Dell U2722D") GetPhys --> PM0["Physical Monitor 0<br/>Handle: 0x0B14<br/>Desc: Dell U2722D"]
└── Physical Monitor 1 (Handle: 0x0B18, Desc: "Dell U2722D") [Mirror mode] GetPhys --> PM1["Physical Monitor 1<br/>Handle: 0x0B18<br/>Desc: Dell U2722D<br/>Mirror mode"]
style HMON fill:#e3f2fd
style PM0 fill:#fff3e0
style PM1 fill:#fff3e0
``` ```
In **mirror/clone mode**, multiple physical monitors share the same GDI device name. In **mirror/clone mode**, multiple physical monitors share the same GDI device name.
@@ -672,15 +676,25 @@ QueryDisplayConfig returns multiple paths with the same `GdiDeviceName` but diff
DisplayPort output using MST (Multi-Stream Transport) technology. This creates unique DisplayPort output using MST (Multi-Stream Transport) technology. This creates unique
challenges for monitor identification. challenges for monitor identification.
``` ```mermaid
┌─────────────┐ DP ┌─────────────┐ DP ┌─────────────┐ flowchart LR
GPU │─────────▶│ Monitor A │─────────▶│ Monitor B │ GPU["GPU<br/>(Single DP Port)"]
│ │ │ (MST Hub) │ │ (End) │ MonA["Monitor A<br/>(MST Hub)"]
└─────────────┘ └─────────────┘ └─────────────┘ MonB["Monitor B<br/>(End)"]
│ Single physical DP port GPU -->|"DP"| MonA -->|"DP"| MonB
Multiple logical displays (DISPLAY1, DISPLAY2) subgraph Result["Result: Multiple Logical Displays"]
D1["DISPLAY1"]
D2["DISPLAY2"]
end
GPU -.-> Result
style GPU fill:#bbdefb
style MonA fill:#c8e6c9
style MonB fill:#c8e6c9
style Result fill:#fff3e0
``` ```
**How Windows Handles MST:** **How Windows Handles MST:**
@@ -695,25 +709,13 @@ challenges for monitor identification.
**MST vs Clone Mode Comparison:** **MST vs Clone Mode Comparison:**
``` | Property | MST Daisy Chain (Extended Desktop) | Clone/Mirror Mode |
MST Daisy Chain (Extended Desktop): |----------|-----------------------------------|-------------------|
┌──────────────────────────────────────────────────────────────┐ | **HMONITOR** | Separate per monitor (HMONITOR_1, HMONITOR_2, ...) | Shared (single HMONITOR_1) |
│ HMONITOR_1 │ HMONITOR_2 │ HMONITOR_3 │ | **GDI Device Name** | Unique per monitor (`\\.\DISPLAY1`, `\\.\DISPLAY2`, ...) | Shared (`\\.\DISPLAY1`) |
│ GDI: \\.\DISPLAY1 │ GDI: \\.\DISPLAY2 │ GDI: \\.\DISPLAY3│ | **Physical Handle** | One per HMONITOR (A, B, C) | Multiple per HMONITOR (A, B) |
│ Physical Handle: A │ Physical Handle: B │ Physical Handle: C│ | **DevicePath** | Unique per monitor (unique1, unique2, ...) | Unique per monitor (unique1, unique2) |
│ DevicePath: unique1 │ DevicePath: unique2 │ DevicePath: unique3│ | **Behavior** | Each monitor = independent logical display | Multiple monitors share same logical display |
└──────────────────────────────────────────────────────────────┘
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:** **PowerDisplay Handling of MST:**
@@ -732,10 +734,11 @@ distinguishes them using:
- **Monitor.Id**: Format `DDC_{HardwareId}_{MonitorNumber}` ensures uniqueness - **Monitor.Id**: Format `DDC_{HardwareId}_{MonitorNumber}` ensures uniqueness
Example with two identical Dell U2722D monitors: Example with two identical Dell U2722D monitors:
```
Monitor 1: Id = "DDC_DEL41B4_1", MonitorNumber = 1 | Monitor | Id | MonitorNumber |
Monitor 2: Id = "DDC_DEL41B4_2", MonitorNumber = 2 |---------|-----|---------------|
``` | Monitor 1 | `DDC_DEL41B4_1` | 1 |
| Monitor 2 | `DDC_DEL41B4_2` | 2 |
##### Connection Mode Summary ##### Connection Mode Summary
@@ -752,20 +755,19 @@ identifiers. Only clone/mirror mode requires special handling due to shared HMON
##### Hardware ID Composition ##### Hardware ID Composition
``` ```mermaid
Hardware ID: "DEL41B4" flowchart TB
───┬─── HardwareId["Hardware ID: DEL41B4"]
┌────────────┴────────────┐ HardwareId --> PnpId["DEL<br/>PnP Manufacturer ID<br/>3 chars, EDID bytes 8-9"]
│ │ HardwareId --> ProductCode["41B4<br/>Product Code<br/>4 hex chars, EDID bytes 10-11"]
"DEL" "41B4"
│ │ style HardwareId fill:#fff3e0
PnP Manufacturer ID Product Code style PnpId fill:#c8e6c9
(3 chars, EDID bytes (4 hex chars, style ProductCode fill:#bbdefb
8-9, compressed) EDID bytes 10-11)
``` ```
The **PnP Manufacturer ID** is a 3-character code assigned by Microsoft (formerly UEFI Forum). The **PnP Manufacturer ID** is a 3-character code assigned by UEFI Forum.
Common laptop display manufacturers: Common laptop display manufacturers:
| PnP ID | Manufacturer | | PnP ID | Manufacturer |
@@ -781,17 +783,18 @@ Common laptop display manufacturers:
##### WMI Instance Name Parsing ##### WMI Instance Name Parsing
``` ```mermaid
WMI InstanceName: "DISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0" flowchart TB
───────┬─────── ──────────┬─────────── InstanceName["WMI InstanceName:<br/>DISPLAY\BOE0900\4#amp;10fd3ab1#amp;0#amp;UID265988_0"]
│ │
Segment 1: "DISPLAY" Segment 3: Device instance InstanceName --> Seg1["Segment 1: DISPLAY<br/>Constant prefix"]
(Constant prefix) InstanceName --> Seg2["Segment 2: BOE0900<br/>Hardware ID<br/>Used for matching with QueryDisplayConfig"]
InstanceName --> Seg3["Segment 3: Device instance<br/>4#amp;10fd3ab1#amp;0#amp;UID265988_0"]
Segment 2: "BOE0900"
style InstanceName fill:#fff3e0
Hardware ID style Seg1 fill:#e0e0e0
(Used for matching with QueryDisplayConfig) style Seg2 fill:#c8e6c9
style Seg3 fill:#e0e0e0
``` ```
##### Monitor Number (Windows Display Settings) ##### Monitor Number (Windows Display Settings)

View File

@@ -9,7 +9,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ManagedCommon; using ManagedCommon;
using PowerDisplay.Common.Helpers; using PowerDisplay.Common.Utils;
using PowerDisplay.Common.Interfaces; using PowerDisplay.Common.Interfaces;
using PowerDisplay.Common.Models; using PowerDisplay.Common.Models;
using WmiLight; using WmiLight;

View File

@@ -6,7 +6,7 @@ using System;
using System.Collections.Frozen; using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
namespace PowerDisplay.Common.Helpers; namespace PowerDisplay.Common.Utils;
/// <summary> /// <summary>
/// Helper class for mapping PnP (Plug and Play) manufacturer IDs to display names. /// Helper class for mapping PnP (Plug and Play) manufacturer IDs to display names.