Skip to content

Instantly share code, notes, and snippets.

@Zayrick
Created December 26, 2025 03:36
Show Gist options
  • Select an option

  • Save Zayrick/09218b7fc19db984e8558fe4687c0c06 to your computer and use it in GitHub Desktop.

Select an option

Save Zayrick/09218b7fc19db984e8558fe4687c0c06 to your computer and use it in GitHub Desktop.
Windows 显示器信息查询工具,通过 Win32 API 和注册表 EDID 数据获取活动/非活动显示器的详细硬件参数(分辨率、物理尺寸、厂商信息等)
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