【pyHook】
pyHook是一個用來進行鍵盤、鼠標等層面事件監控的庫。這個庫的正常工作需要pythoncom等操作系統的API的支持。首先來說說如何安裝。
直接pip install pyHook是找不到相關包的,不知道是不是因為這個庫可以被用於一些比較邪惡的目的。。於是就要去網上下,可以下源碼編譯安裝,不過在https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyhook這里可以直接下載到.whl文件,這個文件可以作為pip install 的參數來進行安裝。也就是說pip install xxx.whl即可。
安裝完成后import pyHook無誤,順便可以檢查一下import win32com和pythoncom兩個庫,確保支持的庫都有安裝。
■ 鍵盤事件監聽基本使用
監測鍵盤事件主要用到了pyHook的KeyBoardEvent類,這個類的源碼是這樣的:
class KeyboardEvent(HookEvent): ''' Holds information about a mouse event. @ivar KeyID: Virtual key code @type KeyID: integer @ivar ScanCode: Scan code @type ScanCode: integer @ivar Ascii: ASCII value, if one exists @type Ascii: string ''' def __init__(self, msg, vk_code, scan_code, ascii, flags, time, hwnd, window_name): '''Initializes an instances of the class.''' HookEvent.__init__(self, msg, time, hwnd, window_name) self.KeyID = vk_code self.ScanCode = scan_code self.Ascii = ascii self.flags = flags def GetKey(self): ''' @return: Name of the virtual keycode @rtype: string ''' return HookConstants.IDToName(self.KeyID) def IsExtended(self): ''' @return: Is this an extended key? @rtype: boolean ''' return self.flags & 0x01 def IsInjected(self): ''' @return: Was this event generated programmatically? @rtype: boolean ''' return self.flags & 0x10 def IsAlt(self): ''' @return: Was the alt key depressed? @rtype: boolean ''' return self.flags & 0x20 def IsTransition(self): ''' @return: Is this a transition from up to down or vice versa? @rtype: boolean ''' return self.flags & 0x80 Key = property(fget=GetKey) Extended = property(fget=IsExtended) Injected = property(fget=IsInjected) Alt = property(fget=IsAlt) Transition = property(fget=IsTransition)
最下面的property函數可以設置一個類似java成員變量的類屬性(之前在Python類結構中就提到過@property這個設置類屬性的方法)。fget參數設置一個方法,這個方法的返回將被設置為這個類屬性的值。那幾個方法返回的內容在注釋里面都說明了,而&這個位運算符,emmm…不說了吧
接下來是一個簡單的實例,當我們按下一個按鍵的時候stdout中打印出這個動作的信息,松開時再打印出松開的信息。注意,pyHook調用的是系統層面的鈎子而不是這個進程自己范圍內的東西。這也就是說,在系統中的任何地方(不論是python shell內外),按鍵時都會觸發事件。
import pyHook import pythoncom class KeyBoardManager(): keyIsPressed = False def onKeyDown(self,event): if self.keyIsPressed: return True print str(event.Key) + ' is pressed' self.keyIsPressed = True return True def onKeyUp(self,event): self.keyIsPressed = False print str(event.Key) + ' is released' return True if __name__ == '__main__': mykbmanager = KeyBoardManager() hookmanager = pyHook.HookManager() hookmanager.KeyDown = mykbmanager.onKeyDown hookmanager.KeyUp = mykbmanager.onKeyUp hookmanager.HookKeyboard() pythoncom.PumpMessages()
可以看到,HookManager是我們用的pyHook庫中的一個類。這個類其實就是用來管理hook各種各樣的事件的。通過它我們可以往不同的事件上綁定不同的函數(方法)從而實現監聽的功能。另外定義了一個類作為充實HookManager的內容。至於為什么不用幾個直接的函數而是用了一個類和類中的方法,這個之后再說。
然后再來看KeyBoardManager類,實現了兩個方法,分別都有一個event參數,這個參數就是指代了一個KeyBoardEvent的實例。這里主要用到了event.Key這個屬性。這個屬性就是指代了哪個鍵盤上的按鍵被按下,這里雖然用了str方法轉化類型,但是似乎直接用event.Key也是一個字符串類型。這里特別需要注意的一點:需要搞清楚事件觸發條件的定義。比如KeyDown事件並不是“按下鍵”,而是“按着鍵”,當我們規定一個方法,方法里print event.Key,然后我們關聯了這個方法和hookmanager.KeyDown,這樣只要我們按着某個鍵不松開,stdout就會源源不斷地打印出這個鍵名。另外,事件響應函數必須在合適的地方進行返回,返回內容最好是True或者False,當返回True時,意思是除了我們規定的事件之外,原來按鍵本身就會發生的事件也會發生。比如在上面這段監聽程序下,我們打開記事本准備輸入,長按空格鍵,當onKeyDown方法兩處都返回True時(如代碼中一樣),空格還是會被源源不斷輸入記事本。但是如果兩處都返回False,則長按空格鍵只是打印出Space is pressed,而不會在記事本中輸入任何空格。這么一來就好比是鍵盤被禁言了,無論在系統的什么地方輸入什么都不會奏效。如果在下面的return處是True,而在上面設置return False可能是比較理想的情況,此時按鍵時的輸入會被寫到記事本,且只寫一次,而按下和松開也有相應的信息輸出。
在這個實例中,我們的期望是按下按鍵之后打印出信息,然后松開的時候再打印一次,中途按着的時候不用打印。這時候就需要在方法的定義上下一些功夫,這也就是為什么我們不用簡單的函數而是用類方法來定義事件響應函數。簡單來說就是我們在類中維護了一個狀態flag,當鍵被按下時首先置狀態為True,之后在按着的過程中此狀態一直保持True,所以一直不會在屏幕上輸出,直到松開,狀態被置回並且打印松開的信息(松開的事件是“送開鍵”而不是“送着鍵”,要不然什么都不干就有事件了。。)
KeyBoardEvent類的屬性,我們只用了Key,其實還有很多:
print "MessageName:", event.MessageName print "Message:", event.Message print "Time:", event.Time print "Window:", event.Window print "WindowName:", event.WindowName print "Ascii:", event.Ascii, chr(event.Ascii) print "Key:", event.Key print "KeyID:", event.KeyID print "ScanCode:", event.ScanCode print "Extended:", event.Extended print "Injected:", event.Injected print "Alt", event.Alt print "Transition", event.Transition ''' 比如我按了下回車鍵,其返回就是: MessageName: key down Message: 256 Time: 195436180 Window: 329524 WindowName: TmpProjects - [D:\PythonProjects\TmpProjects] - ...\tmp.py - PyCharm 5.0.3 Ascii: 13 Key: Return KeyID: 13 ScanCode: 28 Extended: 0 Injected: 0 Alt 0 Transition 0 --- '''
其實這些里大多就是上面提到的KeyBoardEvent類中設置的一些屬性。MessageName字段描述事件的名稱,Message則是事件類型的編號,Time就是事件發生時間,Window是事件發生在的窗口的編號,Ascii是按鍵內容的ASCII碼,如果不屬於ASCII字符集則為0,Extended描述是否是拓展鍵(比如Fn+一些鍵用來調電腦音量,屏幕亮度等就是擴展鍵),Alt是指出了按鍵是否是Alt鍵(存疑。。)若是則值為32,等等……
■ 鼠標事件監聽基本使用
和鍵盤事件類似的,鼠標事件用到了MouseEvent類,然后通過HookManager類的HookMouse方法進行監聽,而關聯鈎子函數和事件響應函數之間的變量名可以類似於MouseLeftUp,MouseRightDown,MouseWheel,MouseMove之類的,如果不想設置這么細致也可以直接一個MouseAll來關聯所有鼠標相關事件和響應函數。
下面是一個類似於上面鍵盤事件監聽的實例:
import pyHook import pythoncom def onMouseEvent(event): print "MessageName:",event.MessageName print "Message:", event.Message print "Time:", event.Time print "Window:", event.Window print "WindowName:", event.WindowName print "Position:", event.Position print "Wheel:", event.Wheel print "Injected:", event.Injected print"---" return True if __name__ == '__main__': hookmanager = pyHook.HookManager() hookmanager.MouseAll = onMouseEvent hookmanager.Mouse hookmanager.HookMouse() pythoncom.PumpMessages() ''' 部分輸出結果是: MessageName: mouse move Message: 512 Time: 197121396 Window: 329524 WindowName: TmpProjects - [D:\PythonProjects\TmpProjects] - ...\tmp.py - PyCharm 5.0.3 Position: (786, 529) Wheel: 0 Injected: 0 --- MessageName: mouse left down Message: 513 Time: 197123690 Window: 329524 WindowName: TmpProjects - [D:\PythonProjects\TmpProjects] - ...\tmp.py - PyCharm 5.0.3 Position: (786, 529) Wheel: 0 Injected: 0 --- MessageName: mouse left up Message: 514 Time: 197123846 Window: 329524 WindowName: TmpProjects - [D:\PythonProjects\TmpProjects] - ...\tmp.py - PyCharm 5.0.3 Position: (786, 529) Wheel: 0 Injected: 0 --- MessageName: mouse wheel Message: 522 Time: 197126856 Window: 329524 WindowName: TmpProjects - [D:\PythonProjects\TmpProjects] - ...\tmp.py - PyCharm 5.0.3 Position: (786, 529) Wheel: -1 Injected: 0 --- 與上面這串輸出對應的操作是先移動鼠標,然后點擊一下左鍵,然后滑動了滾輪 '''
和鍵盤事件響應函數類似的,返回必須要有,而且當返回是False的時候,默認的鼠標事件不發生。也就是說你即使移動了鼠標,光標也不會動,造成一種假死的感覺。
最后,還需要提一下性能方面,監聽鼠標&鍵盤事件可能會另系統無法正常工作,比如上面監測鍵盤事件的時候,快速地(也就是我們正常打字速度吧。。)擊打鍵盤的時候,有時會記錄不全信息,有時也會發生記錄了信息但是我們的按鍵沒有達到目的。
