2023年7月 Python获取Windows和Linux的窗口句柄和属性

显示桌面

Windows显示WSL的桌面可以用MobaXterm,如果WSL是Kali的话也可以用Win-KeX。

获取句柄

Windows中获取窗口句柄可以使用Python的win32gui,使用pip install pywin32安装

def FindWindow(__ClassName: _win32typing.PyResourceId | str | None, __WindowName: str | None) -> int: ...
def FindWindowEx(
    __Parent: int | None, __ChildAfter: int | None, __ClassName: _win32typing.PyResourceId | str | None, __WindowName: str | None
) -> int: ...
import win32gui
win32gui.FindWindow
win32gui.FindWindowEx

Linux中获取窗口句柄可以使用Xlib,Xlib是C的库,Python也有对应的封装,
https://python-xlib.sourceforge.net/

pip install python-xlib

Linux中也可以直接使用xwininfo和xprop获取窗口的句柄和属性。

这里提供Windows和Linux中通用的获取方法,支持Windows和Linux的句柄获取与属性获取,可以通过进程ID、窗口类名、标题获取窗口句柄,同时获得窗口属性。

import os
import platform
import sys
import re
import time
import warnings
import subprocess as sp
from threading import Thread
from typing import Callable
if platform.system() == "Windows":
    import win32gui, win32process, win32api, win32con
elif platform.system() != "Linux":
    warnings.warn("暂不支持的系统类型,暂时当作Linux处理")
else:
    import Xlib
    import Xlib.display
    display_name = None or "127.0.0.1:0.0" # None代表当前默认的,一般的格式是类似"127.0.0.1:0.0"的字符串
    dis = Xlib.display.Display(display_name)
    NET_WM_PID = dis.get_atom("_NET_WM_PID")
    NET_WM_NAME = dis.get_atom("_NET_WM_NAME")
    WM_NAME = Xlib.Xatom.WM_NAME
    NET_WM_CLASS = dis.get_atom("_NET_WM_CLASS")
    WM_CLASS = Xlib.Xatom.WM_CLASS
    STRING = Xlib.Xatom.STRING
    UTF8STRIING = dis.get_atom("UTF8STRING")
    CARDINAL = Xlib.Xatom.CARDINAL


class _Window:
    def __init__(self, window_id):
        self.window_id = window_id
    
    def get_process_id(self) -> int|None:
        pass

    def get_class_name(self) -> str|None:
        pass

    def get_title(self) -> str|None:
        pass

class _WindowMatches:
    """ 窗口类名和标题支持正则表达式,使用了re.match匹配,所以一定从开头开始匹配,不一定匹配到结尾 """
    def __init__(self, window_id=None, process_id=None, class_name=None, title=None) -> None:
        self.matches = []
        self.filters = []
        if window_id is not None:
            self.filters.append(self.filter_window_id(window_id))
        if process_id is not None:
            self.filters.append(self.filter_process_id(process_id))
        if class_name is not None:
            self.filters.append(self.filter_class_name(class_name))
        if title is not None:
            self.filters.append(self.filter_title(title))
        self.matches = self.get_all_windows(*self.filters)
    
    def get_all_windows(self, *filters) -> list[_Window]:
        return self.matches

    def filter_window_id(self, window_id:int) -> Callable[[_Window], bool]:
        def validate(window:_Window):
            return window.window_id == window_id
        return validate
    
    def filter_process_id(self, process_id:int) -> Callable[[_Window], bool]:
        def validate(window:_Window):
            return window.get_process_id() == process_id
        return validate
    
    def filter_class_name(self, class_name:str) -> Callable[[_Window], bool]:
        class_name_pattern = re.compile(class_name)
        def validate(window:_Window):
            return bool(found_class_name := window.get_class_name()) and bool(class_name_pattern.match(found_class_name))
        return validate
    
    def filter_title(self, title:str) -> Callable[[_Window], bool]:
        title_pattern = re.compile(title)
        def validate(window:_Window):
            return bool(found_title := window.get_title()) and bool(title_pattern.match(found_title))
        return validate


class Windows_Window(_Window):
    def get_process_id(self) -> int | None:
        return win32process.GetWindowThreadProcessId(self.window_id)[1] # 返回值是(thread_id, process_id)
    
    def get_class_name(self) -> str | None:
        return win32gui.GetClassName(self.window_id)
    
    def get_title(self) -> str | None:
        return win32gui.GetWindowText(self.window_id)

    def get_child(self) -> int:
        return win32gui.FindWindowEx(self.window_id, None, None, None)


class Windows_WindowMatches(_WindowMatches):
    def get_all_windows(self, *filters) -> list[_Window]:
        def callback(hwnd, _):
            if not (win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd)):
                return True # 继续枚举
            found_window = Windows_Window(hwnd)
            for func in filters:
                if not func(found_window):
                    return True # 继续枚举
            self.matches.append(found_window)
            return True
        win32gui.EnumWindows(callback, None)
        return super().get_all_windows(*filters)


def validate_return_str_property(property_):
    if property_:
        return property_.value.decode()
    return None

class Linux_Window(_Window):
    def __init__(self, _window):
        self._window = _window
        super().__init__(self._window.id)

    def get_process_id(self) -> int | None:
        property_ = self._window.get_full_property(NET_WM_PID, CARDINAL)
        if property_ and len(property_.value) > 0:
            return property_.value[0]
        return None
        # value: array('I', [0])
    
    def get_class_name(self) -> str | None:
        return validate_return_str_property(self._window.get_full_property(NET_WM_CLASS, UTF8STRIING)) or validate_return_str_property(self._window.get_full_property(WM_CLASS, STRING))
    
    def get_title(self) -> str | None:
        return validate_return_str_property(self._window.get_full_property(NET_WM_NAME, UTF8STRIING)) or validate_return_str_property(self._window.get_full_property(WM_NAME, STRING))
    
    def get_child(self):
        return self._window.query_tree()._data['children'][0].id


class Linux_WindowMatches(_WindowMatches):
    def get_all_windows(self, *filters) -> list[_Window]:
        def search(win:_Window):
            for func in filters:
                if not func(win):
                    break
            else:
                self.matches.append(win)
            for child in win._window.query_tree()._data['children']:
                search(Linux_Window(child))
        search(Linux_Window(dis.screen().root))
        return super().get_all_windows(*filters)


class WindowMatches:
    def __new__(cls, window_id=None, process_id=None, class_name=None, title=None) -> Windows_WindowMatches | Linux_WindowMatches:
        if platform.system() == "Windows":
            return Windows_WindowMatches(window_id, process_id, class_name, title)
        else: # 导入时警告过了,所以这里就不用elif了
            return Linux_WindowMatches(window_id, process_id, class_name, title)


if __name__ == '__main__':
	# 以下正则表达式使用re.match匹配,所以一定从开头开始匹配,不一定匹配到结尾,要想匹配到结尾可以手动加$
	class_name = r"\w+" # 表示只获取窗口类名匹配class_name正则表达式的
	title = r"\d+" # 表示只获取窗口标题匹配title正则表达式的
	process_id = None # None表示不限制进程ID
	for window in WindowMatches(process_id=process_id, class_name=class_name, title=title).matches:
		print(window.window_id, window.get_process_id(), window.get_class_name(), window.get_title())
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值