Python天天美味(36) - 用Python实现Spy++

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’)进行一下转换。

演示
image
image
 
二进制下载:

http://pyspyplusplus.googlecode.com/files/pyspy++.exe 
源代码:

http://code.google.com/p/pyspyplusplus/ 
 
 

Python 天天美味系列(总)


Python 天天美味(31) - python数据结构与算法之插入排序 

Python 天天美味(32) - python数据结构与算法之堆排序 

Python 天天美味(33) - 五分钟理解元类(Metaclasses)[转]
Python 天天美味(34) - Decorators详解

Python 天天美味(35) - 细品lambda 

[温馨提示]:该文章由原博客园导入而来,如排版效果不佳,请移步:http://www.cnblogs.com/coderzh/archive/2010/05/02/python-cookbook-pyspy.html

微信扫一扫交流

作者:CoderZh
微信关注:hacker-thinking (一个程序员的思考)
本文出处:https://blog.coderzh.com/2010/05/02/python-cookbook-pyspy/
文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。