Table of Contents
DPI
Awareness
A program that says nothing about DPI gets windows Windows scales up itself on displays above 100%: they look blurred, and every size Windows reports is in made-up units. Turn on per-monitor awareness for the threads that create windows:
(win32:set-thread-dpi-awareness :per-monitor-aware-v2)
From then on, the windows the thread creates get:
- sizes and positions in pixels, from every function and message;
- title bars and borders drawn at the display's DPI;
+wm-dpichanged+when they move to a display of another DPI.
Awareness belongs to the thread, and counts for the windows it creates
afterwards. A thread that creates no windows does not need it. The other
awarenesses are :per-monitor-aware (the first version, without scaled
title bars), :system-aware, :unaware and :unaware-gdi-scaled; there
is rarely a reason to use them. The type dpi-awareness is the five.
set-thread-dpi-awareness, window-dpi and window-size-for-client-size
need Windows 10 version 1607 or later. On an older Windows, user32 lacks
the functions they call, and calling them signals an error from the Lisp
implementation that the foreign function is undefined.
A window's DPI
(win32:window-dpi window) ; => 96 at 100%, 120 at 125%, 144 at 150%, 192 at 200%
win32:+default-dpi+ ; => 96
window-dpi is the DPI of the display a window is on. Its scale is
that over +default-dpi+: (/ (win32:window-dpi window) 96) is 3/2 at
150%. A program draws at the same physical size on every display by
scaling what it draws by that.
To size a window by its content, scale the content's size and add the frame at the same DPI:
(let* ((dpi (win32:window-dpi window))
(scale (/ dpi win32:+default-dpi+)))
(multiple-value-bind (width height)
(win32:window-size-for-client-size (round (* 640 scale)) (round (* 400 scale)) :dpi dpi)
(win32:set-window-bounds window :width width :height height)))
Make the window first, then size it: its DPI is known only once it exists, on the display Windows put it on.
Moving between displays
When a window moves to a display of another DPI, it gets
+wm-dpichanged+. wparam's low word is the new DPI, and lparam points
to the bounds Windows suggests for the window, which keep it the same size
at the new scale. Take them:
((= message win32:+wm-dpichanged+)
(setf *scale* (/ (win32:low-word wparam) win32:+default-dpi+))
(multiple-value-bind (left top right bottom) (win32:read-rect lparam)
(win32:set-window-bounds window :x left :y top
:width (- right left) :height (- bottom top)))
0)
The window then gets +wm-size+ with its new client size, before
set-window-bounds returns.