V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
Leon6868
V2EX  ›  Python

pywin32 win32gui.EnumWindows() 如何去除无意义的窗口句柄

  •  
  •   Leon6868 · 2020-04-23 01:18:16 +08:00 · 4162 次点击
    这是一个创建于 1683 天前的主题,其中的信息可能已经有所发展或是发生改变。

    现在的代码

    hideClass = ["IME","Syn Zoom Class","MyWing","MSCTFIME UI"]
    
    def reset_window_pos(targetTitle):  
        hWndList = []  
        win32gui.EnumWindows(lambda hWnd, param: param.append(hWnd), hWndList)  
        count = 0
        for hwnd in hWndList:
            if win32gui.GetWindowText(hwnd) != "":
                if win32gui.GetClassName(hwnd) not in hideClass:
                    print("count:",count)
                    print(hwnd)
                    print(win32gui.GetWindowText(hwnd))
                    print(win32gui.GetClassName(hwnd))
                    print("-"*10)
                    count += 1
    

    这个代码运行后,还剩下 80 个句柄(我只打开了 5 个窗口)
    只能通过枚举类名的方式排除句柄吗?还是有其他的 api 可以做到?

    第 1 条附言  ·  2020-04-23 07:38:52 +08:00
    “无意义”是指用户不能操作的窗体,比如说 IME 、Syn Zoom Class 、MSCTFIME UI 等
    第 2 条附言  ·  2020-04-23 17:34:40 +08:00

    感谢@geelaw 解决方法是:检测窗体是否隐形

    因为UWP 具有一个称为“ 隐身”的概念,在该概念中,窗口被赋予了所有可见性陷阱,而实际上并未呈现给用户。就该应用程序而言,它似乎是可见的:它仍然具有WS_VISIBLE窗口样式,其坐标仍在监视器的范围内,它仍然获取消息,它具有一个非空的裁剪区域,所以用WS_VISIBLE和GetWindowRect检测会失效。
    具体原因可以查看这里

    直接调用dwmapi.dllDwmGetWindowAttribute方法就可以检测是否隐形了。

    Python原生实现:

    import ctypes
    import ctypes.wintypes
    
    from pprint import pprint
    
    def get_current_size(hWnd):
        try:
            f = ctypes.windll.dwmapi.DwmGetWindowAttribute
        except WindowsError:
            f = None
        if f:
            DWMWA_CLOAKED = 14
            cloaked = ctypes.wintypes.DWORD()
            f(hWnd, #在这里检测
              ctypes.wintypes.DWORD(DWMWA_CLOAKED),
              ctypes.byref(cloaked),
              ctypes.sizeof(cloaked)
              )  
            return cloaked.value
    
    def getTitleList():
        EnumWindows = ctypes.windll.user32.EnumWindows
        EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
        GetWindowText = ctypes.windll.user32.GetWindowTextW
        GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
        IsWindowVisible = ctypes.windll.user32.IsWindowVisible
         
        titles = []
        dwmapi = ctypes.windll.dwmapi
        def foreach_window(hwnd, lParam):
            if IsWindowVisible(hwnd) and GetWindowTextLength(hwnd) != 0 and not get_current_size(hwnd):
                length = GetWindowTextLength(hwnd)
                buff = ctypes.create_unicode_buffer(length + 1)
                GetWindowText(hwnd, buff, length + 1)
                titles.append(buff.value)
            return True
    
        EnumWindows(EnumWindowsProc(foreach_window), 0) #这里其实可以用匿名函数
    
        return titles
     
    pprint(getTitleList())
    
    10 条回复    2020-04-24 17:49:29 +08:00
    geelaw
        1
    geelaw  
       2020-04-23 01:26:44 +08:00 via iPhone   ❤️ 2
    定义“无意义”,如果当前没有显示的窗口是无意义,你可以通过 IsWindowVisible 和 DwmGetWindowAttribute 判断。
    mingl0280
        2
    mingl0280  
       2020-04-23 04:33:24 +08:00 via Android
    正常过滤窗口 handle 都是白名单形式的,因为你根本不知道其它窗口会不会跟你的有同样的 class 和 title
    opengps
        3
    opengps  
       2020-04-23 07:40:33 +08:00 via Android
    我当年写的 socket 服务端,句柄泄露到上百万个都还能正常运行,你才 80 个担心啥
    geelaw
        4
    geelaw  
       2020-04-23 09:45:34 +08:00
    @opengps #3 这个问题和句柄泄露无关,USER 句柄是不计数的,泄露方式只能是建立的窗口之后不使用。而且一个会话里的 USER 句柄数量最多是 65536 。

    “用户不能操作”仍然不是一个有意义的定义,而且用户当然可以操作 IME 窗口,不然候选词列表怎么用?如果你觉得任务栏是否显示这个窗口的按钮可以作为判据,那么你可以模拟任务栏的选择,这是有文档记录的:

    https://docs.microsoft.com/en-us/windows/win32/shell/taskbar#managing-taskbar-buttons
    Leon6868
        5
    Leon6868  
    OP
       2020-04-23 12:38:22 +08:00
    @geelaw
    谢谢,我的“无意义”指的是“桌面上可以被用户操作的窗口”,前面表述的很不清楚,感谢指正:D
    我现在用 IsWindowVisible 判断,可是在 win10 还是有一些问题
    这是我解析出来的窗口

    ['QQ',
    '腾讯会议',
    'D:\\Temp\\win32.py - Sublime Text (UNREGISTERED)',
    '腾讯会议',
    '群等 5 个会话',
    '电影和电视',
    '电影和电视',
    '设置',
    '设置',
    '邮件',
    '收件箱 - Outlook \u200e- 邮件',
    'Microsoft Store',
    'Microsoft Store',
    'Title',
    '便笺',
    'Microsoft Text Input Application',
    '便笺',
    'Program Manager']

    但是我并没有打开“电影和电视”以及“设置”、“'Microsoft Store”和“收件箱 - Outlook \u200e- 邮件”,你知道为什么吗?
    mingl0280
        6
    mingl0280  
       2020-04-23 15:00:34 +08:00
    @Leon6868 很简单,你看起来桌面上只有那么多窗口,但是实际上 windows 里面的窗口不止这么多.
    你要是想遍历所有打开在桌面上的顶层窗口的话,
    python 的库我没用过,不过 C 的话需要做这个过滤:
    使用 GetWindowTextA 获取窗口标题,如果为空跳过.
    使用 uint dwStyle = GetWindowLong(hWnd, GWL_STYLE (-16))获取窗口的样式表,然后判断 dwStyle & WS_VISIBLE (0x10000000) == 1 为可见窗口.

    C/C++的代码基本上长这样
    BOOL CALLBACK EnumProc(HWND hWnd, LPARAM lParam)
    {
    LPSTR TitleStr = new char[4096];
    LPSTR ClassStr = new char[4096];
    GetClassNameA(hWnd, ClassStr, 4096);
    GetWindowTextA(hWnd, TitleStr, 4096);
    string Title(TitleStr);
    string ClassName(ClassStr);
    DWORD dwStyle = GetWindowLong(hWnd, GWL_STYLE);
    if (dwStyle & WS_VISIBLE && !Title.empty())
    {
    // 你要的窗口
    }
    return true;
    }
    geelaw
        7
    geelaw  
       2020-04-23 16:28:00 +08:00   ❤️ 1
    @Leon6868 #5 因为 Windows 10 会预热启动 UWP 来提升体验。我说过了你需要用 DwmGetWindowAttribute,这里你需要判断窗口是否被掩盖( cloaked ),一个例子代码见 https://devblogs.microsoft.com/oldnewthing/20200302-00/?p=103507

    此外我也提示过你可能想要模拟任务栏或者 Alt+Tab 对话框选择窗口的方式。
    Leon6868
        8
    Leon6868  
    OP
       2020-04-23 16:41:35 +08:00
    @mingl0280 还是获取不到 /(ㄒoㄒ)/~~
    现在的代码:

    import win32api
    import win32con
    import win32gui
    from pprint import pprint

    def reset_window_pos():
    hWndList = []
    win32gui.EnumWindows(lambda hWnd, param: param.append(hWnd), hWndList)
    count = 0
    for hWnd in hWndList:
    if win32gui.GetWindowText(hWnd) and win32gui.IsWindowVisible(hWnd):
    dwStyle = win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE)
    if dwStyle & win32con.WS_VISIBLE:
    print(count,"-"*10)
    print("GetWindowText:",win32gui.GetWindowText(hWnd))
    print("win32con.WS_VISIBLE:",win32con.WS_VISIBLE)
    print("GWL_STYLE:",win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE))
    print("GetWindowRect:",win32gui.GetWindowRect(hWnd))
    count += 1

    部分运行结果:
    0 ----------
    GetWindowText: QQ
    win32con.WS_VISIBLE: 268435456
    GWL_STYLE: -1777598464
    GetWindowRect: (35, -535, 325, 5)
    1 ----------
    GetWindowText: F:\ME\beable\LockerTODO\try\win32try\byPywin32.py - Sublime Text (UNREGISTERED)
    win32con.WS_VISIBLE: 268435456
    GWL_STYLE: 365887488
    GetWindowRect: (-7, -7, 1288, 728)
    ………………………………………………………………………………………………
    5 ----------
    GetWindowText: 设置
    win32con.WS_VISIBLE: 268435456
    GWL_STYLE: -1811939328
    GetWindowRect: (0, 1, 719, 486)
    6 ----------
    GetWindowText: 设置
    win32con.WS_VISIBLE: 268435456
    GWL_STYLE: -1798373376
    GetWindowRect: (481, 80, 1214, 573)
    7 ----------
    GetWindowText: 邮件
    win32con.WS_VISIBLE: 268435456
    GWL_STYLE: -1811939328
    GetWindowRect: (0, 1, 719, 656)
    8 ----------
    GetWindowText: 收件箱 - Outlook ‎- 邮件
    win32con.WS_VISIBLE: 268435456
    GWL_STYLE: -1798373376
    GetWindowRect: (0, 57, 733, 720)
    9 ----------
    GetWindowText: 电影和电视
    win32con.WS_VISIBLE: 268435456
    GWL_STYLE: -1811939328
    GetWindowRect: (0, 1, 711, 426)
    10 ----------
    GetWindowText: 电影和电视
    win32con.WS_VISIBLE: 268435456
    GWL_STYLE: -1798373376
    GetWindowRect: (108, 201, 834, 634)
    Leon6868
        9
    Leon6868  
    OP
       2020-04-23 17:20:37 +08:00
    @geelaw 谢谢!用 DwmGetWindowAttribute 搞定了
    果然还是原生的 ctype 靠谱
    geelaw
        10
    geelaw  
       2020-04-24 17:49:29 +08:00
    Cloaking 的概念和 UWP 没关系,如果你用虚拟桌面的话,不在当前桌面显示的窗口都会被 cloaked 。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2596 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 10:48 · PVG 18:48 · LAX 02:48 · JFK 05:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.