Skip to content

Instantly share code, notes, and snippets.

@io-mi
Last active April 11, 2024 06:46
Show Gist options
  • Select an option

  • Save io-mi/ff5dfc45990b4564397216f42fccbed9 to your computer and use it in GitHub Desktop.

Select an option

Save io-mi/ff5dfc45990b4564397216f42fccbed9 to your computer and use it in GitHub Desktop.
Get monitor metrics (DPI and scale factor) in python win32
from ctypes import POINTER, WINFUNCTYPE, Array, Structure, sizeof, windll, c_uint32, byref, HRESULT, c_void_p
from ctypes.wintypes import BOOL, HANDLE, HDC, HMONITOR, HWND, LONG, LPARAM, LPDWORD, LPRECT, LPWSTR, PDWORD, PHANDLE, PUINT, RECT, UINT, POINT, DWORD, USHORT, WCHAR
from enum import IntEnum, auto
class MONITOR_DPI_TYPE(IntEnum):
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
class DEVICE_SCALE_FACTOR(IntEnum):
DEVICE_SCALE_FACTOR_INVALID = 0
SCALE_100_PERCENT = 100
SCALE_120_PERCENT = 120
SCALE_125_PERCENT = 125
SCALE_140_PERCENT = 140
SCALE_150_PERCENT = 150
SCALE_160_PERCENT = 160
SCALE_175_PERCENT = 175
SCALE_180_PERCENT = 180
SCALE_200_PERCENT = 200
SCALE_225_PERCENT = 225
SCALE_250_PERCENT = 250
SCALE_300_PERCENT = 300
SCALE_350_PERCENT = 350
SCALE_400_PERCENT = 400
SCALE_450_PERCENT = 450
SCALE_500_PERCENT = 500
class MC_DISPLAY_TECHNOLOGY_TYPE(IntEnum):
MC_SHADOW_MASK_CATHODE_RAY_TUBE = auto()
MC_APERTURE_GRILL_CATHODE_RAY_TUBE = auto()
MC_THIN_FILM_TRANSISTOR = auto()
MC_LIQUID_CRYSTAL_ON_SILICON = auto()
MC_PLASMA = auto()
MC_ORGANIC_LIGHT_EMITTING_DIODE = auto()
MC_ELECTROLUMINESCENT = auto()
MC_MICROELECTROMECHANICAL = auto()
MC_FIELD_EMISSION_DEVICE = auto()
class MONITORINFOEXW(Structure):
_fields_ = (
("cbSize", DWORD),
("rcMonitor", RECT),
("rcWork", RECT),
("dwFlags", DWORD),
("szDevice", WCHAR*32)
)
class UNICODE_STRING(Structure):
_fields_ = (
("Length", USHORT),
("MaximumLength", USHORT),
("Buffer", LPWSTR)
)
class PHYSICAL_MONITOR(Structure):
_fields_ = (
("hPhysicalMonitor", HANDLE),
("szPhysicalMonitorDescription", WCHAR*128)
)
MONITOR_DEFAULTTONULL = 0x00000000
MONITOR_DEFAULTTOPRIMARY = 0x00000001
MONITOR_DEFAULTTONEAREST = 0x00000002
# src: https://docs.microsoft.com/en-us/windows/win32/com/com-error-codes-5
ERROR_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE = 0xc026258c
ERROR_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA = 0xc0262582
MONITORENUMPROC = WINFUNCTYPE(BOOL, HMONITOR, HDC, LPRECT, LPARAM)
windll.shcore.GetDpiForMonitor.argtypes = HMONITOR, c_uint32, PUINT, PUINT,
windll.shcore.GetDpiForMonitor.restype = HRESULT
windll.shcore.GetScaleFactorForMonitor.argtypes = HMONITOR, c_void_p,
windll.shcore.GetScaleFactorForMonitor.restype = HRESULT
windll.user32.MonitorFromPoint.argtypes = POINT, DWORD,
windll.user32.MonitorFromPoint.restype = HMONITOR
windll.user32.MonitorFromWindow.argtypes = HWND, DWORD,
windll.user32.MonitorFromWindow.restype = HMONITOR
windll.user32.EnumDisplayMonitors.argtypes = HDC, LPRECT, MONITORENUMPROC, LPARAM,
windll.user32.EnumDisplayMonitors.restype = BOOL
windll.user32.GetMonitorInfoW.argtypes = HMONITOR, POINTER(MONITORINFOEXW),
windll.user32.GetMonitorInfoW.restype = BOOL
windll.gdi32.GetNumberOfPhysicalMonitors.argtypes = POINTER(UNICODE_STRING), LPDWORD,
windll.gdi32.GetNumberOfPhysicalMonitors.restype = LONG
windll.gdi32.GetPhysicalMonitors.argtypes = POINTER(UNICODE_STRING), DWORD, PDWORD, PHANDLE,
windll.gdi32.GetPhysicalMonitors.restype = LONG
windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR.argtypes = HMONITOR, LPDWORD,
windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR.restype = BOOL
windll.dxva2.GetPhysicalMonitorsFromHMONITOR.argtypes = HMONITOR, DWORD, POINTER(PHYSICAL_MONITOR),
windll.dxva2.GetPhysicalMonitorsFromHMONITOR.restype = BOOL
windll.dxva2.DestroyPhysicalMonitor.argtypes = HANDLE,
windll.dxva2.DestroyPhysicalMonitor.restype = BOOL
windll.dxva2.DestroyPhysicalMonitors.argtypes = DWORD, POINTER(PHYSICAL_MONITOR),
windll.dxva2.DestroyPhysicalMonitors.restype = BOOL
windll.dxva2.GetMonitorCapabilities.argtypes = HANDLE, LPDWORD, LPDWORD,
windll.dxva2.GetMonitorCapabilities.restype = BOOL
windll.dxva2.GetMonitorTechnologyType.argtypes = HANDLE, POINTER(c_uint32),
windll.dxva2.GetMonitorTechnologyType.restype = BOOL
def GetDpiForMonitor(hmonitor: HMONITOR, dpiType: MONITOR_DPI_TYPE) -> tuple[UINT, UINT]:
dpiX = UINT()
dpiY = UINT()
if windll.shcore.GetDpiForMonitor(hmonitor, dpiType, byref(dpiX), byref(dpiY)) != 0:
raise Exception("GetDpiForMonitor failed.")
return (dpiX.value, dpiY.value)
def GetScaleFactorForMonitor(hmonitor: HMONITOR) -> DEVICE_SCALE_FACTOR:
scale = c_uint32()
if windll.shcore.GetScaleFactorForMonitor(hmonitor, byref(scale)) != 0:
raise Exception("GetScaleFactorForMonitor failed.")
return scale.value
def MonitorFromPoint(pt: POINT, dwFlags: DWORD) -> HMONITOR:
return windll.user32.MonitorFromPoint(pt, dwFlags)
def MonitorFromWindow(hwnd: HWND, dwFlags: DWORD ) -> HMONITOR:
return windll.user32.MonitorFromWindow(hwnd, dwFlags)
def EnumDisplayMonitors() -> set[HMONITOR]:
monitors = set()
@MONITORENUMPROC
def callback(hm, dc, pr, pd):
monitors.add(hm)
return True
windll.user32.EnumDisplayMonitors(None, None, callback, None)
return monitors
def GetMonitorInfoW(hMonitor: HMONITOR) -> MONITORINFOEXW:
mi = MONITORINFOEXW()
mi.cbSize = sizeof(MONITORINFOEXW)
if not windll.user32.GetMonitorInfoW(hMonitor, byref(mi)):
raise Exception("GetMonitorInfoW failed")
return mi
def GetNumberOfPhysicalMonitors(strDeviceName: UNICODE_STRING) -> DWORD:
dwNumberOfPhysicalMonitors = DWORD()
if not windll.gdi32.GetNumberOfPhysicalMonitors(byref(strDeviceName), byref(dwNumberOfPhysicalMonitors)):
raise Exception("GetNumberOfPhysicalMonitors failed")
return dwNumberOfPhysicalMonitors
def GetPhysicalMonitors(strDeviceName: UNICODE_STRING, dwPhysicalMonitorArraySize: DWORD) -> Array[HANDLE]:
dwNumPhysicalMonitorHandlesInArray = DWORD()
hPhysicalMonitorArray = (HANDLE*dwPhysicalMonitorArraySize.value)()
if not windll.gdi32.GetPhysicalMonitors(byref(strDeviceName), dwPhysicalMonitorArraySize, byref(dwNumPhysicalMonitorHandlesInArray), hPhysicalMonitorArray):
raise Exception("GetPhysicalMonitors failed")
return hPhysicalMonitorArray
def GetNumberOfPhysicalMonitorsFromHMONITOR(hMonitor: HMONITOR) -> DWORD:
dwNumberOfPhysicalMonitors = DWORD()
if not windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR(hMonitor, byref(dwNumberOfPhysicalMonitors)):
raise Exception("GetNumberOfPhysicalMonitorsFromHMONITOR failed")
return dwNumberOfPhysicalMonitors
def GetPhysicalMonitorsFromHMONITOR(hMonitor: HMONITOR, dwPhysicalMonitorArraySize: DWORD) -> Array[PHYSICAL_MONITOR]:
physicalMonitorArray = (PHYSICAL_MONITOR * dwPhysicalMonitorArraySize.value)()
if not windll.dxva2.GetPhysicalMonitorsFromHMONITOR(hMonitor, dwPhysicalMonitorArraySize, physicalMonitorArray):
raise Exception("GetPhysicalMonitorsFromHMONITOR failed")
return physicalMonitorArray
def DestroyPhysicalMonitor(hMonitor: HANDLE) -> None:
if not windll.dxva2.DestroyPhysicalMonitor(hMonitor):
raise Exception("DestroyPhysicalMonitor failed")
def DestroyPhysicalMonitors(dwPhysicalMonitorArraySize: DWORD, pPhysicalMonitorArray: POINTER(PHYSICAL_MONITOR)) -> None:
if not windll.dxva2.DestroyPhysicalMonitors(dwPhysicalMonitorArraySize, pPhysicalMonitorArray):
raise Exception("DestroyPhysicalMonitors failed")
def GetMonitorCapabilities(hMonitor: HANDLE) -> tuple[DWORD, DWORD]:
dwMonitorCapabilities = DWORD()
dwSupportedColorTemperatures = DWORD()
if not windll.dxva2.GetMonitorCapabilities(hMonitor, byref(dwMonitorCapabilities), byref(dwSupportedColorTemperatures)):
raise Exception("GetMonitorCapabilities failed")
return dwMonitorCapabilities, dwSupportedColorTemperatures
def GetMonitorTechnologyType(hMonitor: HANDLE) -> MC_DISPLAY_TECHNOLOGY_TYPE:
dtyDisplayTechnologyType = c_uint32()
if not windll.dxva2.GetMonitorTechnologyType(hMonitor, byref(dtyDisplayTechnologyType)):
raise Exception("GetMonitorTechnologyType failed")
return MC_DISPLAY_TECHNOLOGY_TYPE(dtyDisplayTechnologyType.value)
def GetLastError() -> DWORD:
return windll.kernel32.GetLastError()
if __name__ == "__main__":
monitor = MonitorFromWindow(None, MONITOR_DEFAULTTOPRIMARY)
dpi = GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_RAW_DPI)
scale = GetScaleFactorForMonitor(monitor)
print("dpi :", dpi)
print("scale :", scale)
print("true-dpi :", tuple(map(lambda v: v * scale / 100, dpi)))
# npm = GetNumberOfPhysicalMonitorsFromHMONITOR(monitor)
# arr = GetPhysicalMonitorsFromHMONITOR(monitor, npm)
# DestroyPhysicalMonitors(npm, arr)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment