Zuzu-Typ / winput

winput Python extention - capture and send keyboard and mouse input on Windows
zlib License
28 stars 2 forks source link

why does this slow down my entire system #10

Closed lovettchris closed 1 year ago

lovettchris commented 1 year ago

just hook_mouse alone and simply store the event.position and the mouse becomes unusably slow.

Zuzu-Typ commented 1 year ago

Hi there @lovettchris ,

it only seems as though it would slow down your entire system. What's actually slowed down (or rather interrupted) is the Windows message queue.

Most applications use the user32.dll API to handle mouse / keyboard inputs. This API works as a combination of hooks and event-messages. When moving the mouse, its raw movement data is processed by the operating system. Once per screen refresh (e.g. 60 times per second for a display with 60 FPS) the accumulated raw data is summed up (i.e. how much the mouse was moved while the last frame was showing) and mouse speed / acceleration adjustments are applied. The result is the new position of the mouse cursor, without clamping applied (i.e. if you move the mouse up when it's already at the top of the screen, the resulting y-coordinate might be something like -1). This data is then transformed into a message (event) and put onto a message queue.

An application can hook onto specific events. For example, hook_mouse() will hook onto low level mouse input events. Quite importantly though, low level mouse input hooks are only processed, if the thread that installs the hook has a message loop:

This hook is called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook. Therefore, the thread that installed the hook must have a message loop. (source)

Since the hook can't be called by the operating system if no message loop is created, the message queue will wait for the python thread to ask for messages, which it never does in your example. Therefore, all applications that rely on the message queue (which is basically all of them) seem to be slow or hanging. You can evade this scenario by pressing Ctrl+Alt+Del, which temporarily uninstalls the hooks.

Either way, in the winput documentation, perhaps not as prominently as it should be, I stated:

You have to run a message loop to use a hook! (see [Running a message loop] below)

So to fix the issue you've mentioned, please try the following code:

import winput

# called by the user32 API
def mouse_callback(event):
    if event.action == winput.WM_MOUSEMOVE:
        x, y = event.position
        print(f"Mouse pos: {x: 5d} {y: 5d}", end="\r")

    elif event.action == winput.WM_LBUTTONDOWN:
        # exits the infinite message loop (when pressing the left mouse button)
        winput.stop()

# install the hook
winput.hook_mouse(mouse_callback)

# start message loop (infinite)
winput.wait_messages()

# unhook installed mouse hook
winput.unhook_mouse()

Make sure to always have a way of stopping the message loop (see winput.stop in the example above) and to always unhook the installed hooks. If you don't, you'll run into the same problem you had before. Also make sure not to do time consuming tasks (like printing, as in the example). As long as your code processes the events, no other thread can process them. Thus, everything becomes slow again.

You can also create your own message loop, using winput.get_message:

winput.wait_messages()
# is roughly equivalent to:
while True:
    winput.get_message()

Again, if you need to do time consuming tasks, you need to offload them into a different thread.

lovettchris commented 1 year ago

Ah, yes I was not pumping the message queue, so that explains it, thanks. I switched to your winput.get_mouse_pos polling interface and that is much simpler so I'll go with that, thanks..