Back to Powertoys

PowerDisplay Module Design Document

doc/devdocs/modules/powerdisplay/design.md

0.98.162.2 KB
Original Source

PowerDisplay Module Design Document

Table of Contents

  1. Background
  2. Problem Statement
  3. Goals
  4. Technical Terminology
  5. Architecture Overview
  6. Component Design
  7. Data Flow and Communication
  8. Sequence Diagrams
  9. Future Considerations
  10. References

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)
  • Support user-defined profiles for quick configuration switching
  • Integrate with LightSwitch module for automatic profile application on theme changes
  • 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 external monitors support DDC/CI, allowing applications to read and modify settings 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.

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.

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:

TermDescription
VCP (Virtual Control Panel)Standardized codes for monitor settings
MCCS (Monitor Command Control Set)VESA standard defining VCP codes
Capabilities StringMonitor-reported string describing supported features

Common VCP Codes Used:

VCP CodeNameDescription
0x10BrightnessDisplay luminance (0-100)
0x12ContrastDisplay contrast ratio (0-100)
0x14Select Color PresetColor temperature presets (sRGB, 5000K, 6500K, etc.)
0x60Input SourceActive video input (HDMI, DP, USB-C, etc.)
0x62VolumeSpeaker/headphone volume (0-100)

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.


Architecture Overview

High-Level Component Architecture

mermaid
flowchart TB
    subgraph PowerToys["PowerToys Application"]
        Runner["Runner (PowerToys.exe)"]
        SettingsUI["Settings UI (WinUI 3)"]
        LightSwitch["LightSwitch Module"]
    end

    subgraph PowerDisplayModule["PowerDisplay Module"]
        ModuleInterface["Module Interface
(PowerDisplayModuleInterface.dll)"]
        PowerDisplayApp["PowerDisplay App
(PowerToys.PowerDisplay.exe)"]
        PowerDisplayLib["PowerDisplay.Lib
(Shared Library)"]
    end

    subgraph External["External"]
        Hardware["Display Hardware
(External + Internal)"]
        Storage["Persistent Storage
(settings.json, profiles.json)"]
    end

    Runner -->|"Loads DLL"| ModuleInterface
    Runner -->|"Hotkey Events"| ModuleInterface
    SettingsUI <-->|"Named Pipes"| Runner
    SettingsUI -->|"Custom Actions
(Launch, ApplyProfile)"| ModuleInterface

    ModuleInterface <-->|"Windows Events
(Show/Toggle/Terminate)"| PowerDisplayApp
    PowerDisplayApp -->|"RefreshMonitors Event"| SettingsUI
    LightSwitch -->|"Theme Events
(Light/Dark)"| PowerDisplayApp

    PowerDisplayApp --> PowerDisplayLib
    PowerDisplayLib -->|"DDC/CI (Dxva2.dll)"| Hardware
    PowerDisplayLib -->|"WMI (WmiLight)"| Hardware
    PowerDisplayLib -->|"ChangeDisplaySettingsEx"| 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 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 & QueryDisplayConfig
│   │   │   ├── MonitorDiscoveryHelper.cs
│   │   │   └── PhysicalMonitorHandleManager.cs
│   │   ├── WMI/
│   │   │   └── WmiController.cs      # WMI implementation (WmiLight library)
│   │   ├── NativeConstants.cs        # Win32 constants (VCP codes, etc.)
│   │   ├── NativeDelegates.cs        # P/Invoke delegate types
│   │   ├── NativeStructures.cs       # Win32 structures
│   │   └── PInvoke.cs                # P/Invoke declarations
│   ├── Interfaces/
│   │   ├── IMonitorController.cs     # Controller abstraction
│   │   ├── IMonitorData.cs           # Monitor data interface
│   │   └── IProfileService.cs        # Profile service interface
│   ├── Models/
│   │   ├── Monitor.cs                # Runtime monitor data
│   │   ├── MonitorCapabilities.cs    # Monitor capability flags
│   │   ├── MonitorOperationResult.cs # Operation result
│   │   ├── MonitorStateEntry.cs      # Persisted monitor state
│   │   ├── MonitorStateFile.cs       # State file schema
│   │   ├── PowerDisplayProfile.cs    # Profile definition
│   │   ├── PowerDisplayProfiles.cs   # Profile collection
│   │   ├── ProfileMonitorSetting.cs  # Per-monitor profile settings
│   │   ├── ColorPresetItem.cs        # Color preset UI item
│   │   ├── VcpCapabilities.cs        # Parsed VCP capabilities
│   │   └── VcpFeatureValue.cs        # VCP feature value (current/min/max)
│   ├── Serialization/
│   │   └── ProfileSerializationContext.cs  # JSON source generation
│   ├── Services/
│   │   ├── DisplayRotationService.cs # Display rotation via ChangeDisplaySettingsEx
│   │   ├── MonitorStateManager.cs    # State persistence (debounced save) and restore on startup
│   │   └── ProfileService.cs         # Profile persistence
│   ├── Utils/
│   │   ├── ColorTemperatureHelper.cs # Color temp utilities
│   │   ├── EventHelper.cs            # Windows Event utilities
│   │   ├── MccsCapabilitiesParser.cs # DDC/CI capabilities parser
│   │   ├── MonitorFeatureHelper.cs   # Monitor feature utilities
│   │   ├── MonitorMatchingHelper.cs  # Profile-to-monitor matching
│   │   ├── MonitorValueConverter.cs  # Value conversion utilities
│   │   ├── PnpIdHelper.cs            # PnP manufacturer ID lookup
│   │   ├── ProfileHelper.cs          # Profile helper utilities
│   │   ├── SimpleDebouncer.cs        # Generic debouncer
│   │   └── VcpNames.cs               # VCP code and value name lookup
│   └── PathConstants.cs              # File path constants
│
├── PowerDisplay/                     # WinUI 3 application
│   ├── Assets/                       # App icons and images
│   ├── Configuration/
│   │   └── AppConstants.cs           # Application constants
│   ├── Helpers/
│   │   ├── DisplayChangeWatcher.cs   # Monitor hot-plug detection (WinRT DeviceWatcher)
│   │   ├── MonitorManager.cs         # Discovery orchestrator
│   │   ├── NativeEventWaiter.cs      # Windows Event waiting
│   │   ├── ResourceLoaderInstance.cs # Resource loader singleton
│   │   ├── SettingsDeepLink.cs       # Deep link to Settings UI
│   │   ├── TrayIconService.cs        # System tray integration
│   │   ├── TypePreservation.cs       # AOT type preservation
│   │   └── WindowHelper.cs           # Window utilities
│   ├── PowerDisplayXAML/
│   │   ├── App.xaml / App.xaml.cs    # Application entry point
│   │   ├── MainWindow.xaml / .cs     # Main UI window
│   │   ├── IdentifyWindow.xaml / .cs # Monitor identify overlay
│   │   └── MonitorIcon.xaml / .cs    # Monitor icon control
│   ├── Serialization/
│   │   └── JsonSourceGenerationContext.cs  # JSON source generation
│   ├── Services/
│   │   └── LightSwitchService.cs     # LightSwitch theme change handling
│   ├── Strings/                      # Localization resources (en-us)
│   ├── Telemetry/
│   │   └── Events/
│   │       └── PowerDisplayStartEvent.cs  # Telemetry event
│   ├── ViewModels/
│   │   ├── ColorTemperatureItem.cs   # Color temperature dropdown item
│   │   ├── InputSourceItem.cs        # Input source dropdown item
│   │   ├── MainViewModel.cs          # Main VM (partial class)
│   │   ├── MainViewModel.Monitors.cs # Monitor discovery methods
│   │   ├── MainViewModel.Settings.cs # Settings persistence methods
│   │   └── MonitorViewModel.cs       # Per-monitor VM
│   ├── GlobalUsings.cs               # Global using directives
│   └── Program.cs                    # Application entry point
│
├── PowerDisplay.Lib.UnitTests/       # Unit tests
│   ├── MccsCapabilitiesParserTests.cs
│   └── MonitorMatchingHelperTests.cs
│
└── PowerDisplayModuleInterface/      # C++ DLL (module interface)
    ├── dllmain.cpp                   # PowertoyModuleIface impl
    ├── Constants.h                   # Module constants (event names, timeouts)
    ├── resource.h                    # Resource definitions
    ├── pch.h / pch.cpp               # Precompiled headers
    └── Trace.h / Trace.cpp           # ETW telemetry tracing

Component Design

PowerDisplay Module Internal Structure

mermaid
flowchart TB
    subgraph ExternalInputs["External Inputs"]
        ModuleInterface["Module Interface
(C++ DLL)"]
        LightSwitch["LightSwitch Module"]
    end

    subgraph WindowsEvents["Windows Events (IPC)"]
        direction LR
        ShowToggleEvents["Show/Toggle/Terminate
Events"]
        ThemeChangedEvent["ThemeChanged
Events"]
    end

    subgraph PowerDisplayModule["PowerDisplay Module"]
        subgraph PowerDisplayApp["PowerDisplay App (WinUI 3)"]
            MainViewModel
            MonitorViewModel
            MonitorManager
            DisplayChangeWatcher["DisplayChangeWatcher
(Hot-Plug Detection)"]
            LightSwitchService["LightSwitchService
(Theme Handler)"]
        end

        subgraph PowerDisplayLib["PowerDisplay.Lib"]
            subgraph Services
                ProfileService
                MonitorStateManager
                DisplayRotationService
            end
            subgraph Drivers
                DdcCiController
                WmiController
            end
            subgraph Utils
                PnpIdHelper["PnpIdHelper
(Manufacturer Names)"]
            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 --> LightSwitchService

    %% App internal
    LightSwitchService -.->|"Get profile name"| MainViewModel
    MainViewModel --> MonitorViewModel
    MonitorViewModel --> MonitorManager
    DisplayChangeWatcher -.->|"DisplayChanged event"| MainViewModel

    %% App to Lib services
    MainViewModel --> ProfileService
    MonitorViewModel --> MonitorStateManager
    MonitorManager --> Drivers
    MonitorManager --> DisplayRotationService

    %% Utils used during discovery
    WmiController --> PnpIdHelper

    %% Services to Storage
    ProfileService --> ProfilesJson
    MonitorStateManager --> MonitorStateJson

    %% Drivers to Hardware
    DdcCiController -->|"DDC/CI"| ExternalMonitor
    WmiController -->|"WMI"| LaptopDisplay
    DisplayRotationService -->|"ChangeDisplaySettingsEx"| ExternalMonitor
    DisplayRotationService -->|"ChangeDisplaySettingsEx"| LaptopDisplay

    %% Force vertical layout: PowerDisplay.Lib above Storage/Hardware
    PowerDisplayLib ~~~ Storage
    PowerDisplayLib ~~~ Hardware

    %% 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 Utils fill:#dcedc8,stroke:#689f38
    style Storage fill:#e1bee7,stroke:#8e24aa
    style Hardware fill:#b2dfdb,stroke:#00897b

DisplayChangeWatcher - Monitor Hot-Plug Detection

The DisplayChangeWatcher component provides automatic detection of monitor connect/disconnect events using the WinRT DeviceWatcher API.

Key Features:

  • Uses DisplayMonitor.GetDeviceSelector() to watch for display device changes
  • Implements 1-second debouncing to coalesce rapid connect/disconnect events
  • Triggers DisplayChanged event to notify MainViewModel for monitor list refresh
  • Runs continuously after initial monitor discovery completes

Implementation Details:

csharp
// Device selector for display monitors
string selector = DisplayMonitor.GetDeviceSelector();
_deviceWatcher = DeviceInformation.CreateWatcher(selector);

// Events monitored
_deviceWatcher.Added += OnDeviceAdded;      // New monitor connected
_deviceWatcher.Removed += OnDeviceRemoved;  // Monitor disconnected
_deviceWatcher.Updated += OnDeviceUpdated;  // Monitor properties changed

Debouncing Strategy:

  • Each device change event schedules a DisplayChanged event after 1 second
  • Subsequent events within the debounce window cancel the previous timer
  • This prevents excessive refreshes when multiple monitors change simultaneously

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
(P/Invoke)"]
        PhysicalMonitorMgr["PhysicalMonitorHandleManager"]
        MonitorDiscovery["MonitorDiscoveryHelper"]
        CapParser["MccsCapabilitiesParser"]

        subgraph Win32["Win32 APIs"]
            User32["User32.dll
EnumDisplayMonitors
GetMonitorInfo"]
            Dxva2["Dxva2.dll
GetVCPFeature
SetVCPFeature
Capabilities"]
        end
    end

    subgraph WMIStack["WMI Stack"]
        WmiLight["WmiLight Library
(Native AOT compatible,
NuGet package)"]
        PnpHelper["PnpIdHelper
(Manufacturer name lookup)"]

        subgraph WMIClasses["WMI Classes (root\\WMI)"]
            WmiMonBright["WmiMonitorBrightness"]
            WmiMonBrightMethods["WmiMonitorBrightnessMethods"]
        end
    end

    subgraph Hardware["Hardware Layer"]
        ExtMon["External Monitor
(DDC/CI capable)"]
        LaptopMon["Laptop Display
(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
    WMI --> PnpHelper
    WmiLight --> WmiMonBright
    WmiLight --> WmiMonBrightMethods

    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(cancellationToken) IEnumerable~Monitor~
        +GetBrightnessAsync(monitor, cancellationToken) VcpFeatureValue
        +SetBrightnessAsync(monitor, brightness, cancellationToken) MonitorOperationResult
        +SetContrastAsync(monitor, contrast, cancellationToken) MonitorOperationResult
        +SetVolumeAsync(monitor, volume, cancellationToken) MonitorOperationResult
        +GetColorTemperatureAsync(monitor, cancellationToken) VcpFeatureValue
        +SetColorTemperatureAsync(monitor, vcpValue, cancellationToken) MonitorOperationResult
        +GetInputSourceAsync(monitor, cancellationToken) VcpFeatureValue
        +SetInputSourceAsync(monitor, inputSource, cancellationToken) MonitorOperationResult
        +Dispose()
    }

    class DdcCiController {
        -_handleManager: PhysicalMonitorHandleManager
        -_discoveryHelper: MonitorDiscoveryHelper
        +Name: "DDC/CI Monitor Controller"
        +DiscoverMonitorsAsync()
        +GetBrightnessAsync(monitor)
        +SetBrightnessAsync(monitor, brightness)
        +SetContrastAsync(monitor, contrast)
        +SetVolumeAsync(monitor, volume)
        +GetColorTemperatureAsync(monitor)
        +SetColorTemperatureAsync(monitor, colorTemperature)
        +GetInputSourceAsync(monitor)
        +SetInputSourceAsync(monitor, inputSource)
        +GetCapabilitiesStringAsync(monitor) string
        -GetVcpFeatureAsync(monitor, vcpCode)
        -CollectCandidateMonitorsAsync()
        -FetchCapabilitiesInParallelAsync()
        -GetPhysicalMonitorsWithRetryAsync()
    }

    class WmiController {
        +Name: "WMI Monitor Controller"
        +DiscoverMonitorsAsync()
        +GetBrightnessAsync(monitor)
        +SetBrightnessAsync(monitor, brightness)
        +SetContrastAsync(monitor, contrast)
        +SetVolumeAsync(monitor, volume)
        +GetColorTemperatureAsync(monitor)
        +SetColorTemperatureAsync(monitor, colorTemperature)
        +GetInputSourceAsync(monitor)
        +SetInputSourceAsync(monitor, inputSource)
        -ExtractHardwareIdFromInstanceName()
        -GetMonitorDisplayInfoByHardwareId()
    }

    IMonitorController <|.. DdcCiController
    IMonitorController <|.. WmiController

Why WmiLight Instead of System.Management

PowerDisplay uses the 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

AspectSystem.ManagementWmiLight
Native AOT Support❌ Not supported✅ Supported (v5.0.0+)
Memory Leaks⚠️ Leaks on remote operations✅ No known leaks
API ComplexityMore verboseSimpler, lighter
Long-running ServicesNot recommended✅ Recommended
Static Linking❌ Not available✅ Optional (PublishWmiLightStaticallyLinked)

References


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 CaseHow Parser Helps
Feature DetectionDetermine if monitor supports contrast, volume, color temperature, input switching
Input Source DropdownExtract valid input source values (e.g., HDMI-1=0x11, DP=0x0F) for UI dropdown
Color Preset ListExtract supported color presets (e.g., sRGB, 5000K, 6500K)
DiagnosticsDisplay raw VCP codes in Settings UI for troubleshooting
PIP/PBP SupportParse 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 mccsParserDesign.md for the complete parser architecture, grammar definition, and implementation details.


Monitor Identification: Handles, IDs, and Names

Understanding how Windows identifies monitors is critical for PowerDisplay's operation. Different Windows APIs use different identifiers, and PowerDisplay must correlate these to provide a unified view across DDC/CI and WMI subsystems.

Windows Display Subsystem Overview

mermaid
flowchart TB
    subgraph WindowsAPIs["Windows Display APIs"]
        EnumDisplayMonitors["EnumDisplayMonitors
(User32.dll)"]
        QueryDisplayConfig["QueryDisplayConfig
(User32.dll)"]
        GetPhysicalMonitors["GetPhysicalMonitorsFromHMONITOR
(Dxva2.dll)"]
        WmiMonitor["WMI root\\WMI
(WmiLight)"]
    end

    subgraph Identifiers["Monitor Identifiers"]
        HMONITOR["HMONITOR
(Logical Monitor Handle)"]
        GdiDeviceName["GDI Device Name
(e.g., \\\\.\\DISPLAY1)"]
        PhysicalHandle["Physical Monitor Handle
(IntPtr for DDC/CI)"]
        DevicePath["Device Path
(Unique per target)"]
        HardwareId["Hardware ID
(e.g., DEL41B4)"]
        InstanceName["WMI Instance Name
(e.g., DISPLAY\\BOE0900\\...)"]
        MonitorNumber["Monitor Number
(1-based, matches Windows Settings)"]
    end

    EnumDisplayMonitors --> HMONITOR
    HMONITOR --> GdiDeviceName
    GetPhysicalMonitors --> PhysicalHandle

    QueryDisplayConfig --> GdiDeviceName
    QueryDisplayConfig --> DevicePath
    QueryDisplayConfig --> HardwareId
    QueryDisplayConfig --> MonitorNumber

    WmiMonitor --> InstanceName
    InstanceName --> HardwareId

    style HMONITOR fill:#e3f2fd
    style GdiDeviceName fill:#fff3e0
    style PhysicalHandle fill:#c8e6c9
    style DevicePath fill:#f3e5f5
    style HardwareId fill:#ffccbc
    style InstanceName fill:#ffe0b2
    style MonitorNumber fill:#b2dfdb

Identifier Definitions

IdentifierSourceFormatExampleScope
HMONITOREnumDisplayMonitorsIntPtr0x00010001Logical monitor (may represent multiple physical monitors in clone mode)
GDI Device NameGetMonitorInfo / QueryDisplayConfigString\\.\DISPLAY1Adapter output; multiple targets can share same GDI name in mirror mode
Physical Monitor HandleGetPhysicalMonitorsFromHMONITORIntPtr0x00000B14DDC/CI communication handle; valid for GetVCPFeature / SetVCPFeature
Device PathQueryDisplayConfigString\\?\DISPLAY#DEL41B4#5&12a3b4c&0&UID123#{...}Unique per target; used as primary key in MonitorDisplayInfo
Hardware IDEDID (via QueryDisplayConfig)StringDEL41B4Manufacturer (3-char PnP ID) + Product Code (4-char hex); identifies monitor model
WMI Instance NameWmiMonitorBrightnessStringDISPLAY\BOE0900\4&10fd3ab1&0&UID265988_0WMI object identifier; contains hardware ID in second segment
Monitor NumberQueryDisplayConfig path indexInteger1, 2, 31-based; matches Windows Settings → Display → "Identify" feature

DDC/CI Monitor Discovery Flow

mermaid
sequenceDiagram
    participant App as PowerDisplay
    participant Enum as EnumDisplayMonitors
    participant Info as GetMonitorInfo
    participant QDC as QueryDisplayConfig
    participant Phys as GetPhysicalMonitors
    participant DDC as DDC/CI (I2C)

    App->>Enum: EnumDisplayMonitors(callback)
    Enum-->>App: HMONITOR handles

    loop For each HMONITOR
        App->>Info: GetMonitorInfo(hMonitor)
        Info-->>App: GDI Device Name (e.g., "\\.\DISPLAY1")

        App->>Phys: GetPhysicalMonitorsFromHMONITOR(hMonitor)
        Phys-->>App: Physical Monitor Handle(s) + Description
    end

    App->>QDC: QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS)
    QDC-->>App: MonitorDisplayInfo[] (DevicePath, GdiDeviceName, HardwareId, MonitorNumber)

    Note over App: Match Physical Handles to MonitorDisplayInfo
using GDI Device Name

    loop For each Physical Handle
        App->>DDC: GetCapabilitiesStringLength(handle)
        DDC-->>App: Capabilities length
        App->>DDC: CapabilitiesRequestAndCapabilitiesReply(handle)
        DDC-->>App: Capabilities string (MCCS format)
    end

    Note over App: Create Monitor objects with:
- Handle (Physical Monitor Handle)
- MonitorNumber (from QueryDisplayConfig)
- GdiDeviceName (for rotation APIs)

WMI Monitor Discovery Flow

mermaid
sequenceDiagram
    participant App as PowerDisplay
    participant WMI as WmiLight
    participant QDC as QueryDisplayConfig
    participant PnP as PnpIdHelper

    App->>WMI: Query WmiMonitorBrightness
    WMI-->>App: InstanceName, CurrentBrightness

    Note over App: Extract HardwareId from InstanceName
"DISPLAY\BOE0900\..." → "BOE0900"

    App->>QDC: GetAllMonitorDisplayInfo()
    QDC-->>App: MonitorDisplayInfo[] (keyed by DevicePath)

    Note over App: Match WMI monitor to QueryDisplayConfig
by comparing HardwareId

    App->>PnP: GetBuiltInDisplayName("BOE0900")
    PnP-->>App: "BOE Built-in Display"

    Note over App: Create Monitor objects with:
- InstanceName (for WMI queries)
- MonitorNumber (from QueryDisplayConfig)
- GdiDeviceName (for rotation APIs)

Key Relationships

GDI Device Name ↔ Physical Monitors
mermaid
flowchart TB
    HMON["HMONITOR (Logical)"]

    HMON --> GDI["GetMonitorInfo()
→ GDI Device Name
\.DISPLAY1"]
    HMON --> GetPhys["GetPhysicalMonitorsFromHMONITOR()"]

    GetPhys --> PM0["Physical Monitor 0
Handle: 0x0B14
Desc: Dell U2722D"]
    GetPhys --> PM1["Physical Monitor 1
Handle: 0x0B18
Desc: Dell U2722D
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. QueryDisplayConfig returns multiple paths with the same GdiDeviceName but different DevicePath values, allowing us to distinguish them.

DisplayPort Daisy Chain (MST - Multi-Stream Transport)

Daisy chaining allows multiple monitors to be connected in series through a single DisplayPort output using MST (Multi-Stream Transport) technology. This creates unique challenges for monitor identification.

mermaid
flowchart LR
    GPU["GPU
(Single DP Port)"]
    MonA["Monitor A
(MST Hub)"]
    MonB["Monitor B
(End)"]

    GPU -->|"DP"| MonA -->|"DP"| MonB

    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:

AspectBehavior
HMONITOREach daisy-chained monitor gets its own HMONITOR
GDI Device NameEach monitor gets a unique GDI name (e.g., \\.\DISPLAY1, \\.\DISPLAY2)
Physical Monitor HandleEach monitor has its own physical handle for DDC/CI
Device PathUnique for each monitor in the chain
Hardware IDDifferent if monitors are different models; same if identical models

MST vs Clone Mode Comparison:

PropertyMST Daisy Chain (Extended Desktop)Clone/Mirror Mode
HMONITORSeparate per monitor (HMONITOR_1, HMONITOR_2, ...)Shared (single HMONITOR_1)
GDI Device NameUnique per monitor (\\.\DISPLAY1, \\.\DISPLAY2, ...)Shared (\\.\DISPLAY1)
Physical HandleOne per HMONITOR (A, B, C)Multiple per HMONITOR (A, B)
DevicePathUnique per monitor (unique1, unique2, ...)Unique per monitor (unique1, unique2)
BehaviorEach monitor = independent logical displayMultiple monitors share same logical display

PowerDisplay Handling of MST:

  1. Discovery: EnumDisplayMonitors returns separate HMONITOR for each MST monitor
  2. Physical Handles: GetPhysicalMonitorsFromHMONITOR returns one handle per HMONITOR
  3. Matching: QueryDisplayConfig provides unique DevicePath for each MST target
  4. DDC/CI: Each monitor in the chain can be controlled independently via its handle

Identifying Same-Model Monitors in Daisy Chain:

When multiple identical monitors are daisy-chained (same Hardware ID), PowerDisplay distinguishes them using:

  • MonitorNumber: Different path index in QueryDisplayConfig (1, 2, 3...)
  • DevicePath: Unique system-generated path for each target
  • Monitor.Id: Format DDC_{HardwareId}_{MonitorNumber} ensures uniqueness

Example with two identical Dell U2722D monitors:

MonitorIdMonitorNumber
Monitor 1DDC_DEL41B4_11
Monitor 2DDC_DEL41B4_22
Connection Mode Summary
ModeHMONITORGDI Device NamePhysical HandlesUse Case
Standard (separate cables)1 per monitorUnique per monitor1 per HMONITORMost common setup
Clone/Mirror1 sharedSharedMultiple per HMONITORPresentation, duplication
MST Daisy Chain1 per monitorUnique per monitor1 per HMONITORReduced cable clutter
USB-C/Thunderbolt Hub1 per monitorUnique per monitor1 per HMONITORLaptop docking

Key Insight: From PowerDisplay's perspective, MST daisy chain and standard multi-cable setups behave identically - each monitor appears as an independent display with unique identifiers. Only clone/mirror mode requires special handling due to shared HMONITOR/GDI names.

Hardware ID Composition
mermaid
flowchart TB
    HardwareId["Hardware ID: DEL41B4"]

    HardwareId --> PnpId["DEL
PnP Manufacturer ID
3 chars, EDID bytes 8-9"]
    HardwareId --> ProductCode["41B4
Product Code
4 hex chars, EDID bytes 10-11"]

    style HardwareId fill:#fff3e0
    style PnpId fill:#c8e6c9
    style ProductCode fill:#bbdefb

The PnP Manufacturer ID is a 3-character code assigned by UEFI Forum. Common laptop display manufacturers:

PnP IDManufacturer
BOEBOE Technology
LGDLG Display
AUOAU Optronics
CMNChi Mei Innolux
SDCSamsung Display
SHPSharp
LENLenovo
DELDell
WMI Instance Name Parsing
mermaid
flowchart TB
    InstanceName["WMI InstanceName:
DISPLAY\BOE0900\4#amp;10fd3ab1#amp;0#amp;UID265988_0"]

    InstanceName --> Seg1["Segment 1: DISPLAY
Constant prefix"]
    InstanceName --> Seg2["Segment 2: BOE0900
Hardware ID
Used for matching with QueryDisplayConfig"]
    InstanceName --> Seg3["Segment 3: Device instance
4#amp;10fd3ab1#amp;0#amp;UID265988_0"]

    style InstanceName fill:#fff3e0
    style Seg1 fill:#e0e0e0
    style Seg2 fill:#c8e6c9
    style Seg3 fill:#e0e0e0
Monitor Number (Windows Display Settings)

The MonitorNumber in PowerDisplay corresponds exactly to the number shown in:

  • Windows Settings → System → Display → "Identify" button
  • The number overlay that appears on each display

This is derived from the path index in QueryDisplayConfig:

  • paths[0] → Monitor 1
  • paths[1] → Monitor 2
  • etc.

Display Rotation and GDI Device Name

The ChangeDisplaySettingsEx API requires the GDI Device Name to target a specific display:

cpp
// Correct: Target specific display by GDI name
ChangeDisplaySettingsEx("\\.\DISPLAY2", &devMode, NULL, 0, NULL);

// Wrong: NULL affects primary display only
ChangeDisplaySettingsEx(NULL, &devMode, NULL, 0, NULL);

PowerDisplay stores GdiDeviceName in each Monitor object specifically for rotation operations.

Cross-Reference Summary

PowerDisplay PropertyDDC/CI SourceWMI Source
Monitor.Id"DDC_{HardwareId}_{MonitorNumber}""WMI_{HardwareId}_{MonitorNumber}"
Monitor.HandlePhysical Monitor HandleN/A (uses InstanceName)
Monitor.InstanceNameN/AWMI InstanceName
Monitor.GdiDeviceNameQueryDisplayConfigQueryDisplayConfig
Monitor.MonitorNumberQueryDisplayConfig path indexQueryDisplayConfig (matched by HardwareId)
Monitor.NameEDID FriendlyName or DescriptionPnpIdHelper.GetBuiltInDisplayName()

Settings UI and PowerDisplay Interaction Architecture

mermaid
flowchart LR
    subgraph SettingsUI["Settings UI Process"]
        direction TB
        Page["PowerDisplayPage.xaml"]
        VM["PowerDisplayViewModel"]
        Page --> VM
    end

    subgraph Runner["Runner Process"]
        direction TB
        Exe["PowerToys.exe"]
        Pipe["Named Pipe IPC"]
        Module["PowerDisplayModuleInterface.dll"]
        Pipe --> Exe --> Module
    end

    subgraph PDApp["PowerDisplay Process"]
        direction TB
        MainVM["MainViewModel"]
        Events["Event Listeners
Refresh / Profile"]
        Events --> MainVM
    end

    subgraph Storage["File System"]
        direction TB
        Settings[("settings.json")]
        Profiles[("profiles.json")]
    end

    %% Main flow: Settings UI → Runner → PowerDisplay
    VM -->|"IPC Message"| Pipe
    Module -->|"SetEvent()"| Events

    %% File access
    VM <-.->|"Read/Write"| Settings
    VM <-.->|"Read/Write"| Profiles
    MainVM <-.->|"Read"| Settings
    MainVM <-.->|"Read/Write"| Profiles

    style SettingsUI fill:#e3f2fd
    style Runner fill:#fff3e0
    style PDApp fill:#e8f5e9
    style Storage fill:#fffde7

Data Models (in Settings.UI.Library):

ModelPurpose
PowerDisplaySettingsMain settings container with properties
MonitorInfoPer-monitor settings displayed in Settings UI (includes feature visibility flags like EnableColorTemperature)

Windows Events for IPC

Event names use fixed GUID suffixes to ensure uniqueness (defined in shared_constants.h).

ConstantDirectionPurpose
TOGGLE_POWER_DISPLAY_EVENTRunner → AppToggle visibility
TERMINATE_POWER_DISPLAY_EVENTRunner → AppTerminate process
REFRESH_POWER_DISPLAY_MONITORS_EVENTSettings → AppRefresh monitor list
SETTINGS_UPDATED_POWER_DISPLAY_EVENTSettings → AppNotify settings changed (feature visibility, tray icon)
LightSwitchLightThemeEventNameLightSwitch → AppApply light mode profile
LightSwitchDarkThemeEventNameLightSwitch → AppApply dark mode profile

Profile Application via Named Pipe IPC:

Profile application from Settings UI uses Named Pipe IPC (via Runner's call_custom_action) instead of Windows Events. When the user clicks "Apply" on a profile in Settings UI, the message is sent through the Runner to the Module Interface, which forwards it to PowerDisplay.exe via Named Pipe.

Event Name Format: Local\PowerToysPowerDisplay-{EventType}-{GUID}

Example: Local\PowerToysPowerDisplay-ToggleEvent-5f1a9c3e-7d2b-4e8f-9a6c-3b5d7e9f1a2c


LightSwitch Profile Integration Architecture

mermaid
flowchart TB
    subgraph LightSwitchModule["LightSwitch Module (C++)"]
        StateManager["LightSwitchStateManager"]
        ThemeEval["Theme Evaluation
(Time/System)"]
        LightSwitchSettings["LightSwitchSettings"]
        NotifyPD["NotifyPowerDisplay(isLight)"]
    end

    subgraph PowerDisplayModule["PowerDisplay Module (C#)"]
        subgraph App["PowerDisplay App"]
            EventWaiter["NativeEventWaiter
(Background Thread)"]
            LightSwitchSvc["LightSwitchService
(Static Helper)"]
            MainViewModel["MainViewModel"]
        end

        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
{lightProfile, darkProfile}"]
        PDProfilesJson["PowerDisplay/profiles.json
{profiles: [...]}"]
    end

    subgraph Hardware["Hardware"]
        Monitors["Connected Monitors"]
    end

    %% LightSwitch flow
    ThemeEval -->|"Time boundary
or manual"| StateManager
    StateManager --> LightSwitchSettings
    StateManager --> NotifyPD
    NotifyPD -->|"isLight=true"| LightEvent
    NotifyPD -->|"isLight=false"| DarkEvent

    %% PowerDisplay flow - theme determined from event
    LightEvent -->|"Event signaled"| EventWaiter
    DarkEvent -->|"Event signaled"| EventWaiter
    EventWaiter -->|"isLightMode"| LightSwitchSvc
    LightSwitchSvc -->|"GetProfileForTheme()"| LSSettingsJson
    LightSwitchSvc -->|"Profile name"| MainViewModel
    MainViewModel -->|"LoadProfiles()"| ProfileService
    ProfileService <--> PDProfilesJson
    MainViewModel -->|"ApplyProfileAsync()"| MonitorVMs
    MonitorVMs --> Controllers
    Controllers --> Monitors

    style LightSwitchModule fill:#ffccbc
    style PowerDisplayModule fill:#c8e6c9
    style App fill:#a5d6a7
    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])
    Start --> MM["MonitorManager.DiscoverMonitorsAsync()"]

    MM --> DDC["DdcCiController.DiscoverMonitorsAsync()"]
    MM --> WMI["WmiController.DiscoverMonitorsAsync()"]

    DDC --> Merge["Merge Results"]
    WMI --> Merge

    Merge --> Sort["Sort by MonitorNumber"]
    Sort --> Update["UpdateMonitorList()"]
    Update --> Check{"RestoreSettingsOnStartup?"}
    Check -->|Yes| Restore["RestoreMonitorSettingsAsync()
(Set hardware values)"]
    Check -->|No| Done
    Restore --> Done([Discovery Complete])

    style Start fill:#e8f5e9
    style Done fill:#e8f5e9
    style DDC fill:#e3f2fd
    style WMI fill:#fff3e0
    style Restore fill:#fff9c4

Note: DDC/CI and WMI discovery run in parallel via Task.WhenAll.

Settings Restore: When RestoreSettingsOnStartup is enabled, RestoreMonitorSettingsAsync() is called after monitor discovery to restore saved brightness, contrast, color temperature, and volume values to the hardware. The UI remains in "scanning" state until restore completes.

DDC/CI Discovery (Three-Phase Approach)

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:
FetchCapabilities
~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:
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
from InstanceName"]
    QDC["QueryDisplayConfig"] --> Match["Match by HardwareId"]
    Extract --> Match
    Match --> Name["Get Display Name
via PnpIdHelper"]
    Name --> Create["Create Monitor
Brightness + WMI"]

    style Query fill:#fff3e0
    style Create fill:#fff3e0

Key Differences

AspectDDC/CIWMI
TargetExternal monitorsInternal laptop displays
CapabilitiesFull VCP support (brightness, contrast, volume, color temp, input)Brightness only
DiscoveryThree-phase with parallel I2C fetchingSingle WMI query
InitializationReads current values for all supported VCP codesBrightness from query result
Performance~4s per monitor (I2C), parallelizedFast (~100ms total)

Sequence Diagrams

Sequence: Modifying Color Temperature in Flyout UI

Color temperature adjustment is now handled directly in the PowerDisplay Flyout UI, providing a more responsive user experience without requiring IPC round-trips to Settings UI.

mermaid
sequenceDiagram
    participant User
    participant Flyout as MainWindow (Flyout)
    participant MonitorVM as MonitorViewModel
    participant MonitorManager
    participant Controller as DdcCiController
    participant StateManager as MonitorStateManager
    participant Monitor as Physical Monitor

    User->>Flyout: Opens PowerDisplay flyout
(via hotkey or tray icon)

    Note over Flyout: Color temperature switcher visible
(if enabled in Settings)

    User->>Flyout: Selects color temperature preset
from dropdown (e.g., 6500K)

    Flyout->>MonitorVM: ColorTemperatureListView_SelectionChanged
    MonitorVM->>MonitorVM: SetColorTemperatureAsync(vcpValue)

    MonitorVM->>MonitorManager: SetColorTemperatureAsync(monitor, vcpValue)

    MonitorManager->>Controller: SetColorTemperatureAsync(monitor, vcpValue)
    Controller->>Controller: SetVcpFeatureAsync(VcpCodeColorTemperature)
    Controller->>Monitor: SetVCPFeature(0x14, vcpValue)
    Monitor-->>Controller: OK

    Controller-->>MonitorManager: MonitorOperationResult.Success
    MonitorManager-->>MonitorVM: Success

    MonitorVM->>MonitorVM: RefreshAvailableColorPresets()
    Note over MonitorVM: Regenerate ColorTemperatureItem list
with updated IsSelected flags

    MonitorVM->>StateManager: UpdateMonitorParameter("ColorTemperature", vcpValue)

    Note over StateManager: Debounced save (2 seconds)
    StateManager->>StateManager: Schedule file write

    Note over StateManager: After 2s idle
    StateManager->>StateManager: SaveToFile(monitor_state.json)

    Note over MonitorVM: UI updates to show
selected preset with checkmark

Color Temperature Selection UI:

The color temperature switcher displays a list of available presets (e.g., 5000K, 6500K, sRGB). Each preset shows a checkmark icon when selected. The ColorTemperatureItem class stores IsSelected state, which is updated by regenerating the entire AvailableColorPresets list after a successful color temperature change. This ensures the checkmark displays correctly for the newly selected preset.

Flyout Display Options:

The Flyout UI visibility is controlled by a combination of global settings and per-monitor settings:

Global Settings (in PowerDisplayProperties):

SettingDefaultDescription
ShowProfileSwitchertrueShow profile switcher (also requires profiles to exist)
ShowIdentifyMonitorsButtontrueShow "Identify Monitors" button

Per-Monitor Settings (in MonitorInfo):

SettingDefaultDescription
EnableContrasttrue (if supported)Show contrast slider for this monitor
EnableVolumetrue (if supported)Show volume slider for this monitor
EnableInputSourcetrue (if supported)Show input source selector for this monitor
EnableRotationtrueShow rotation control for this monitor
EnableColorTemperaturetrue (if supported)Show color temperature switcher for this monitor
IsHiddenfalseHide this monitor from the flyout entirely

Users can configure per-monitor visibility in Settings UI under the "Monitors" section. Each monitor shows checkboxes for the features it supports, allowing fine-grained control over the flyout UI.

Color Temperature Warning Dialog:

When enabling EnableColorTemperature for a monitor in Settings UI, a warning dialog is displayed to inform users about potential risks. Color temperature changes can cause unpredictable results on some monitors, including incorrect colors, display malfunction, or settings that cannot be reverted. The dialog requires explicit confirmation before enabling the feature.

Implementation notes:

  • The warning dialog only appears when the user explicitly checks the checkbox (not during initial page load)
  • A _isPageLoaded flag prevents the dialog from appearing during data binding
  • If the user cancels the dialog, the checkbox is reverted to unchecked state

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
(brightness, contrast, etc.)
    User->>ProfileDialog: Clicks "Save"

    ProfileDialog->>ProfileDialog: Validate inputs
    Note over ProfileDialog: Check name unique,
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 EventWaiter as NativeEventWaiter
    participant LSSvc as LightSwitchService
    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
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 EventWaiter: Background thread waiting
on both Light and Dark events
    EventWaiter->>WinEvent: WaitAny([lightEvent, darkEvent]) returns index

    Note over EventWaiter: Theme determined from event:
index 0 = Light, index 1 = Dark
    EventWaiter->>LSSvc: GetProfileForTheme(isLightMode)
    LSSvc->>LSSvc: Read LightSwitch/settings.json
    LSSvc-->>EventWaiter: profileName (or null)

    EventWaiter->>MainVM: Dispatch to UI thread with profileName

    MainVM->>ProfileService: LoadProfiles()
    ProfileService-->>MainVM: PowerDisplayProfiles

    MainVM->>MainVM: Find profile by name
    MainVM->>MainVM: ApplyProfileAsync(profile.MonitorSettings)

    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

        alt Orientation specified
            MainVM->>MonitorVM: SetOrientationAsync(orientation)
            MonitorVM->>Controller: SetRotationAsync(monitor, orientation)
            Controller->>Monitor: ChangeDisplaySettingsEx
        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 StateManager as MonitorStateManager
    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()

        alt RestoreSettingsOnStartup enabled
            PowerDisplayApp->>StateManager: GetMonitorParameters(monitorId)
            StateManager-->>PowerDisplayApp: Saved brightness, contrast, etc.
            PowerDisplayApp->>MonitorManager: SetBrightnessAsync(savedValue)
            PowerDisplayApp->>MonitorManager: SetContrastAsync(savedValue)
            Note over PowerDisplayApp: Restore all saved settings to hardware
        end

        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)

Future Considerations

Already Implemented

  • Monitor Hot-Plug: DisplayChangeWatcher uses WinRT DeviceWatcher + DisplayMonitor API with 1-second debouncing
  • Display Rotation: DisplayRotationService uses Windows ChangeDisplaySettingsEx API
  • LightSwitch Integration: Automatic profile application on theme changes via LightSwitchService
  • Monitor Identification: Overlay windows showing monitor numbers via IdentifyWindow
  • Mirror Mode Support: Correct orientation sync for multiple monitors sharing the same GDI device name

Potential Future Enhancements

  1. Advanced Color Management: Integration with Windows Color Management APIs (HDR, ICC profiles)
  2. PIP/PBP Control: Picture-in-Picture and Picture-by-Picture configuration (VcpCapabilities already parses window capabilities)
  3. Power State Control: Monitor power on/off via VCP code 0xD6

References