pythoncom crashes on KeyDown when used hooked to certain applications - python

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

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.

If only 1 out of each 2 presses works, it's definetely a problem with missing return value. Try returning either True or False.

The TypeError: KeyboardSwitch() missing 8 required positional arguments: 'msg', 'vk_code', 'scan_code', 'ascii', 'flags', 'time', 'hwnd', and 'win_name' error message indicates that you are trying to call a function named KeyboardSwitch and have not provided values for all of the required arguments.
The error message lists the required arguments as 'msg', 'vk_code', 'scan_code', 'ascii', 'flags', 'time', 'hwnd', and 'win_name'. These are the names of the arguments that the KeyboardSwitch function expects to receive when it is called. You must provide a value for each of these arguments in the correct order when you call the function.
To fix this error, you will need to make sure that you are providing values for all of the required arguments when you call the KeyboardSwitch function. You may also want to check the documentation for the KeyboardSwitch function to make sure that you are using it correctly and understand what each of the required arguments represents.

There's a pyhook for python3: https://github.com/Answeror/pyhook_py3k
This bug has been fixed in this project.

Related

How do I hook a ctypes.windll.user32.MessageBoxW by using ctypes.windll.user32.SetWindowsHookExW?

I wanna make a joke program that at first it opens a message box, after it closes then another message box appear at a random position. It would keep repeating like that until anything kills its task. Using tkinter message boxes then those can't be hooked, I must make another tkinter form (Which is really ugly and different from the Windows message box). So I switched to ctypes, and the problems begins. I created a callback function for the hook then, when I pass the function to the second parameter of the ctypes.windll.user32.SetWindowsHookExA function, then it show the TypeError: wrong type. How can I fix this?
I've tried to cast the function to a c_void_p, but doing that just get more errors like 'Illegal instruction'.
This is my code:
import ctypes, random
def msgBoxHook(nCode, wParam, lParam):
if nCode == 3:
hwnd = ctypes.wintypes.HWND
hwnd = wParam
msgRekt = ctypes.wintypes.RECT # >:)
ctypes.windll.user32.GetWindowRect(hwnd, ctypes.byref(msgRekt))
ctypes.windll.user32.MoveWindow(hwnd, randint(0, ctypes.windll.user32.GetSystemMetrics(0)), randint(0, ctypes.windll.user32.GetSystemMetrics(1)), msgRekt.right - msgRekt.left, msgRekt.bottom - msgRekt.top, True)
return ctypes.windll.user32.CallNextHookEx(0, nCode, wParam, lParam)
# When I try to call
ctypes.windll.user32.SetWindowsHookExA(5, msgBoxHook, 0, ctypes.windll.kernel32.GetCurrentThreadId())
# It shows:
"""
Traceback (most recent call last):
File "test.py", line 1, in <module>
ctypes.ArgumentError: argument 2: <class 'TypeError'>: wrong type
"""
Expected: When use ctypes.windll.user32.MessageBoxW(None, 'Hello', 'World', 0x00000010 | 0x00000000) it opens a message box at random position with caption 'World', text 'Hello' and Stop icon with OK button.
Reality: As shown above.
Listing [Python 3.Docs]: ctypes - A foreign function library for Python.
You can't pass a Python object to a plain C function (I mean you can, but the results won't be the expected ones).
[MS.Docs]: SetWindowsHookExA function expects an [MS.Docs]: CBTProc callback function when its 1st argument is WH_CBT (5). So, you have to wrap your function:
WH_CBT = 5
HOOKProc = ctypes.WINFUNCTYPE(wintypes.LPVOID, ctypes.c_int, wintypes.WPARAM, wintypes.LPARAM)
CBTProc = HOOKProc
hook = ctypes.windll.user32.SetWindowsHookExA(WH_CBT, CBTProc(msgBoxHook), 0, ctypes.windll.kernel32.GetCurrentThreadId())
This should get you past the current problem. But you'll definitely run into others (which of course can be the subject of other questions). One that I've already spotted, is that don't define argtypes and restype for any of the functions you use, so that will (almost) certainly get you into trouble (crashes). Some examples:
[SO]: Python ctypes cdll.LoadLibrary, instantiate an object, execute its method, private variable address truncated (#CristiFati's answer)
[SO]: python ctypes issue on different OSes (#CristiFati's answer)

Maxscript Python addModifier

I'm writing maxscript in python and the following code throws a type error:
import MaxPlus
res = MaxPlus.Core.GetRootNode()
#This is just as example that I use the first child.
child = MaxPlus.INode.GetChild(res,0)
morpherFP = MaxPlus.FPValue()
MaxPlus.Core.EvalMAXScript("Morpher()", morpherFP)
morpher = MaxPlus.FPValue.Get(morpherFP)
MaxPlus.INode.AddModifier(child, morpher)
And from the MaxScript Listener I always receive the following error:
type 'exceptions.TypeError' in method 'INode_AddModifier', argument 2 of type 'Autodesk::Max::Modifier'"
while the type of morpher is Animatable(Morpher) and Animatable is a subclass of Modifier. Could someone help me with this?
Thank you in advance
I think I found a possible solution (The only thing I know is that the MaxScript Listener doesn't throw an error):
import MaxPlus
res = MaxPlus.Core.GetRootNode()
#I use the first child as example
child = MaxPlus.INode.GetChild(res,0)
morpher = MaxPlus.Factory.CreateObjectModifier(MaxPlus.ClassIds.Morpher)
MaxPlus.INode.AddModifier(child, morpher)
# the following also seems to work aka it does not throw any errors
child.InsertModifier(morpher,1)
Let me know if it is not correct or there is an easier or more understandable way.

Python program working in CMD but not when exported to .exe

I'm having an issue where my python program works correctly when ran from the command prompt but does not function properly when exported to an exe. Specifically I'm having an issue with this section of code, maybe there is a better way of doing this?:
def select_pcb_num(self, boardDrawingNumber):
xTuple = XTuple()
temp = xTuple.find_pcb_item_number(boardDrawingNumber)
if len(temp)>1:
iSelect = int(0)
rawChar = ''
query = '{0} variants found, select correct board [up/down]: {1}\t\t\t\t'
sys.stdout.write(query.format(len(temp), temp[iSelect]))
rawChar = msvcrt.getch()
while not rawChar == '\r':
if ord(rawChar) == int(72): # upkey
iSelect = (iSelect + 1)%len(temp)
elif ord(rawChar) == int(80): # downkey
iSelect = (iSelect - 1)%len(temp)
sys.stdout.write('\r')
sys.stdout.write(query.format(len(temp), temp[iSelect]))
rawChar = msvcrt.getch()
sys.stdout.write('\n')
return temp[iSelect]
else:
return temp
In command prompt it correctly returns to the beginning of the line and writes over it when an up or down arrow is pressed. However when exported to an exe it causes the same line to be reprinted and than prints the correct line. Please see the example picture, the lines with the red arrows should not be printed and there shouldn't have been any new lines since I didn't get to the '\n' because no selection was made.
Update:
Input printed with the method repr() looks like when the down arrow is pressed it first registers as '\xe0' and than as 'P', why would having compiled to an exe cause this? Also I don't see why it's adding a new line since it should be in the while loop
This is the documented behaviour of getch on Windows. Arrow keys return first either 0x00 or 0xE0, then the key code. See documentation:
When reading a function key or an arrow key, each function must be
called twice; the first call returns 0 or 0xE0, and the second call
returns the actual key code.

How do I (using python) correctly create and pass a ctypes structure to a WER API function?

First off, I found the following two similar questions:
Passing Structure to Windows API in python ctypes
ctypes and passing a by reference to a function
The first does not have an accepted answer, and I do not think that I'm doing anything in separate processes. The second simply points out pointer() and byref(), both of which I have tried using to no avail.
Now, on to my question:
I am trying to call the function WERReportCreate with my own pReportInformation (which is a pointer to a struct whose first data value is its own size). This fails in various ways, depending on how I go about it, but I'm not sure how to do it correctly. It is complicated by the fact that one of the requirements is that the structure know it's own size, which I'm not sure how to programatically determine (though if that was the only issue, I think I would have guessed the right value by now). The relevant information from the WER API is shown below:
HRESULT WINAPI WerReportCreate(
__in PCWSTR pwzEventType,
__in WER_REPORT_TYPE repType,
__in_opt PWER_REPORT_INFORMATION pReportInformation,
__out HREPORT *phReportHandle
);
(full info at http://msdn.microsoft.com/en-us/library/windows/desktop/bb513625%28v=vs.85%29.aspx)
typedef struct _WER_REPORT_INFORMATION {
DWORD dwSize;
HANDLE hProcess;
WCHAR wzConsentKey[64];
WCHAR wzFriendlyEventName[128];
WCHAR wzApplicationName[128];
WCHAR wzApplicationPath[MAX_PATH];
WCHAR wzDescription[512];
HWND hwndParent;
} WER_REPORT_INFORMATION, *PWER_REPORT_INFORMATION;
(full info at http://msdn.microsoft.com/en-us/library/windows/desktop/bb513637%28v=vs.85%29.aspx)
This is the code that I have tried:
import ctypes
import ctypes.wintypes
class ReportInfo( ctypes.Structure):
_fields_ = [ ("dwSize", ctypes.wintypes.DWORD),
("hProcess", ctypes.wintypes.HANDLE),
("wzConsentKey", ctypes.wintypes.WCHAR * 64),
("wzFriendlyEventName", ctypes.wintypes.WCHAR * 128),
("wzApplicationName", ctypes.wintypes.WCHAR * 128),
("wzApplicationPath", ctypes.wintypes.WCHAR * ctypes.wintypes.MAX_PATH),
("wzDescription", ctypes.wintypes.WCHAR * 512),
("hwndParent", ctypes.wintypes.HWND) ]
def genReportInfo():
import os
size = 32 #Complete SWAG, have tried many values
process = os.getpid()
parentwindow = ctypes.windll.user32.GetParent(process)
werreportinfopointer = ctypes.POINTER(ReportInfo)
p_werinfo = werreportinfopointer()
p_werinfo = ReportInfo(size, process, "consentkey", "friendlyeventname", "appname", "apppath", "desc", parentwindow)
return p_werinfo
if __name__ == '__main__':
reporthandle = ctypes.wintypes.HANDLE()
res = ctypes.wintypes.HRESULT()
### First pass NULL in as optional parameter to get default behavior ###
p_werinfo = None
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', 2, p_werinfo, ctypes.byref(reporthandle))
print "Return Code",res,"\nHandle",reporthandle #Return Code 0, reporthandle is correct (verified by submitting report in a different test)
p_werinfo = genReportInfo() # Create our own struct
### Try Again Using Our Own Struct (via 'byref') ###
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', 2, ctypes.byref(p_werinfo), ctypes.byref(reporthandle))
print "Return Code",res,"\nHandle",reporthandle #Return Code Nonzero, reporthandle is None
### Try Again Using Our Own Struct (directly) ###
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', 2, p_werinfo, ctypes.byref(reporthandle))
print "Return Code",res,"\nHandle",reporthandle #Exception Occurs, Execution halts
And this is the output I get:
Return Code 0
Handle c_void_p(26085328)
Return Code -2147024809
Handle c_void_p(None)
Traceback (most recent call last):
File "test.py", line 40, in <module>
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', s.byref(reporthandle))
WindowsError: exception: access violation writing 0x0000087C
The fact that it works when I pass in a null, but not when I actually pass my (reference to my?) structure suggests to me I have one of three problems: I am not creating the structure correctly (I'm not certain the wzConsentKey is correctly defined), or I am not correctly figuring out the struct's size (I'm actually using struct.calcsize with various options to get initial guesses, and adding and subtracting 1 randomly), or I am not correctly passing the (reference to the?) structure.
Here is where I've hit a deadend. Any help would be appreciated (as well as suggestions for how to improve the clarity, formatting, or quality of my question; this is my first post).
The general short answer to the posted question is: Be sure you are putting the correct information into the structure, other than that, the provided code is a good example of creating and passing a structure. Here's what solved my problem specifically:
There were two issues with the provided code: First, as Mark Tolonen pointed out, I was passing an incorrect size. Using ctypes.sizeof(ReportInfo) solved that problem. The second issue was that I was using a process ID where a process handle was required. Using OpenProcess to obtain a valid process handle in place of my "process" argument solved the second problem.
To anyone debugging similar issues in the future, printing the HRESULTS out as hex numbers rather than integers to make better sense of the return codes:
print "Return Code %08x" % (res & 0xffffffff)
This, in my case, produced the following results:
Return Code 80070057
for my original error, and
Return Code 80070006
for the second error. Using the information at http://msdn.microsoft.com/en-us/library/bb446131.aspx , I saw the first half was metadata, the second half was my actual error code.
After converting the Error Code part of the Hex number back to decimal, I used http://msdn.microsoft.com/en-us/library/bb202810.aspx to determine that
Error Code 87 (57 in hex) meant "Parameter Incorrect" (size was wrong)
and
Error Code 6 (6 in hex) meant "The Handle is Invalid" (I was passing in a process ID).
You can use ctypes.sizeof(ReportInfo) to obtains the size in bytes of the structure.
Simply create the ReportInfo instance with genReportInfo. You don't need a pointer at this point:
def genReportInfo():
import os
size = ctypes.sizeof(ReportInfo)
process = os.getpid()
parentwindow = ctypes.windll.user32.GetParent(process)
return ReportInfo(size, process, "consentkey", "friendlyeventname", "appname", "apppath", "desc", parentwindow)
Call WerReportCreate like this. byref passes the pointer to the ReportInfo instance.
werinfo = genReportInfo()
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', 2, ctypes.byref(werinfo), ctypes.byref(reporthandle))
I think that will work for you. I don't have wer.dll so I can't test it.

Interesting "getElementById() takes exactly 1 argument (2 given)", sometimes it occurs. Can someone explain it?

#-*- coding:utf-8 -*-
import win32com.client, pythoncom
import time
ie = win32com.client.DispatchEx('InternetExplorer.Application.1')
ie.Visible = 1
ie.Navigate('http://ieeexplore.ieee.org/xpl/periodicals.jsp')
time.sleep( 5 )
ie.Document.getElementById("browse_keyword").value ="Computer"
ie.Document.getElementsByTagName("input")[24].click()
import win32com.client, pythoncom
import time
ie = win32com.client.DispatchEx('InternetExplorer.Application')
ie.Visible = 1
ie.Navigate('www.baidu.com')
time.sleep(5)
print 'browse_keword'
ie.Document.getElementById("kw").value ="Computer"
ie.Document.getElementById("su").click()
print 'Done!'
When run the first section code,it will popup:
ie.Document.getElementById("browse_keyword").value ="Computer"
TypeError: getElementById() takes exactly 1 argument (2 given)
And the second section code runs ok. What is the difference that making the result different?
The difference between the two cases has nothing to do with the COM name you specify: either InternetExplorer.Application or InternetExplorer.Application.1 result in the exact same CLSID which gives you an IWebBrowser2 interface. The difference in runtime behaviour is purely down to the URL you retrieved.
The difference here may be that the page which works is HTML whereas the other one is XHTML; or it may simply be that errors in the failing page prevent the DOM initialising properly. Whichever it appears to be a 'feature' of the IE9 parser.
Note that this doesn't happen if you enable compatibility mode (after the second line below I clicked the compatibility mode icon in the address bar):
(Pdb) ie.Document.DocumentMode
9.0
(Pdb) ie.Document.getElementById("browse_keyword").value
*** TypeError: getElementById() takes exactly 1 argument (2 given)
(Pdb) ie.Document.documentMode
7.0
(Pdb) ie.Document.getElementById("browse_keyword").value
u''
Unfortunately I don't know how to toggle compatibility mode from a script (the documentMode property is not settable). Maybe someone else does?
The wrong argument count is, I think, coming from COM: Python passes in the arguments and the COM object rejects the call with a misleading error.
As a method of a COMObject, getElementById is built by win32com dynamically.
On my computer, if url is http://ieeexplore.ieee.org/xpl/periodicals.jsp, it will be almost equivalent to
def getElementById(self):
return self._ApplyTypes_(3000795, 1, (12, 0), (), 'getElementById', None,)
If the url is www.baidu.com, it will be almost equivalent to
def getElementById(self, v=pythoncom.Missing):
ret = self._oleobj_.InvokeTypes(1088, LCID, 1, (9, 0), ((8, 1),),v
)
if ret is not None:
ret = Dispatch(ret, 'getElementById', {3050F1FF-98B5-11CF-BB82-00AA00BDCE0B})
return ret
Obviously, if you pass an argument to the first code, you'll receive a TypeError. But if you try to use it directly, namely, invoke ie.Document.getElementById(), you won't receive a TypeError, but a com_error.
Why win32com built the wrong code?
Let us look at ie and ie.Document. They are both COMObjects, more precisely, win32com.client.CDispatch instances. CDispatch is just a wrapper class. The core is attribute _oleobj_, whose type is PyIDispatch.
>>> ie, ie.Document
(<COMObject InternetExplorer.Application>, <COMObject <unknown>>)
>>> ie.__class__, ie.Document.__class__
(<class win32com.client.CDispatch at 0x02CD00A0>,
<class win32com.client.CDispatch at 0x02CD00A0>)
>>> oleobj = ie.Document._oleobj_
>>> oleobj
<PyIDispatch at 0x02B37800 with obj at 0x003287D4>
To build getElementById, win32com needs to get the type information for getElementById method from _oleobj_. Roughly, win32com uses the following procedure
typeinfo = oleobj.GetTypeInfo()
typecomp = typeinfo.GetTypeComp()
x, funcdesc = typecomp.Bind('getElementById', pythoncom.INVOKE_FUNC)
......
funcdesc contains almost all import information, e.g. the number and types of the parameters.
If url is http://ieeexplore.ieee.org/xpl/periodicals.jsp, funcdesc.args is (), while the correc funcdesc.args should be ((8, 1, None),).
Long story in short, win32com had retrieved the wrong type information, thus it built the wrong method.
I am not sure who is to blame, PyWin32 or IE. But base on my observation, I found nothing wrong in PyWin32's code. On the other hand, the following script runs perfectly in Windows Script Host.
var ie = new ActiveXObject("InternetExplorer.Application");
ie.Visible = 1;
ie.Navigate("http://ieeexplore.ieee.org/xpl/periodicals.jsp");
WScript.sleep(5000);
ie.Document.getElementById("browse_keyword").value = "Computer";
Duncan has already pointed out IE's compatibility mode can prevent the problem. Unfortunately, it seems it's impossible to enable compatibility mode from a script.
But I found a trick, which can help us bypass the problem.
First, you need to visit a good site, which gives us a HTML page, and retrieve a correct Document object from it.
ie = win32com.client.DispatchEx('InternetExplorer.Application')
ie.Visible = 1
ie.Navigate('http://www.haskell.org/arrows')
time.sleep(5)
document = ie.Document
Then jump to the page which doesn't work
ie.Navigate('http://ieeexplore.ieee.org/xpl/periodicals.jsp')
time.sleep(5)
Now you can access the DOM of the second page via the old Document object.
document.getElementById('browse_keyword').value = "Computer"
If you use the new Document object, you will get a TypeError again.
>>> ie.Document.getElementById('browse_keyword')
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
TypeError: getElementById() takes exactly 1 argument (2 given)
I just got this issue when I upgraded to IE11 from IE8.
I've only tested this on the getElementsByTagName function. You have to call the function from the Body element.
#-*- coding:utf-8 -*-
import win32com.client, pythoncom
import time
ie = win32com.client.DispatchEx('InternetExplorer.Application.1')
ie.Visible = 1
ie.Navigate('http://ieeexplore.ieee.org/xpl/periodicals.jsp')
time.sleep( 5 )
ie.Document.Body.getElementById("browse_keyword").value ="Computer"
ie.Document.Body.getElementsByTagName("input")[24].click()
Calls to methods of instances in Python automatically adds the instance as first argument - that's why you have to explicitly write the 'self' argument inside methods.
For example, instance.method(args...) is equal to Class.method(instance, args...).
From what I see the programmer must have forgotten to write the self keyword, resulting in breaking the method. Try to look inside the library code.

Categories

Resources