Closed panofish closed 8 years ago
I am using a modified version of your code to make it work with 64 bit python 2.7.
If that is the case, I ask that you share the code that you are using (both a minimum reproduction case of the PyQt code, and the full source of pyhooked's __init__.py
) as I can't help you unless I know how you have changed the pyhooked file, and how you are using it.
This will better enable me to help you.
Here is my updated version:
import ctypes
from ctypes import wintypes
from collections import namedtuple
import platform
__version__="0.0.6"
KeyEvents=namedtuple("KeyEvents",(['event_type', 'key_code',
'scan_code', 'alt_pressed',
'time']))
MouseEvents=namedtuple("MouseEvents",(['event_type','mouse_x','mouse_y']))
class hook:
"""Main class to create and track hotkeys. Use hook.Hotkey to make a new hotkey"""
def __init__(self):
self.fhot=[]
self.list=[]
self.handlers=[]
self.IDs=[]
self.oldID=0
self.current_keys=[]
self.ID_TO_KEY = {
60129542152: 'Back',
64424509449: 'Tab',
120259084301: 'Return',
249108103188: 'Capital',
4294967323: 'Escape',
244813135904: 'Space',
#33: 'Prior',
#34: 'Next',
339302416419: 'End',
304942678052: 'Home',
322122547237: 'Left',
309237645350: 'Up',
330712481831: 'Right',
343597383720: 'Down',
360777252908: 'Snapshot', #PRTSCR
356482285614: 'Delete',
47244640304: '0',
8589934641: '1',
12884901938: '2',
17179869235: '3',
21474836532: '4',
25769803829: '5',
30064771126: '6',
34359738423: '7',
38654705720: '8',
42949673017: '9',
128849018945: 'A',
206158430274: 'B',
197568495683: 'C',
137438953540: 'D',
77309411397: 'E',
141733920838: 'F',
146028888135: 'G',
150323855432: 'H',
98784247881: 'I',
154618822730: 'J',
158913790027: 'K',
163208757324: 'L',
214748364877: 'M',
210453397582: 'N',
103079215183: 'O',
107374182480: 'P',
68719476817: 'Q',
81604378706: 'R',
133143986259: 'S',
85899346004: 'T',
94489280597: 'U',
201863462998: 'V',
73014444119: 'W',
193273528408: 'X',
90194313305: 'Y',
188978561114: 'Z',
390842024027: 'Lwin',
# 92: 'Rwin',
# 93: 'App',
# 95: 'Sleep',
352187318368: 'Numpad0',
339302416481: 'Numpad1',
343597383778: 'Numpad2',
347892351075: 'Numpad3',
322122547300: 'Numpad4',
326417514597: 'Numpad5',
330712481894: 'Numpad6',
304942678119: 'Numpad7',
309237645416: 'Numpad8',
313532612713: 'Numpad9',
236223201386: 'Multiply',
335007449195: 'Add',
317827580013: 'Subtract',
356482285678: 'Decimal',
227633266799: 'Divide',
253403070576: 'F1',
257698037873: 'F2',
261993005170: 'F3',
266287972467: 'F4',
270582939764: 'F5',
274877907061: 'F6',
279172874358: 'F7',
283467841655: 'F8',
287762808952: 'F9',
292057776249: 'F10',
373662154874: 'F11',
377957122171: 'F12',
296352743568: 'Numlock',
180388626592: 'Lshift',
231928234145: 'Rshift',
124554051746: 'LCtrl', # merged hotkeys
124554051747: 'RCtrl',
# 164: 'Lmenu',
# 165: 'Rmenu',
# 186: 'Oem_1',
# 187: 'Oem_Plus',
# 188: 'Oem_Comma',
# 189: 'Oem_Minus',
# 190: 'Oem_Period',
# 191: 'Oem_2',
# 192: 'Oem_3',
# 219: 'Oem_4',
# 220: 'Oem_5',
# 221: 'Oem_6',
# 222: 'Oem_7',
# 1001: 'mouse left', # mouse hotkeys
# 1002: 'mouse right',
# 1003: 'mouse middle',
# 1000: 'mouse move', # single event hotkeys
# 1004: 'mouse wheel up',
# 1005: 'mouse wheel down',
240518168740: 'LAlt',
240518168741: 'RAlt',
180388626592: 'Shift',
395136991324: 'RWin',
390842024027: 'LWin'
}
#Scancodes and a few key codes
self.keylist=["Null","Esc","1","2","3","4","5","6","7","8","9","0","-","=","Backspace","Tab","Q","W","E","R","T","Y","U","I","O","P","[","]","Return","LCtrl","A","S","D","F","G","H","J","K","L",";","'","`","LShift","\\","Z","X","C","V","B","N","M",",",".","/","RShift","Key*","LAlt","Space","Capslock","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","Numlock","ScrollLock","KeyHome","Up","KeyPgUp","Key-","Left","Key5","Right","Key+","End","Down","KeyPgDn","KeyIns","KeyDel","SYSRQ","","","F11","F12","","","LWin","RWin","MenuKey","RAlt","RCtrl","PrtSc"]
def print_event(self,e):
"""This parses through the keyboard events. You shouldn't ever need this. Actually, don't mess with this; you may break your computer."""
if platform.python_implementation()=="PyPy":
if "scan_code" in str(e):
self.estr=str(e)
start=self.estr.index("scan_code=")
#PyPy seems to append an 'L' so search until that
end=self.estr.index("L",start)
scancode=long(str(e)[(start+10):end])
start2=self.estr.index("key_code=")
end2=self.estr.index("L",start2)
try:
keycode=long(str(e)[(start2+9):end2])
except:
keycode=0
try:
key=self.keylist[scancode]
except:
key=str(e)
elif platform.python_implementation()=="CPython" or platform.python_implementation()=="IronPython":
if "scan_code" in str(e):
self.estr=str(e)
start=self.estr.index("scan_code=")
end=self.estr.index(",",start)
scancode=long(str(e)[(start+10):end])
start2=self.estr.index("key_code=")
end2=self.estr.index(",",start2)
try:
keycode=long(str(e)[(start2+9):end2])
except:
keycode=01
try:
key = self.ID_TO_KEY[keycode]
# key=self.keylist[scancode]
except:
key=str(e)
# print "key=",key
if str(e.event_type)=="move":
key=[e.mouse_x,e.mouse_y]
elif not ("scan_code" in str(e)):
if e.event_type[0]=="l":
key="LMouse"
elif e.event_type[0]=="r":
key="RMouse"
elif e.event_type[:2]=="mi":
key="MMouse"
elif "wheel" in str(e.event_type):
key="Wheel"
# #alt keys
# if key=="LAlt":
# if keycode==64:
# key=self.keylist[56] # LAlt
# elif keycode==65:
# key=self.keylist[94] # RAlt
# #Ctrl keys
# if key=="LCtrl":
# if keycode==62:
# key=self.keylist[29]
# elif keycode==63:
# key=self.keylist[95]
#Workaround for PrtSc
if key == "Key*":
if keycode == 44:
key = self.keylist[96]
#append to current_keys when down
if str(e.event_type)=="key down" or str(e.event_type)=="left down" or str(e.event_type)=="right down" or "wheel" in str(e.event_type):
self.current_keys.append(key)
for id in self.IDs:
#This next bit is complex. Basically it checks all the hotkeys provided to see if they are in self.current_keys
if all([(i in self.current_keys) for i in id[1]]):
if len(id)==4:
id[2](id[3])
else:
id[2]()
#remove key when released
elif str(e.event_type)=="key up" or str(e.event_type)=="left up" or str(e.event_type)=="right up":
try:
while key in self.current_keys:
self.current_keys.remove(key)
except:
pass
#append location for motion
elif str(e.event_type)=="move":
for i in self.current_keys:
if len(i)>1:
self.current_keys.remove(i)
self.current_keys.append(key)
for id in self.IDs:
#This next bit is complex. Basically it checks all the hotkeys provided to see if they are in self.current_keys
if all([(i in self.current_keys) for i in id[1]]):
if len(id)==4:
id[2](id[3])
else:
id[2]()
else:
pass
# print(e) # uncomment to discover keys not set in dictionary
def Hotkey(self,list=[],fhot=None,args=None):
"""Adds a new hotkey. Definition: Hotkey(list=[],fhot=None) where list is the list of
keys and fhot is the callback function"""
if not (args is None):
self.IDs.append([self.oldID,list,fhot,args])
else:
self.IDs.append([self.oldID,list,fhot])
self.oldID+=1
if self.list is [] or self.fhot is None:
raise Exception("Error: Empty key list or no callback function.")
elif len(self.IDs)==1:
self.handlers.append(self.print_event)
return (self.oldID-1)
def RemHotKey(self,hkey):
"""Remove a hotkey. Specify the id, the key list, or the function to remove the hotkey."""
if str(type(hkey))=="<type 'int'>":
for hotk in self.IDs:
if hotk[0]==hkey:
self.IDs.remove(hotk)
elif str(type(hkey))=="<type 'list'>":
for hotk in self.IDs:
if hotk[1]==hkey:
self.IDs.remove(hotk)
elif str(type(hkey))=="<type 'function'>":
for hotk in self.IDs:
if hotk[2]==hkey:
self.IDs.remove(hotk)
def listener(self):
"""The listener listens to events and adds them to handlers"""
from ctypes import windll, CFUNCTYPE, POINTER, c_int, c_void_p, byref, Structure
import atexit
event_types = {0x100: 'key down', #WM_KeyDown for normal keys
0x101: 'key up', #WM_KeyUp for normal keys
0x104: 'key down', # WM_SYSKEYDOWN, used for Alt key.
0x105: 'key up', # WM_SYSKEYUP, used for Alt key.
}
mouse_types={0x200: 'move', #WM_MOUSEMOVE
0x20A: 'wheel', #WM_MOUSEWHEEL
0x20E: 'H_wheel', #WM_MOUSEHWHEEL
0x204: 'right down', #WM_RBUTTONDOWN
0x205: 'right up', #WM_RBUTTONUP
0x201: 'left down', #WM_LBUTTONDOWN
0x202: 'left up', #WM_LBUTTONUP
0x207: 'middle down', #WM_MBUTTONDOWN
0x208: 'middle up'} #WM_MBUTTONUP
def low_level_handler(nCode, wParam, lParam):
"""
Processes a low level Windows keyboard event.
"""
event = KeyEvents(event_types[wParam], lParam[0], lParam[1],
lParam[2] == 32, lParam[3])
for h in self.handlers:
h(event)
#return next hook
return windll.user32.CallNextHookEx(hook_id, nCode, wParam, lParam)
CMPFUNC = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p))
#Make a C pointer
pointer = CMPFUNC(low_level_handler)
windll.kernel32.GetModuleHandleW.restype = wintypes.HMODULE
windll.kernel32.GetModuleHandleW.argtypes = [wintypes.LPCWSTR]
hook_id = windll.user32.SetWindowsHookExA(0x00D, pointer,
windll.kernel32.GetModuleHandleW(None), 0)
def low_level_handler_mouse(nCode, wParam, lParam):
"""
Processes a low level Windows mouse event.
"""
event = MouseEvents(mouse_types[wParam], lParam[0], lParam[1])
for h in self.handlers:
h(event)
#return next hook
return windll.user32.CallNextHookEx(hook_id, nCode, wParam, lParam)
pointer2 = CMPFUNC(low_level_handler_mouse)
hook_id = windll.user32.SetWindowsHookExA(0x0E, pointer2,
windll.kernel32.GetModuleHandleW(None), 0)
#Remove hook when done
atexit.register(windll.user32.UnhookWindowsHookEx, hook_id)
while True:
msg = windll.user32.GetMessageW(None, 0, 0,0)
windll.user32.TranslateMessage(byref(msg))
windll.user32.DispatchMessageW(byref(msg))
def listen(self):
"""Start listening for hooks"""
self.listener()
def foo(*args):
"""For the example, it prints 'foo'."""
print("foo", args)
def foobar():
"""For the example, it prints 'foobar'."""
print("foobar")
def exiter():
raise SystemExit
if __name__ == '__main__':
hk=hook()
hk.Hotkey(["LCtrl","A"],foo,args=("HI")) # hotkey 0
hk.Hotkey(["LCtrl","C"],foobar) #hotkey 1
hk.RemHotKey(1) # or you could use hk.RemHotKey(["LCtrl","B"]) or hk.RemHotKey(foobar)
hk.Hotkey(['LCtrl','LAlt','C'],exiter) #allows you to exit
hk.listen()
Here is the sample PyQt4 code necessary to recreate the access violation error message. If you comment out the pyhooked logic, then the PyQt4 window appears normally without any errors.
import sys
sys.path.append('./Lib')
from pyhooked import hook
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QMainWindow):
def __init__(self, *args):
QMainWindow.__init__(self, *args)
layout = QVBoxLayout()
label = QLabel(self)
label.setText('A PyQt4 Window')
layout.addWidget(label)
self.adjustSize()
hk=hook() #make a new instance of PyHooked
hk.Hotkey(["LWin","Z"],self.foo) #add a new shortcut win+z, that calls foo() when pressed
hk.listen() #start listening
def foo(self):
print "activate menu... win-z has been pressed"
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
I believe that @swprojects put his/her GUI code in a thread, which solved most of his/her problems. I would suggest that until I can test this more.
I tried that and it appears to work, but it adds considerable extra logic. My PyQt app already has several threads and I need the pyhook logic to communicate with the thread. I was hoping for something a bit more elegant.
So, I took a look with the latest code in the dev
branch. And testing this:
import sys
from pyhooked import Hook, KeyboardEvent
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QMainWindow):
def __init__(self, *args):
QMainWindow.__init__(self, *args)
self.label = QLabel(self)
self.label.setText('A PyQt4 Window')
self.resize(1000,800)
hk=Hook() #make a new instance of PyHooked
hk.handler = self.foo
hk.hook() #start listening
def foo(self,args):
if isinstance(args, KeyboardEvent):
print(args.key_code)
if args.current_key == 'A' and args.event_type == 'key down' and 'Lcontrol' in args.pressed_key:
self.label.setText("Ctrl + A was pressed")
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
It no longer gives an exception, however on calling hk.hook()
, it seems to block showing QMainWindow
. However, changing the code around calling hk.hook()
to the following makes the above work as expected:
hk=Hook() #make a new instance of PyHooked
hk.handler = self.foo
thread = threading.Thread(target=hk.hook)
thread.start()
This requires substantially less new logic to implement, and I hope is an acceptable solution to your issue.
@panofish if the above is still not acceptable, I will reopen, but I think that is about as good as it will get. Perhaps adding a threaded=True
flag to the creation of Hook
so that it takes care of threading hk.hook()
itself?
I have am running into the same problem as previously posted.
The error occurs in listener: msg = windll.user32.GetMessageW(None, 0, 0,0) WindowsError: exception: access violation reading 0x0000000000000008
Everything works fine when I use the demo examples, however, when I use the same logic inside my PyQt application, then the above error occurs.
I am using a modified version of your code to make it work with 64 bit python 2.7.