Created
December 26, 2025 03:36
-
-
Save Zayrick/09218b7fc19db984e8558fe4687c0c06 to your computer and use it in GitHub Desktop.
Windows 显示器信息查询工具,通过 Win32 API 和注册表 EDID 数据获取活动/非活动显示器的详细硬件参数(分辨率、物理尺寸、厂商信息等)
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
| import winreg | |
| import ctypes | |
| from ctypes import wintypes | |
| # ========== Win32 API 常量和结构体定义 ========== | |
| CCHDEVICENAME = 32 | |
| MONITORINFOF_PRIMARY = 0x00000001 | |
| class MONITORINFOEX(ctypes.Structure): | |
| """扩展的显示器信息结构体""" | |
| _fields_ = [ | |
| ("cbSize", wintypes.DWORD), | |
| ("rcMonitor", wintypes.RECT), | |
| ("rcWork", wintypes.RECT), | |
| ("dwFlags", wintypes.DWORD), | |
| ("szDevice", wintypes.WCHAR * CCHDEVICENAME), | |
| ] | |
| # 回调函数类型 | |
| MONITORENUMPROC = ctypes.WINFUNCTYPE( | |
| wintypes.BOOL, | |
| wintypes.HMONITOR, | |
| wintypes.HDC, | |
| ctypes.POINTER(wintypes.RECT), | |
| wintypes.LPARAM | |
| ) | |
| # Win32 API 函数 | |
| user32 = ctypes.windll.user32 | |
| EnumDisplayMonitors = user32.EnumDisplayMonitors | |
| EnumDisplayMonitors.argtypes = [wintypes.HDC, ctypes.POINTER(wintypes.RECT), MONITORENUMPROC, wintypes.LPARAM] | |
| EnumDisplayMonitors.restype = wintypes.BOOL | |
| GetMonitorInfoW = user32.GetMonitorInfoW | |
| GetMonitorInfoW.argtypes = [wintypes.HMONITOR, ctypes.POINTER(MONITORINFOEX)] | |
| GetMonitorInfoW.restype = wintypes.BOOL | |
| def get_active_monitors(): | |
| """ | |
| 使用 EnumDisplayMonitors 获取当前活动的显示器列表。 | |
| 返回列表,每个元素包含: | |
| { | |
| "handle": 显示器句柄, | |
| "device_name": 设备名称 (如 \\\\.\\DISPLAY1), | |
| "is_primary": 是否是主显示器, | |
| "monitor_rect": (left, top, right, bottom) 显示器矩形, | |
| "work_rect": (left, top, right, bottom) 工作区域矩形 | |
| } | |
| """ | |
| monitors = [] | |
| def enum_callback(hMonitor, hdcMonitor, lprcMonitor, dwData): | |
| info = MONITORINFOEX() | |
| info.cbSize = ctypes.sizeof(MONITORINFOEX) | |
| if GetMonitorInfoW(hMonitor, ctypes.byref(info)): | |
| monitor_data = { | |
| "handle": hMonitor, | |
| "device_name": info.szDevice, | |
| "is_primary": bool(info.dwFlags & MONITORINFOF_PRIMARY), | |
| "monitor_rect": ( | |
| info.rcMonitor.left, | |
| info.rcMonitor.top, | |
| info.rcMonitor.right, | |
| info.rcMonitor.bottom | |
| ), | |
| "work_rect": ( | |
| info.rcWork.left, | |
| info.rcWork.top, | |
| info.rcWork.right, | |
| info.rcWork.bottom | |
| ) | |
| } | |
| monitors.append(monitor_data) | |
| return True # 继续枚举 | |
| callback = MONITORENUMPROC(enum_callback) | |
| EnumDisplayMonitors(None, None, callback, 0) | |
| return monitors | |
| def parse_edid_size(edid: bytes): | |
| """从 EDID 解析物理尺寸,返回 (width_mm, height_mm) 或 (None, None)""" | |
| if not isinstance(edid, (bytes, bytearray)) or len(edid) < 0x17: | |
| return None, None | |
| width_cm = edid[0x15] | |
| height_cm = edid[0x16] | |
| if width_cm == 0 or height_cm == 0: | |
| return None, None | |
| return width_cm * 10, height_cm * 10 # 转换成 mm | |
| def parse_edid_vendor_product(edid: bytes): | |
| """ | |
| 从 EDID 解析厂商 ID (3 字符) 和产品码 (16bit) | |
| 厂商 ID 在 byte 8-9,产品码在 byte 10-11 (小端) | |
| """ | |
| if not isinstance(edid, (bytes, bytearray)) or len(edid) < 12: | |
| return None, None | |
| manufacturer_id_raw = (edid[8] << 8) | edid[9] | |
| # 每 5 bit 一个字母,A=1 => 'A'(65) | |
| m1 = chr(((manufacturer_id_raw >> 10) & 0x1F) + 64) | |
| m2 = chr(((manufacturer_id_raw >> 5) & 0x1F) + 64) | |
| m3 = chr((manufacturer_id_raw & 0x1F) + 64) | |
| manufacturer_id = m1 + m2 + m3 | |
| product_code = edid[10] | (edid[11] << 8) | |
| return manufacturer_id, product_code | |
| def parse_edid_monitor_name(edid: bytes): | |
| """ | |
| 从 EDID 的 Detailed Timing Descriptors 里解析 Monitor Name (0xFC) | |
| 标准 EDID v1 的描述符从 offset 54 开始,共 4 块 * 18 字节。 | |
| """ | |
| if not isinstance(edid, (bytes, bytearray)) or len(edid) < 128: | |
| return None | |
| # 4 个描述符,每个 18 字节,从 offset 54 开始 | |
| for i in range(4): | |
| base = 54 + i * 18 | |
| block = edid[base:base + 18] | |
| if len(block) < 18: | |
| continue | |
| # 00 00 00 FC 00 => Descriptor type 为 Monitor Name | |
| if block[0:4] == b'\x00\x00\x00\xfc': | |
| # 第 5 字节开始是文本,最长 13 字节,以 0x0A 结尾 | |
| name_bytes = block[5:18] | |
| # 去掉换行和空字符 | |
| name_bytes = name_bytes.split(b'\x0a', 1)[0] | |
| try: | |
| name = name_bytes.decode('ascii', errors='ignore').strip() | |
| except UnicodeDecodeError: | |
| name = None | |
| return name or None | |
| return None | |
| def get_monitor_info_from_registry(): | |
| """ | |
| 枚举 Windows 注册表下的显示器,返回列表: | |
| { | |
| "reg_path": 实例路径, | |
| "friendly_name": 注册表 FriendlyName, | |
| "hardware_id": HardwareID (MultiSZ), | |
| "edid_vendor": EDID 厂商 ID (如 'DEL'), | |
| "edid_product_code": EDID 产品码 (int), | |
| "edid_monitor_name": EDID 中的显示器名字符串, | |
| "width_mm": 物理宽度 (mm), | |
| "height_mm": 物理高度 (mm) | |
| } | |
| """ | |
| monitors = [] | |
| base_path = r"SYSTEM\CurrentControlSet\Enum\DISPLAY" | |
| try: | |
| h_base = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, base_path) | |
| except FileNotFoundError: | |
| print("未找到 DISPLAY 注册表路径,确认是否在 Windows 上运行并有权限。") | |
| return monitors | |
| try: | |
| i = 0 | |
| while True: | |
| try: | |
| mfg_key_name = winreg.EnumKey(h_base, i) # 比如 'DEL40A0' | |
| except OSError: | |
| break | |
| i += 1 | |
| mfg_path = base_path + "\\" + mfg_key_name | |
| try: | |
| h_mfg = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, mfg_path) | |
| except FileNotFoundError: | |
| continue | |
| j = 0 | |
| while True: | |
| try: | |
| instance_key_name = winreg.EnumKey(h_mfg, j) | |
| except OSError: | |
| break | |
| j += 1 | |
| # 实例根键 (这里有 FriendlyName / HardwareID 等) | |
| instance_root_path = mfg_path + "\\" + instance_key_name | |
| try: | |
| h_inst_root = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, instance_root_path) | |
| except FileNotFoundError: | |
| continue | |
| # FriendlyName | |
| try: | |
| friendly_name, _ = winreg.QueryValueEx(h_inst_root, "FriendlyName") | |
| except FileNotFoundError: | |
| friendly_name = None | |
| # HardwareID 可能是 REG_MULTI_SZ | |
| try: | |
| hardware_id, _ = winreg.QueryValueEx(h_inst_root, "HardwareID") | |
| except FileNotFoundError: | |
| hardware_id = None | |
| winreg.CloseKey(h_inst_root) | |
| # Device Parameters 子键里存 EDID | |
| device_params_path = instance_root_path + r"\Device Parameters" | |
| try: | |
| h_params = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, device_params_path) | |
| except FileNotFoundError: | |
| continue | |
| try: | |
| edid_value, value_type = winreg.QueryValueEx(h_params, "EDID") | |
| except FileNotFoundError: | |
| winreg.CloseKey(h_params) | |
| continue | |
| winreg.CloseKey(h_params) | |
| width_mm, height_mm = parse_edid_size(edid_value) | |
| edid_vendor, edid_product_code = parse_edid_vendor_product(edid_value) | |
| edid_monitor_name = parse_edid_monitor_name(edid_value) | |
| monitors.append({ | |
| "reg_path": instance_root_path, | |
| "friendly_name": friendly_name, | |
| "hardware_id": hardware_id, | |
| "edid_vendor": edid_vendor, | |
| "edid_product_code": edid_product_code, | |
| "edid_monitor_name": edid_monitor_name, | |
| "width_mm": width_mm, | |
| "height_mm": height_mm, | |
| }) | |
| finally: | |
| winreg.CloseKey(h_base) | |
| return monitors | |
| def get_display_device_info(): | |
| """ | |
| 使用 EnumDisplayDevices 获取显示设备详细信息, | |
| 用于将活动显示器与注册表 EDID 数据匹配。 | |
| 返回字典,键为设备名称 (如 \\\\.\\DISPLAY1),值为设备信息。 | |
| """ | |
| DISPLAY_DEVICE_ACTIVE = 0x00000001 | |
| DISPLAY_DEVICE_ATTACHED_TO_DESKTOP = 0x00000001 | |
| class DISPLAY_DEVICE(ctypes.Structure): | |
| _fields_ = [ | |
| ("cb", wintypes.DWORD), | |
| ("DeviceName", wintypes.WCHAR * 32), | |
| ("DeviceString", wintypes.WCHAR * 128), | |
| ("StateFlags", wintypes.DWORD), | |
| ("DeviceID", wintypes.WCHAR * 128), | |
| ("DeviceKey", wintypes.WCHAR * 128), | |
| ] | |
| EnumDisplayDevicesW = user32.EnumDisplayDevicesW | |
| EnumDisplayDevicesW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, ctypes.POINTER(DISPLAY_DEVICE), wintypes.DWORD] | |
| EnumDisplayDevicesW.restype = wintypes.BOOL | |
| devices = {} | |
| adapter_index = 0 | |
| while True: | |
| adapter = DISPLAY_DEVICE() | |
| adapter.cb = ctypes.sizeof(DISPLAY_DEVICE) | |
| if not EnumDisplayDevicesW(None, adapter_index, ctypes.byref(adapter), 0): | |
| break | |
| adapter_index += 1 | |
| # 检查适配器是否活动 | |
| if not (adapter.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP): | |
| continue | |
| # 枚举该适配器下的监视器 | |
| monitor_index = 0 | |
| while True: | |
| monitor = DISPLAY_DEVICE() | |
| monitor.cb = ctypes.sizeof(DISPLAY_DEVICE) | |
| if not EnumDisplayDevicesW(adapter.DeviceName, monitor_index, ctypes.byref(monitor), 0): | |
| break | |
| monitor_index += 1 | |
| # DeviceID 格式通常为: MONITOR\DEL40A0\{4d36e96e-e325-11ce-bfc1-08002be10318}\0000 | |
| # 我们需要提取中间的硬件 ID 部分 | |
| device_id = monitor.DeviceID | |
| devices[adapter.DeviceName] = { | |
| "adapter_name": adapter.DeviceName, | |
| "adapter_string": adapter.DeviceString, | |
| "monitor_name": monitor.DeviceName, | |
| "monitor_string": monitor.DeviceString, | |
| "device_id": device_id, | |
| "is_active": bool(adapter.StateFlags & DISPLAY_DEVICE_ACTIVE), | |
| } | |
| return devices | |
| def match_monitors_with_edid(active_monitors, registry_monitors, display_devices): | |
| """ | |
| 将活动显示器与注册表 EDID 数据进行匹配。 | |
| 返回活动显示器的完整信息列表。 | |
| """ | |
| matched_monitors = [] | |
| for active in active_monitors: | |
| device_name = active["device_name"] | |
| device_info = display_devices.get(device_name, {}) | |
| device_id = device_info.get("device_id", "") | |
| # 从 device_id 中提取硬件标识符用于匹配 | |
| # 格式: MONITOR\DEL40A0\{guid}\xxxx | |
| hardware_id_part = None | |
| if device_id: | |
| parts = device_id.split("\\") | |
| if len(parts) >= 2: | |
| hardware_id_part = parts[1] # 如 "DEL40A0" | |
| # 尝试匹配注册表中的 EDID 数据 | |
| matched_edid = None | |
| for reg_mon in registry_monitors: | |
| reg_path = reg_mon.get("reg_path", "") | |
| # 注册表路径格式: SYSTEM\CurrentControlSet\Enum\DISPLAY\DEL40A0\xxx | |
| if hardware_id_part and hardware_id_part in reg_path: | |
| matched_edid = reg_mon | |
| break | |
| monitor_info = { | |
| "device_name": device_name, | |
| "is_primary": active["is_primary"], | |
| "monitor_rect": active["monitor_rect"], | |
| "work_rect": active["work_rect"], | |
| "adapter_string": device_info.get("adapter_string", "Unknown"), | |
| "monitor_string": device_info.get("monitor_string", "Unknown"), | |
| "device_id": device_id, | |
| "is_active": True, # 来自 EnumDisplayMonitors,必定是活动的 | |
| } | |
| # 如果匹配到 EDID 数据,添加物理尺寸等信息 | |
| if matched_edid: | |
| monitor_info.update({ | |
| "reg_path": matched_edid.get("reg_path"), | |
| "friendly_name": matched_edid.get("friendly_name"), | |
| "hardware_id": matched_edid.get("hardware_id"), | |
| "edid_vendor": matched_edid.get("edid_vendor"), | |
| "edid_product_code": matched_edid.get("edid_product_code"), | |
| "edid_monitor_name": matched_edid.get("edid_monitor_name"), | |
| "width_mm": matched_edid.get("width_mm"), | |
| "height_mm": matched_edid.get("height_mm"), | |
| }) | |
| matched_monitors.append(monitor_info) | |
| return matched_monitors | |
| def get_all_monitors_with_status(): | |
| """ | |
| 获取所有显示器信息,标记活动和非活动状态。 | |
| 返回 (active_monitors, inactive_monitors) 两个列表。 | |
| """ | |
| # 获取活动显示器 | |
| active_monitors = get_active_monitors() | |
| # 获取显示设备信息(用于匹配) | |
| display_devices = get_display_device_info() | |
| # 获取注册表中的所有显示器 EDID 数据 | |
| registry_monitors = get_monitor_info_from_registry() | |
| # 匹配活动显示器与 EDID 数据 | |
| matched_active = match_monitors_with_edid(active_monitors, registry_monitors, display_devices) | |
| # 找出非活动的显示器(在注册表中但不在活动列表中) | |
| active_hardware_ids = set() | |
| for m in matched_active: | |
| device_id = m.get("device_id", "") | |
| if device_id: | |
| parts = device_id.split("\\") | |
| if len(parts) >= 2: | |
| active_hardware_ids.add(parts[1]) | |
| inactive_monitors = [] | |
| for reg_mon in registry_monitors: | |
| reg_path = reg_mon.get("reg_path", "") | |
| # 检查是否已经在活动列表中 | |
| is_active = any(hw_id in reg_path for hw_id in active_hardware_ids) | |
| if not is_active: | |
| reg_mon["is_active"] = False | |
| inactive_monitors.append(reg_mon) | |
| return matched_active, inactive_monitors | |
| if __name__ == "__main__": | |
| print("=" * 60) | |
| print("显示器信息查询工具") | |
| print("使用 EnumDisplayMonitors/GetMonitorInfo Win32 API") | |
| print("=" * 60) | |
| print() | |
| active_mons, inactive_mons = get_all_monitors_with_status() | |
| # 输出活动显示器 | |
| print(f"【当前活动显示器】共 {len(active_mons)} 个") | |
| print("-" * 60) | |
| if not active_mons: | |
| print("没有找到活动的显示器。") | |
| else: | |
| for idx, m in enumerate(active_mons, 1): | |
| primary_tag = " [主显示器]" if m.get("is_primary") else "" | |
| print(f"\n==== 活动显示器 #{idx}{primary_tag} ====") | |
| print(f"设备名称: {m['device_name']}") | |
| print(f"显卡: {m.get('adapter_string', 'N/A')}") | |
| print(f"显示器型号: {m.get('monitor_string', 'N/A')}") | |
| rect = m.get("monitor_rect") | |
| if rect: | |
| width = rect[2] - rect[0] | |
| height = rect[3] - rect[1] | |
| print(f"分辨率: {width} x {height}") | |
| print(f"位置: ({rect[0]}, {rect[1]}) - ({rect[2]}, {rect[3]})") | |
| work_rect = m.get("work_rect") | |
| if work_rect: | |
| print(f"工作区域: ({work_rect[0]}, {work_rect[1]}) - ({work_rect[2]}, {work_rect[3]})") | |
| if m.get("edid_vendor"): | |
| print(f"EDID 厂商ID: {m['edid_vendor']} 产品码: {m.get('edid_product_code')}") | |
| if m.get("edid_monitor_name"): | |
| print(f"EDID 显示器名称: {m['edid_monitor_name']}") | |
| if m.get("friendly_name"): | |
| print(f"系统名称: {m['friendly_name']}") | |
| if m.get("width_mm") and m.get("height_mm"): | |
| width_mm = m["width_mm"] | |
| height_mm = m["height_mm"] | |
| # 计算对角线英寸 | |
| import math | |
| diagonal_mm = math.sqrt(width_mm**2 + height_mm**2) | |
| diagonal_inch = diagonal_mm / 25.4 | |
| print(f"物理尺寸: {width_mm} mm x {height_mm} mm (约 {diagonal_inch:.1f} 英寸)") | |
| else: | |
| print("物理尺寸: (EDID 未提供)") | |
| # 输出非活动显示器 | |
| print() | |
| print(f"\n【非活动显示器(历史记录)】共 {len(inactive_mons)} 个") | |
| print("-" * 60) | |
| if not inactive_mons: | |
| print("没有找到非活动的显示器记录。") | |
| else: | |
| for idx, m in enumerate(inactive_mons, 1): | |
| print(f"\n---- 非活动显示器 #{idx} ----") | |
| if m.get("friendly_name"): | |
| print(f"系统名称: {m['friendly_name']}") | |
| if m.get("edid_monitor_name"): | |
| print(f"EDID 显示器名称: {m['edid_monitor_name']}") | |
| if m.get("edid_vendor"): | |
| print(f"EDID 厂商ID: {m['edid_vendor']} 产品码: {m.get('edid_product_code')}") | |
| if m.get("width_mm") and m.get("height_mm"): | |
| width_mm = m["width_mm"] | |
| height_mm = m["height_mm"] | |
| import math | |
| diagonal_mm = math.sqrt(width_mm**2 + height_mm**2) | |
| diagonal_inch = diagonal_mm / 25.4 | |
| print(f"物理尺寸: {width_mm} mm x {height_mm} mm (约 {diagonal_inch:.1f} 英寸)") | |
| print(f"注册表路径: {m.get('reg_path', 'N/A')}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment