- Enumerates all top-level visible windows using
EnumWindows, then filters to windows that are visible and have a non-empty title (IsWindowVisible,GetWindowText*). - Enriches each window with process and metadata by collecting the window class (
GetClassNameW), PID (GetWindowThreadProcessId), and executable path (OpenProcess+QueryFullProcessImageNameW). - Extracts and interprets window style flags via
GetWindowLongPtrW/GetWindowLongWto report key attributes such asDISABLED,TOPMOST,TOOLWIN, andAPPWINalongside rawSTYLE/EXSTYLEhex values.
Created
December 17, 2025 04:21
-
-
Save aont/13b61366ca97291572be13cf0f45edd1 to your computer and use it in GitHub Desktop.
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 ctypes | |
| from ctypes import wintypes | |
| import sys | |
| try: | |
| sys.stdout.reconfigure(encoding="utf-8", errors="replace") | |
| except Exception: | |
| pass | |
| user32 = ctypes.WinDLL("user32", use_last_error=True) | |
| kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) | |
| EnumWindowsProc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM) | |
| PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 | |
| # --- GWL / style constants --- | |
| GWL_STYLE = -16 | |
| GWL_EXSTYLE = -20 | |
| WS_DISABLED = 0x08000000 | |
| WS_EX_TOPMOST = 0x00000008 | |
| WS_EX_TOOLWINDOW = 0x00000080 | |
| WS_EX_APPWINDOW = 0x00040000 | |
| # --- prototypes --- | |
| user32.EnumWindows.argtypes = [EnumWindowsProc, wintypes.LPARAM] | |
| user32.EnumWindows.restype = wintypes.BOOL | |
| user32.IsWindowVisible.argtypes = [wintypes.HWND] | |
| user32.IsWindowVisible.restype = wintypes.BOOL | |
| user32.GetWindowTextLengthW.argtypes = [wintypes.HWND] | |
| user32.GetWindowTextLengthW.restype = ctypes.c_int | |
| user32.GetWindowTextW.argtypes = [wintypes.HWND, wintypes.LPWSTR, ctypes.c_int] | |
| user32.GetWindowTextW.restype = ctypes.c_int | |
| user32.GetClassNameW.argtypes = [wintypes.HWND, wintypes.LPWSTR, ctypes.c_int] | |
| user32.GetClassNameW.restype = ctypes.c_int | |
| user32.GetWindowThreadProcessId.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.DWORD)] | |
| user32.GetWindowThreadProcessId.restype = wintypes.DWORD | |
| kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] | |
| kernel32.OpenProcess.restype = wintypes.HANDLE | |
| kernel32.CloseHandle.argtypes = [wintypes.HANDLE] | |
| kernel32.CloseHandle.restype = wintypes.BOOL | |
| kernel32.QueryFullProcessImageNameW.argtypes = [ | |
| wintypes.HANDLE, wintypes.DWORD, wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD) | |
| ] | |
| kernel32.QueryFullProcessImageNameW.restype = wintypes.BOOL | |
| # --- GetWindowLongPtrW (32/64対応) --- | |
| if ctypes.sizeof(ctypes.c_void_p) == 8: | |
| user32.GetWindowLongPtrW.argtypes = [wintypes.HWND, ctypes.c_int] | |
| user32.GetWindowLongPtrW.restype = ctypes.c_longlong | |
| def get_window_long_ptr(hwnd, index) -> int: | |
| return int(user32.GetWindowLongPtrW(hwnd, index)) | |
| else: | |
| user32.GetWindowLongW.argtypes = [wintypes.HWND, ctypes.c_int] | |
| user32.GetWindowLongW.restype = ctypes.c_long | |
| def get_window_long_ptr(hwnd, index) -> int: | |
| return int(user32.GetWindowLongW(hwnd, index)) | |
| def get_window_title(hwnd: int) -> str: | |
| length = user32.GetWindowTextLengthW(hwnd) | |
| if length <= 0: | |
| return "" | |
| buf = ctypes.create_unicode_buffer(length + 1) | |
| user32.GetWindowTextW(hwnd, buf, length + 1) | |
| return buf.value | |
| def get_window_class(hwnd: int) -> str: | |
| buf = ctypes.create_unicode_buffer(256) | |
| n = user32.GetClassNameW(hwnd, buf, len(buf)) | |
| return buf.value if n > 0 else "" | |
| def get_pid(hwnd: int) -> int: | |
| pid = wintypes.DWORD(0) | |
| user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) | |
| return int(pid.value) | |
| def get_process_image_path(pid: int) -> str: | |
| hproc = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid) | |
| if not hproc: | |
| return "" | |
| try: | |
| size = wintypes.DWORD(4096) | |
| buf = ctypes.create_unicode_buffer(size.value) | |
| if kernel32.QueryFullProcessImageNameW(hproc, 0, buf, ctypes.byref(size)): | |
| return buf.value | |
| return "" | |
| finally: | |
| kernel32.CloseHandle(hproc) | |
| def get_styles(hwnd: int) -> tuple[int, int]: | |
| style = get_window_long_ptr(hwnd, GWL_STYLE) | |
| exstyle = get_window_long_ptr(hwnd, GWL_EXSTYLE) | |
| return style, exstyle | |
| def has_flag(value: int, flag: int) -> bool: | |
| return (value & flag) == flag | |
| def enum_windows(): | |
| results = [] | |
| @EnumWindowsProc | |
| def callback(hwnd, lparam): | |
| if not user32.IsWindowVisible(hwnd): | |
| return True | |
| title = get_window_title(hwnd) | |
| if not title: | |
| return True | |
| cls = get_window_class(hwnd) | |
| pid = get_pid(hwnd) | |
| image = get_process_image_path(pid) | |
| style, exstyle = get_styles(hwnd) | |
| results.append((hwnd, pid, cls, title, image, style, exstyle)) | |
| return True | |
| if not user32.EnumWindows(callback, 0): | |
| raise ctypes.WinError(ctypes.get_last_error()) | |
| return results | |
| if __name__ == "__main__": | |
| for hwnd, pid, cls, title, image, style, exstyle in enum_windows(): | |
| disabled = has_flag(style, WS_DISABLED) | |
| topmost = has_flag(exstyle, WS_EX_TOPMOST) | |
| toolwin = has_flag(exstyle, WS_EX_TOOLWINDOW) | |
| appwin = has_flag(exstyle, WS_EX_APPWINDOW) | |
| print( | |
| f"HWND=0x{hwnd:08X} PID={pid:<6} " | |
| f"DISABLED={int(disabled)} TOPMOST={int(topmost)} TOOLWIN={int(toolwin)} APPWIN={int(appwin)} " | |
| f"STYLE=0x{style:08X} EXSTYLE=0x{exstyle:08X} " | |
| f"CLASS={cls} TITLE={title} EXE={image}" | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment