pythoncom crashes on KeyDown when used hooked to certain applications

Kevin picture Kevin · Oct 2, 2014 · Viewed 7.3k times · Source

I wrote this code on to observe the event of a keydown motion. The problem appears to be that when this script is run, certain programs will crash this program, spitting out this error message:

TypeError: KeyboardSwitch() missing 8 required positional arguments: 'msg', 'vk_
code', 'scan_code', 'ascii', 'flags', 'time', 'hwnd', and 'win_name'

Some programs observed to crash are: Skype, Sublime Text 2

After a few trials at debugging it, the problem appears to be occurring on the final line but I can't seem to narrow it down. I also don't understand the meaning of KeyboardSwitch() as returned by the compiler...

I have also found that the program would alternately return this error message

Traceback (most recent call last):
  File "C:\Python34\lib\site-packages\pyHook\HookManager.py", line 351, in KeyboardSwitch
    return func(event)
  File "observe.py", line 6, in OnKeyboardEvent
    print ('MessageName:',event.MessageName)
TypeError: an integer is required (got type NoneType)

What is the cause and how do I fix this, especially since it only appears for only 1 in 2 keys pressed

import pyHook, pythoncom

def OnKeyboardEvent(event):
# Source: http://code.activestate.com/recipes/553270-using-pyhook-to-block-windows-keys/ 
    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 ('---')

hooks_manager = pyHook.HookManager()
hooks_manager.KeyDown = OnKeyboardEvent
hooks_manager.HookKeyboard()
pythoncom.PumpMessages()

P.S. As a beginner, I'm not very familiar with the function of pythoncom and the online definitions appear to be rather vague. An explanation on the function of pythoncom and PumpMessages would be greatly appreciated.

Thanks

Answer

strubbly picture strubbly · Oct 27, 2015

I think the problem is that when pyHook gets called back by Windows, the first thing it does is get the window name for the window with focus.

PSTR win_name = NULL;
...
// grab the window name if possible
win_len = GetWindowTextLength(hwnd);
if(win_len > 0) {
  win_name = (PSTR) malloc(sizeof(char) * win_len + 1);
  GetWindowText(hwnd, win_name, win_len + 1);
}

So I think the problem here is that, even if GetWindowText is not returning wide characters, it can return non-ascii characters from an ANSI codepage. That won't fail, however, until we do this:

// pass the message on to the Python function
arglist = Py_BuildValue("(iiiiiiiz)", wParam, kbd->vkCode, kbd->scanCode, ascii,
                        kbd->flags, kbd->time, hwnd, win_name);

Here, because of the z in the format string, the data in the win_name variable is being converted to a unicode str with Py_BuildValue assuming it is ASCII. But it's not: and so it can trigger a UnicodeDecodeError. This then causes the arglist to be NULL and therefore your function to be called with no arguments.

So I'm not completely sure on the best fix here. But I just changed both bits of code to use wide characters and unicode instead of ascii, and rebuilt pyHook, and that seemed to fix it. I think it will only work in Python 3 versions, but for Python 2, I think the old pyHook still works anyway.

LPWSTR win_name = NULL;

...
// grab the window name if possible
win_len = GetWindowTextLengthW(hwnd);
if(win_len > 0) {
  win_name = (LPWSTR) malloc(sizeof(wchar_t) * win_len + 1);
  GetWindowTextW(hwnd, win_name, win_len + 1);
}

and

// pass the message on to the Python function
arglist = Py_BuildValue("(iiiiiiiu)", wParam, kbd->vkCode, kbd->scanCode, ascii,
                        kbd->flags, kbd->time, hwnd, win_name);

The problem occurs only with windows with non-ascii characters in their title: Skype is one.