mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
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:
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user