How to get Windows window names with ctypes in python

后端 未结 1 1409
失恋的感觉
失恋的感觉 2021-01-01 04:28

I try to get Windows window title names and pids through handles with long objects. My code works but there is something wrong with it. I get only 4 window titles when I sho

相关标签:
1条回答
  • 2021-01-01 04:58

    You're passing a process ID to functions that take a window handle. What you want to do is enumerate handles for the top-level windows and then map each window to a process ID.

    First lets define the ctypes function prototypes to get proper type checking on function arguments. Also, use use_last_error=True to get the safest error handling via ctypes.get_last_error. A lot of Windows functions return 0 for an error, so it's convenient to have a single errcheck function for this case, such as check_zero.

    from __future__ import print_function
    
    import ctypes
    from ctypes import wintypes
    from collections import namedtuple
    
    user32 = ctypes.WinDLL('user32', use_last_error=True)
    
    def check_zero(result, func, args):    
        if not result:
            err = ctypes.get_last_error()
            if err:
                raise ctypes.WinError(err)
        return args
    
    if not hasattr(wintypes, 'LPDWORD'): # PY2
        wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
    
    WindowInfo = namedtuple('WindowInfo', 'pid title')
    
    WNDENUMPROC = ctypes.WINFUNCTYPE(
        wintypes.BOOL,
        wintypes.HWND,    # _In_ hWnd
        wintypes.LPARAM,) # _In_ lParam
    
    user32.EnumWindows.errcheck = check_zero
    user32.EnumWindows.argtypes = (
       WNDENUMPROC,      # _In_ lpEnumFunc
       wintypes.LPARAM,) # _In_ lParam
    
    user32.IsWindowVisible.argtypes = (
        wintypes.HWND,) # _In_ hWnd
    
    user32.GetWindowThreadProcessId.restype = wintypes.DWORD
    user32.GetWindowThreadProcessId.argtypes = (
      wintypes.HWND,     # _In_      hWnd
      wintypes.LPDWORD,) # _Out_opt_ lpdwProcessId
    
    user32.GetWindowTextLengthW.errcheck = check_zero
    user32.GetWindowTextLengthW.argtypes = (
       wintypes.HWND,) # _In_ hWnd
    
    user32.GetWindowTextW.errcheck = check_zero
    user32.GetWindowTextW.argtypes = (
        wintypes.HWND,   # _In_  hWnd
        wintypes.LPWSTR, # _Out_ lpString
        ctypes.c_int,)   # _In_  nMaxCount
    

    Here's a function to list the visible windows. It uses a callback that's a closure over result instead of using the optional lParam argument. The latter would require casting the argument. Using a closure is simpler.

    def list_windows():
        '''Return a sorted list of visible windows.'''
        result = []
        @WNDENUMPROC
        def enum_proc(hWnd, lParam):
            if user32.IsWindowVisible(hWnd):
                pid = wintypes.DWORD()
                tid = user32.GetWindowThreadProcessId(
                            hWnd, ctypes.byref(pid))
                length = user32.GetWindowTextLengthW(hWnd) + 1
                title = ctypes.create_unicode_buffer(length)
                user32.GetWindowTextW(hWnd, title, length)
                result.append(WindowInfo(pid.value, title.value))
            return True
        user32.EnumWindows(enum_proc, 0)
        return sorted(result)
    

    For completeness, here's a function to list all process IDs. This includes processes that belong to other Windows sessions (e.g. services in session 0).

    psapi = ctypes.WinDLL('psapi', use_last_error=True)
    
    psapi.EnumProcesses.errcheck = check_zero
    psapi.EnumProcesses.argtypes = (
       wintypes.LPDWORD,  # _Out_ pProcessIds
       wintypes.DWORD,    # _In_  cb
       wintypes.LPDWORD,) # _Out_ pBytesReturned
    
    def list_pids():
        '''Return sorted list of process IDs.'''
        length = 4096
        PID_SIZE = ctypes.sizeof(wintypes.DWORD)
        while True:
            pids = (wintypes.DWORD * length)()
            cb = ctypes.sizeof(pids)
            cbret = wintypes.DWORD()
            psapi.EnumProcesses(pids, cb, ctypes.byref(cbret))
            if cbret.value < cb:
                length = cbret.value // PID_SIZE
                return sorted(pids[:length])
            length *= 2
    

    For example:

    if __name__ == '__main__':
        print('Process IDs:')
        print(*list_pids(), sep='\n')
        print('\nWindows:')
        print(*list_windows(), sep='\n')
    

    MSDN links:

    • EnumWindows
    • IsWindowVisible
    • GetWindowThreadProcessId
    • GetWindowTextLength
    • GetWindowText
    • EnumProcesses
    0 讨论(0)
提交回复
热议问题