0

I am trying to make use of Windows Hooks in order to intercept and block keystrokes while my application sends its own gui events.

I came up with the following listing:

import pythoncom
import pyHook
import threading
import time


def on_keyboard_event(event):
    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
    print '---'
    return False


class WindowsHooksWrapper:
    def __init__(self):
        self.started = False
        self.thread = threading.Thread(target=self.thread_proc)
        self.hook_manager = pyHook.HookManager()

    def start(self):
        if self.started:
            self.stop()

        # Register hook
        self.hook_manager.KeyDown = on_keyboard_event
        self.hook_manager.KeyUp = on_keyboard_event
        self.hook_manager.HookKeyboard()

        # Start the windows message pump
        self.started = True
        self.thread.start()

    def stop(self):
        if not self.started:
            return

        self.started = False
        self.thread.join()

        self.hook_manager.UnhookKeyboard()

    def thread_proc(self):
        print "Thread started"
        while self.started:
            pythoncom.PumpWaitingMessages()

        print "Thread exiting..."


class WindowsHooksWrapper2:
    def __init__(self):
        self.started = False
        self.thread = threading.Thread(target=self.thread_proc)

    def start(self):
        if self.started:
            self.stop()

        self.started = True
        self.thread.start()

    def stop(self):
        if not self.started:
            return

        self.started = False
        self.thread.join()

    def thread_proc(self):
        print "Thread started"

        # Evidently, the hook must be registered on the same thread with the windows msg pump or
        #     it will not work and no indication of error is seen
        # Also note that for exception safety, when the hook manager goes out of scope, it
        #     unregisters all outstanding hooks
        hook_manager = pyHook.HookManager()
        hook_manager.KeyDown = on_keyboard_event
        hook_manager.KeyUp = on_keyboard_event
        hook_manager.HookKeyboard()

        while self.started:
            pythoncom.PumpWaitingMessages()

        print "Thread exiting..."
        self.hook_manager.UnhookKeyboard()


def main():
    # hook_manager = pyHook.HookManager()
    # hook_manager.KeyDown = on_keyboard_event
    # hook_manager.KeyUp = on_keyboard_event
    # hook_manager.HookKeyboard()
    # pythoncom.PumpMessages()

    hook_wrapper = WindowsHooksWrapper2()
    hook_wrapper.start()
    time.sleep(30)
    hook_wrapper.stop()


if __name__ == "__main__":
    main()

The commented out section in main was from the pyhook wiki tutorial and it works fine.

I then tried to integrate that into a class, which is the 'WindowsHooksWrapper' class. If I used that class, it does not work and keyboard messages go through to their intended target.

On a hunch, I then tried 'WindowsHooksWrapper2', where I moved the registration of the hook to the same thread with the message pump. It now works.

Is my hunch correct that it is a requirement for the registration to be on the same thread as the pump? If so, why?

Note that I have a feeling this is a requirement of the windows 32 API rather than python or the pyhook library itself, because I did it in C++ and had the same result using 'SetWindowsHook' directly.

Christopher Pisz
  • 3,757
  • 4
  • 29
  • 65

1 Answers1

1

You've created a thread-Scope hook.

These hook events are associated either with a specific thread or with all threads in the same desktop as the calling thread.

pythoncom.PumpWaitingMessages() in Python and GetMessage/PeekMessage in Win32 are the methods that get message from that "specific thread or all threads in the same desktop as the calling thread."

To create a global hook, in order for your keyboard hook to be accessible from all processes, it has to be placed in a DLL which will then be loaded into each process' address space. See this Answer for details of how to make a global keyboard hook.

Drake Wu
  • 6,927
  • 1
  • 7
  • 30