Last active
April 11, 2024 06:46
-
-
Save io-mi/ff5dfc45990b4564397216f42fccbed9 to your computer and use it in GitHub Desktop.
Get monitor metrics (DPI and scale factor) in python win32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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