Spy++是微软出品的用来获取Window窗口信息的一个小工具。实现的原理其实不难,通过调用某些特定的Windows API即可。于是,我打算用Python也实现一个功能简化版本的小工具,取名叫PySpy++。Python中调用Windows API一般使用pywin32这套库,界面库我使用PyQT4。
Spy++原理
Spy++中,最常用的一个功能,就是识别窗口。其中主要需要用到的Windows API有:
获取当前鼠标位置:
BOOL GetCursorPos( LPPOINT lpPoint );
获取位于指定位置的窗口句柄:
HWND WindowFromPoint( POINT Point );
获取窗口类别:
int
GetClassName( HWND hWnd, LPTSTR lpClassName,
int
nMaxCount );
获取窗口内容或标题:
方法一:
int
GetWindowText( HWND hWnd, LPTSTR lpString,
int
nMaxCount );
这个API有时候不能取到某些控件的值,因此,使用方法二。
方法二:
给窗口发送WM_GETTEXT消息:
LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
高亮选中的窗口:
先获取当前窗口的大小,然后画一个矩形框。
BOOL GetWindowRect( HWND hWnd, LPRECT lpRect );
BOOL Rectangle(
HDC hdc,
//
handle to DC
int
nLeftRect,
//
x-coord of upper-left corner of rectangle
int
nTopRect,
//
y-coord of upper-left corner of rectangle
int
nRightRect,
//
x-coord of lower-right corner of rectangle
int
nBottomRect
//
y-coord of lower-right corner of rectangle
);
复制代码
鼠标移开窗口后,窗口需要恢复原状,需要重新刷新:
BOOL InvalidateRect(
HWND hWnd,
//
handle to window
CONST RECT
*
lpRect,
//
rectangle coordinates
BOOL bErase
//
erase state
);
BOOL UpdateWindow(
HWND hWnd
//
handle to window
);
BOOL RedrawWindow(
HWND hWnd,
//
handle to window
CONST RECT
*
lprcUpdate,
//
update rectangle
HRGN hrgnUpdate,
//
handle to update region
UINT flags
//
array of redraw flags
);
复制代码
PyWin32对应的函数
在Python中调用Windows API,首先下载PyWin32,地址:http://pywin32.sourceforge.net/
安装完成后,打开帮助文档Python for Windows Documentation,里面有所有需要的东西,随时用来查看。
常用的API在win32api模块里,界面相关的API在win32gui模块里,API参数中定义的一些常量在win32con模块中。上面的Windows API对应PyWin32中的函数为:
(int, int)
=
win32gui.
GetCursorPos
()
int
=
win32gui.
WindowFromPoint
(point)
string
=
win32gui.
GetClassName
(hwnd)
string
=
win32gui.
GetWindowText
(hwnd)
int
=
win32gui.
SendMessage
(hwnd, message , wparam , lparam )
(left, top, right, bottom)
=
win32gui.
GetWindowRect
(hwnd)
win32gui.
Rectangle
(hdc, LeftRect, TopRect, RightRect, BottomRect)
win32gui.
InvalidateRect
(hWnd, Rect, Erase)
win32gui.
UpdateWindow
(hwnd)
win32gui.
RedrawWindow
(hWnd, rcUpdate, hrgnUpdate, flags)
复制代码
代码实现
界面库使用PyQT4,参考资料可以从我之前的一篇博客里了解:PyQt4 学习资料汇总
工具对话框窗口有两个控件,一个是QLabel控件,一个是QTextEdit控件。QLabel控件就是那个用来鼠标按下去后去捕捉窗口,QTextEdit控件用来显示窗口的信息。为了让QTextEdit响应自定义的鼠标事件,我创建了一个自定义QLabel控件SpyLabel,继承自QLabel。
class
SpyLabel(QtGui.QLabel):
def
__init__
(self, parent
=
None):
QtGui.QLabel.
__init__
(self, parent)
self.parent
=
parent
self.spying
=
False
self.rectanglePen
=
win32gui.CreatePen(win32con.PS_SOLID,
3
, win32api.RGB(
255
, 0, 0))
self.prevWindow
=
None
self.setCursor(QtCore.Qt.SizeAllCursor)
复制代码
SpyLabel中处理鼠标移动事件:
def
mouseMoveEvent(self, event):
if
self.spying:
curX, curY
=
win32gui.GetCursorPos()
hwnd
=
win32gui.
WindowFromPoint
((curX, curY))
if
self.checkWindowValidity(hwnd):
if
self.prevWindow:
self.refreshWindow(self.prevWindow)
self.prevWindow
=
hwnd
self.highlightWindow(hwnd)
self.displayWindowInformation(hwnd)
复制代码
鼠标松开事件:
def
mouseReleaseEvent(self, event):
if
self.spying:
if
self.prevWindow:
self.refreshWindow(self.prevWindow)
win32gui.ReleaseCapture()
self.spying
=
False
复制代码
高亮窗口的函数:
def
highlightWindow(self, hwnd):
left, top, right, bottom
=
win32gui.GetWindowRect(hwnd)
windowDc
=
win32gui.GetWindowDC(hwnd)
if
windowDc:
prevPen
=
win32gui.SelectObject(windowDc, self.rectanglePen)
prevBrush
=
win32gui.SelectObject(windowDc, win32gui.GetStockObject(win32con.HOLLOW_BRUSH))
win32gui.
Rectangle
(windowDc, 0, 0, right
-
left, bottom
-
top)
win32gui.SelectObject(windowDc, prevPen)
win32gui.SelectObject(windowDc, prevBrush)
win32gui.ReleaseDC(hwnd, windowDc)
复制代码
刷新窗口的函数:
def
refreshWindow(self, hwnd):
win32gui.
InvalidateRect
(hwnd, None, True)
win32gui.
UpdateWindow
(hwnd)
win32gui.
RedrawWindow
(hwnd,
None,
None,
win32con.RDW_FRAME
|
win32con.RDW_INVALIDATE
|
win32con.RDW_UPDATENOW
|
win32con.RDW_ALLCHILDREN)
复制代码
显示窗口信息:
def
displayWindowInformation(self, hwnd):
className
=
win32gui.GetClassName(hwnd)
buf_size
=
1
+
win32gui.
SendMessage
(hwnd, win32con.WM_GETTEXTLENGTH, 0, 0)
buffer
=
win32gui.PyMakeBuffer(buf_size)
win32gui.
SendMessage
(hwnd, win32con.WM_GETTEXT, buf_size, buffer)
windowText
=
buffer[:buf_size]
try
:
windowText
=
unicode(windowText,
'
gbk
'
)
except
:
pass
message
=
[
'
Handle:\t
'
+
str(hwnd),
'
Class Name:\t
'
+
className,
'
Window Text:\t
'
+
windowText]
self.output(
'
\r\n
'
.join(message))
复制代码
注意到上面SendMessage函数,需要传入一个分配的缓冲区,用于获取返回的内容。这里使用了:
buffer
=
win32gui.PyMakeBuffer(buf_size)
由于返回的内容中可能有中文,因此使用unicode(windowText, 'gbk')进行一下转换。
演示
二进制下载:
http://pyspyplusplus.googlecode.com/files/pyspy++.exe
源代码:
http://code.google.com/p/pyspyplusplus/