显示桌面
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())