[Python] Several pits about PyHook3.HookManager

1. It is recommended to PyHook3.HookManagerfix a small bug first

This is a destructor bug of a class object, which is triggered when it is destructed (del): AttributeError: 'HookManager' object has no attribute 'keyboard_hook'
if it has not been repaired, the following code will trigger the bug when it runs (I have repaired it, so I will not post a screenshot)

from PyHook3 import HookManager
def Func():
    hm = HookManager()
Func()    

The solution is as shown in the figure (click the picture to jump to the github link):




2. PyHook3.HookManagerThe hooking behavior should be completed in a single thread, cross-threading is not allowed

When it comes to hooks, you have to mention a great API in the great win32 programming . That's right, the SetWindowsHookEx function (winuser.h)
. Although it mentions that the uninstall hook SetWindowsHookExneeds to be called after the call, it doesn't mention cross-threading at all. UnhookWindowsHookExTo put it simply, the hanging and removing of the hook must be done in the same function, and cannot cross threads , otherwise you can experience what is called "untouchable pain". I will write about how to use win32 hooks when I have time.

Back here, similarly, how can the global hook of PyHook3 bypass the underlying API, since the underlying API must be hooked and removed in the same thread, let alone in Python. If the installation and uninstallation of the hook are not completed within the same life cycle, then the hook will be directly abolished and cannot be used normally .

The test code is as follows:

from threading import Thread
from PyHook3 import HookManager
from time import sleep
import pythoncom

def onMouseEvent(event):#回调函数-鼠标
    print(event.Position)
    return True

hm=None#HookManager对象,由函数ThreadFunc创建生成
def ThreadFunc():
    global hm
    hm=HookManager()
    hm.MouseAll = onMouseEvent
    hm.HookMouse()
    # hm.UnhookMouse()#取消这条注释,或者注释掉上一条的“hm.HookMouse()”,都能成功运行钩子函数
    
if __name__=='__main__':
    Thread(target=ThreadFunc).start()
    sleep(0.1)
    hm.UnhookMouse()
    hm.HookMouse()
    pythoncom.PumpMessages()#进入消息循环

Then there are, by the way, "global hooks". The following is a short science popularization time, you can skip it if you are not interested.

In win32 programming, the global hook refers to the mouse and keyboard. The global hook is a thing that can successfully take effect everywhere after being hung (not just limited to a certain process). Among them, the enumeration value of the hook WH_KEYBOARD_LLis and WH_MOUSE_LL, (click here to view all hook enumeration values) , where "LL" means "Low-Level", which means "low-level", but this is not in the popular sense The meaning of "bad" is the "lower level". To put it bluntly, it has a high priority and a wide range of effects. In general, Low-Level functions are more difficult to use and more likely to be limited (for example, the applicable system is limited), such as Low-Level console output functions




3. After the hook is hung, it must enter the message loop

The hook must enter the message loop to take effect normally. The daydream of wanting to replace the message loop with methods such as time.sleep(), etc. so that the hook only runs for a period of time will actually only increase the pain. threading.Event.wait()This is a bit like "General threads are not allowed to modify UI elements. If you want to modify UI elements, you must use the UI thread", that is, "use a specific API to complete a specific behavior". Here, you must enter the message loop after hooking up, ( I didn't say that you can only use pythoncom.PumpMessagesthis function to enter the message loop)

from threading import Event#信号量
from threading import Thread
from PyHook3 import HookManager
from time import sleep
import pythoncom

def onMouseEvent(event):#回调函数-鼠标
    print(event.Position)
    return True

event=Event()#信号量
def ThreadFunc():
    hm=HookManager()
    hm.MouseAll = onMouseEvent
    hm.HookMouse()
    pythoncom.PumpMessages()#进入消息循环,只有这个才能成功运行,但是无法退出
    # sleep(1)#使用sleep,无端的挣扎并且钩子函数并没有成功调用
    # event.wait()#使用Event.wait等待信号,也是无端的挣扎
    hm.UnhookMouse()
    
if __name__=='__main__':
    event.clear()#清除状态
    Thread(target=ThreadFunc).start()
    sleep(2)
    event.set()#设置状态



4. It is recommended to win32gui.PumpMessagesenter the message loop

Let me talk about it here first, win32gui and pythoncom should be a family, why do they say "should", because I didn't check it carefully.
pythoncom的API:http://www.markjour.com/docs/pywin32-docs/pythoncom.html
win32gui的API:http://www.markjour.com/docs/pywin32-docs/win32gui.html
These two modules are in the same website, but I dare not say that this website is an official website. Although the documentation is in English, the documentation is incomplete (some APIs only have signatures without explaining functions and usage), and the label icon on the webpage is actually the Chinese word "code"...

Then, pythoncom.PumpMessagesit win32gui.PumpMessagescan be said that it is exactly the same, the API is the same, the function is the same, and more importantly, using the win32gui API can make the code more controllable, allowing you to exit the message loop at any time to remove the hook. Run the test code below to open up a new world for you. (Programming veterans please detour, let me fill a cup here)

#代码运行的1秒内,移动鼠标将输出鼠标坐标
#代码运行1秒后,钩子取下
#代码运行2秒后,结束运行

import win32gui as WG#喜闻乐见的消息循环PumpMessages
import win32api as WA#获取当前线程tid
import win32con as WC#获取枚举值WM_QUIT
from threading import Thread
from PyHook3 import HookManager
from time import sleep

def onMouseEvent(event):#回调函数-鼠标
    print(event.Position)
    return True

tid=0#线程id
def ThreadFunc():#线程
    global tid
    hm = HookManager()
    hm.MouseAll = onMouseEvent
    hm.HookMouse()
    tid=WA.GetCurrentThreadId()
    WG.PumpMessages()


if __name__=='__main__':
	print("Start Tracking")
	Thread(target=ThreadFunc).start()

	sleep(1)
	print("Stop Tracking")
	WG.PostThreadMessage(tid,WC.WM_QUIT,0,0)#偶然找到的API:https://www.cnblogs.com/strive-sun/p/14487576.html

	sleep(1)
	print("Exit")
	exit()




5. The wonderful restriction between [Xinkeng] PyHook3and PyQtandwin32ui

It is well combined between and, and then it stinks immediately when it is imported , PyHook3and it is amazing that these three, the two are no problem when combined with each other, but when the three are together, they really feel disgusting immediately, I don’t know the pot whose . As for why I use it , it is because a piece of my code needs to use screen capture, and the result stinks immediately, and then I have to bypass and use other methods to capture the screen.PyQt5win32ui
win32ui

When the following code is running, as long as the Qt window has the window focus, there must be serious freeze + no response when the mouse moves to the window, comment it out and it import win32uican run normally

import win32ui #该语句一旦运行,那么鼠标移至Qt窗口时必然会出现卡顿情况

import sys
from PyQt5.QtWidgets import QWidget,QApplication
from threading import Thread

from PyHook3 import HookManager
import win32gui as WG#喜闻乐见的消息循环PumpMessages
import win32api as WA#获取当前线程tid
import win32con as WC#获取枚举值WM_QUIT

class MouseHook:
    __tid=None#线程id
    def Start(self):
        if(not self.__tid):
            Thread(target=self.__ThreadFunc).start()
    def Stop(self):
        if(self.__tid):
            WG.PostThreadMessage(self.__tid,WC.WM_QUIT,0,0)

    def __ThreadFunc(self):#线程函数
        self.__tid=WA.GetCurrentThreadId()
        hm = HookManager()
        hm.MouseAll = self.__OnMouseEvent#设置挂钩函数
        hm.HookMouse()#启动挂钩
        WG.PumpMessages()#进入消息循环,直到调用WG.PostThreadMessage(tid,WC.WM_QUIT,0,0)
        hm.UnhookMouse()#关闭挂钩
        self.__tid=None
    def __OnMouseEvent(self,event):#回调函数-鼠标
        print(event.Position)
        return True

if __name__ == '__main__':
    app = QApplication(sys.argv)
    
    w=QWidget()
    w.show()
    mk=MouseHook()
    mk.Start()
    
    exit(app.exec())
    



6. [Fix the pit] PyQtIf there is UI operation when using it, it should be avoided as much as possiblethreading.Thread

Some strange UI symptoms are often threading.Threadcaused by (and often mistaken for other places), such as the module conflict problem mentioned in point 5 above. If the child thread PyQt5.QtCore.pyqtSignalis created by Qt's thread (or uses a semaphore) instead of using threading.Threadit, then the problem will be solved directly...
The following is a sample code:

import win32ui#即使不注释该句,脚本也能正常运行

import sys
from PyQt5.QtWidgets import QWidget,QApplication
from PyQt5.QtCore import Qt,QObject,pyqtSignal

from PyHook3 import HookManager,HookConstants
import win32gui as WG#喜闻乐见的消息循环PumpMessages
import win32api as WA#获取当前线程tid
import win32con as WC#获取枚举值WM_QUIT


class MouseHook(QObject):
    __tid=None#线程id
    __signal=pyqtSignal()
    def __init__(self):
        super().__init__()
        self.__signal.connect(self.__ThreadFunc)
    def Start(self):
        if(not self.__tid):
            self.__signal.emit()
    def Stop(self):
        if(self.__tid):
            WG.PostThreadMessage(self.__tid,WC.WM_QUIT,0,0)

    def __ThreadFunc(self):#线程函数
        self.__tid=WA.GetCurrentThreadId()
        hm = HookManager()
        hm.MouseAll = self.__OnMouseEvent#设置挂钩函数
        hm.HookMouse()#启动挂钩
        WG.PumpMessages()#进入消息循环,直到调用WG.PostThreadMessage(tid,WC.WM_QUIT,0,0)
        hm.UnhookMouse()#关闭挂钩
        self.__tid=None
    def __OnMouseEvent(self,event):#回调函数-鼠标
        print(event.Position)
        WA.SetCursorPos(event.Position)
        if(event.Message==HookConstants.WM_RBUTTONDOWN):#按下右键结束
            self.Stop()
            return False
        return True


if __name__ == '__main__':
    app = QApplication(sys.argv)
    
    w=QWidget()
    w.show()
    mk=MouseHook()
    mk.Start()
    
    exit(app.exec())
    



The above are a few pitfalls that I have stepped on PyHook3.HookManager. Code words are not easy, free whoring is free, but please do not reprint without permission.
The original blog was published on CSDN: https://blog.csdn.net/weixin_44733774/article/details/128379683

Guess you like

Origin blog.csdn.net/weixin_44733774/article/details/128379683