zoukankan      html  css  js  c++  java
  • 【pyHook】 监测键盘鼠标事件等

    【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:PythonProjectsTmpProjects] - ...	mp.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:PythonProjectsTmpProjects] - ...	mp.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:PythonProjectsTmpProjects] - ...	mp.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:PythonProjectsTmpProjects] - ...	mp.py - PyCharm 5.0.3
    Position: (786, 529)
    Wheel: 0
    Injected: 0
    ---
    MessageName: mouse wheel
    Message: 522
    Time: 197126856
    Window: 329524
    WindowName: TmpProjects - [D:PythonProjectsTmpProjects] - ...	mp.py - PyCharm 5.0.3
    Position: (786, 529)
    Wheel: -1
    Injected: 0
    ---
    与上面这串输出对应的操作是先移动鼠标,然后点击一下左键,然后滑动了滚轮
    '''

      和键盘事件响应函数类似的,返回必须要有,而且当返回是False的时候,默认的鼠标事件不发生。也就是说你即使移动了鼠标,光标也不会动,造成一种假死的感觉。

        最后,还需要提一下性能方面,监听鼠标&键盘事件可能会另系统无法正常工作,比如上面监测键盘事件的时候,快速地(也就是我们正常打字速度吧。。)击打键盘的时候,有时会记录不全信息,有时也会发生记录了信息但是我们的按键没有达到目的。

  • 相关阅读:
    “echo >”和“echo >>”的区别
    socket 简介
    Shell 获取当前执行脚本的路径
    input子系统 KeyPad-Touch上报数据格式与机制
    Java垃圾回收-GC
    Java多线程-yield函数&interrupt函数
    Java内存模型-堆和栈的区别
    Java反射
    Java正则表达式
    Java多线程-sleep和wait的区别
  • 原文地址:https://www.cnblogs.com/franknihao/p/7904434.html
Copyright © 2011-2022 走看看