Implementing DPI-Aware Functionality in PowerToys Modules Using DPIAware
PowerToys modules achieve per-monitor DPI awareness by calling DPIAware::EnableDPIAwarenessForThisProcess() at startup and using conversion helpers like DPIAware::Convert() to scale UI coordinates between logical (96 DPI) and physical pixels.
PowerToys requires pixel-perfect rendering across displays with varying scaling factors. The centralized DPIAware utility in src/common/Display abstracts Win32 DPI APIs, providing a testable interface that modules use instead of calling GetDpiForMonitor or SetProcessDpiAwarenessContext directly.
Understanding the DPIAware Architecture
The DPIAware namespace in src/common/Display/dpi_aware.h encapsulates all DPI-related functionality. The implementation in src/common/Display/dpi_aware.cpp wraps the modern Shell Scaling API, handling per-monitor awareness and coordinate conversion.
Key components include:
EnableDPIAwarenessForThisProcess: Sets process-wide DPI awarenessGetScreenDPIForMonitorandGetScreenDPIForWindow: Retrieve scaling factorsConvertandInverseConvert: Transform between logical and physical coordinatesConvertByCursorPosition: Scale based on the monitor under the cursor
Enabling Per-Monitor DPI Awareness
Every PowerToys executable must opt into per-monitor-v2 DPI awareness before creating any UI. This is done exactly once per process.
In src/runner/main.cpp at line 184, the Runner initializes awareness:
#include "dpi_aware.h"
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
DPIAware::EnableDPIAwarenessForThisProcess(); // Required for per-monitor-v2
// ... create window and run message loop
}
The implementation calls SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) as seen in lines 31-33 of src/common/Display/dpi_aware.cpp.
Converting Coordinates and Dimensions
After enabling awareness, modules must convert logical coordinates (based on 96 DPI) to physical pixels for the specific monitor displaying the window.
Window-Based Scaling
Use DPIAware::Convert when you have a window handle and need to scale dimensions or rectangles to the monitor containing that window.
From src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp at line 317:
#include "dpi_aware.h"
void ScaleWindowRect(HWND hwnd, RECT& rect)
{
// Convert logical coordinates (96 DPI) to physical pixels
DPIAware::Convert(hwnd, rect);
}
The Convert method accepts both HWND and HMONITOR, allowing usage even before window creation if you have a monitor handle.
Cursor-Based Scaling
When positioning UI based on the current mouse location (such as context menus or floating toolbars), use cursor-based helpers to get the DPI of the monitor under the cursor.
From src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp at line 128:
#include "dpi_aware.h"
void ResizeBasedOnCursor(float& width, float& height)
{
DPIAware::ConvertByCursorPosition(width, height);
}
Similarly, src/modules/alwaysontop/AlwaysOnTop/ScalingUtils.cpp at line 10 retrieves DPI for an existing window:
auto res = DPIAware::GetScreenDPIForWindow(window, dpi);
Integration Examples from PowerToys Modules
The DPIAware utility provides a consistent pattern across all PowerToys modules:
- Runner (
src/runner/main.cpp): Initializes process-wide awareness before any UI creation - FancyZones (
src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp): Scales zone rectangles usingConvertandInverseConvert - Keyboard Manager (
src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp): Adapts editor window size based on cursor position usingConvertByCursorPosition - Always-On-Top (
src/modules/alwaysontop/AlwaysOnTop/ScalingUtils.cpp): Retrieves monitor-specific DPI for window scaling usingGetScreenDPIForWindow
Summary
- Enable awareness early: Call
DPIAware::EnableDPIAwarenessForThisProcess()once at process startup before creating any windows. - Use conversion helpers: Scale logical coordinates to physical pixels using
DPIAware::Convertfor window-based operations orDPIAware::ConvertByCursorPositionfor cursor-based positioning. - Centralized implementation: All DPI logic lives in
src/common/Display/dpi_aware.handsrc/common/Display/dpi_aware.cpp, ensuring consistent behavior across PowerToys modules. - Testable abstraction: The helper functions wrap Win32 APIs like
GetDpiForMonitorandSetProcessDpiAwarenessContext, providing a mockable interface for unit testing.
Frequently Asked Questions
When should I call EnableDPIAwarenessForThisProcess?
You must call DPIAware::EnableDPIAwarenessForThisProcess() exactly once per executable, before any UI thread creates windows or accesses display metrics. In PowerToys, this happens in src/runner/main.cpp at line 184 and in each module's entry point that creates its own process.
How do I handle DPI changes when moving a window between monitors?
Use DPIAware::Convert with the window handle when positioning or sizing the window. The function automatically retrieves the DPI for the monitor containing the window. For operations based on mouse position (like showing a context menu), use DPIAware::ConvertByCursorPosition to get the DPI of the monitor currently under the cursor.
What is the difference between Convert and InverseConvert?
DPIAware::Convert transforms logical coordinates (based on 96 DPI) into physical pixels for the target monitor. DPIAware::InverseConvert performs the reverse operation, converting physical pixel measurements back to logical coordinates. FancyZones uses both when calculating zone layouts and converting them to window positions.
Can I use DPIAware in a new PowerToys module?
Yes. Include #include "dpi_aware.h" from src/common/Display in your module's source files. Call DPIAware::EnableDPIAwarenessForThisProcess() at startup if your module runs in its own process, then use DPIAware::Convert or DPIAware::GetScreenDPIForWindow whenever you need to scale UI elements to the current monitor's DPI.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →