我正在尝试使用 PySDL2 绑定为带有 SDL2 的 Windows 10 编写一个 DPI 感知应用程序。现在我不关心支持比周年更新更早的任何其他操作系统或 Windows 版本。问题是,当在具有不同缩放因子的监视器之间拖动时,我没有收到需要调整窗口大小的 WM_DPICHANGED 消息。
我使用 Windows API 将整个过程设置为 per-monitor-dpi-aware (v2),这似乎工作正常。窗口内容从不缩放,我可以正确轮询当前显示 DPI,并且 Windows 正在缩放非客户区(例如标题栏),正如它们应该的那样。其他本地 Windows 消息也通过 WM_MOUSEMOVE 看到。
版本:Windows 10 1809、Python 3.7.2、SDL 2.0.9 全 64 位、PySDL2 0.9.6,如果您想在本地运行,您需要将 SDL2.dll 放在工作目录的“/native”子目录中, 或适当修改第 3 行
import sys
import os
os.environ["PYSDL2_DLL_PATH"] = os.path.abspath(os.getcwd() + "/native")
import sdl2.ext
import ctypes
# https://docs.microsoft.com/de-de/windows/desktop/hidpi/wm-dpichanged
WM_DPICHANGED = 0x02E0
WM_MOUSEMOVE = 0x0200
# https://docs.microsoft.com/de-de/windows/desktop/hidpi/dpi-awareness-context
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
def run():
# Set the DPI Awareness Context to DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 (-4), which means we need to do all
# DPI scaling by ourselves, EXCEPT for win32 popups and non client areas (like the title bar)
# This function was introduced with the Windows 10 Anniversary Update, so we should probably have fallbacks for
# other operating system versions
# https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setprocessdpiawarenesscontext
if not ctypes.windll.user32.SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2):
print("Unable to set DPI awareness")
sdl2.ext.init()
# We want to receive native WM_DPICHANGED messages, so we need to tell SDL to pass them through
sdl2.SDL_EventState(sdl2.SDL_SYSWMEVENT, sdl2.SDL_ENABLE)
window_width = 640
window_height = 480
window = sdl2.ext.Window("λ³", (window_width, window_height), flags=sdl2.SDL_WINDOW_OPENGL | sdl2.SDL_WINDOW_ALLOW_HIGHDPI)
sdl2_window = window.window
sdl2.SDL_SetWindowResizable(sdl2_window, True)
window.show()
running = True
scaling_percentage = 100
while running:
sdl2_events = sdl2.ext.get_events()
for sdl2_event in sdl2_events:
if sdl2_event.type == sdl2.SDL_QUIT:
running = False
break
elif sdl2_event.type == sdl2.SDL_SYSWMEVENT:
message_pointer = sdl2_event.syswm.msg
# Workaround because the default pointer type that is returned has no definition for the structure
message_pointer_usable = ctypes.cast(message_pointer, ctypes.POINTER(sdl2.syswm.SDL_SysWMmsg))
sys_wm_msg = message_pointer_usable.contents
windows_msg = message_pointer_usable.contents.msg.win
# On 32 bit Windows both W and L are 32 bit, on 64 bit Windows both are 64 bit
# When values are stored in the 'high' and 'low' 'word' of W or L, we still mean the first 16 bit or
# 16 bit after that, regardless of the platform
l_high = (windows_msg.lParam & 0xFFFF0000) >> 16
l_low = windows_msg.lParam & 0xFFFF
w_high = (windows_msg.wParam & 0xFFFF0000) >> 16
w_low = windows_msg.wParam & 0xFFFF
if windows_msg.msg == WM_DPICHANGED:
print("This never gets called")
if windows_msg.msg == WM_MOUSEMOVE:
# This works
print(f"x {l_low}, y {l_high}")
display_index = sdl2.SDL_GetWindowDisplayIndex(sdl2_window)
diagonal_dpi = ctypes.c_float()
horizontal_dpi = ctypes.c_float()
vertical_dpi = ctypes.c_float()
# This also works and reports the percentage correctly when dragging between monitors
sdl2.SDL_GetDisplayDPI(display_index, ctypes.byref(diagonal_dpi), ctypes.byref(horizontal_dpi), ctypes.byref(vertical_dpi))
new_scaling_percentage = int(diagonal_dpi.value/96*100)
if new_scaling_percentage != scaling_percentage:
scaling_percentage = new_scaling_percentage
print(f"Display scale changed to {scaling_percentage}%")
sdl2.SDL_Delay(10)
sdl2.ext.quit()
return 0
if __name__ == "__main__":
sys.exit(run())