ethanhs / pyhooked

Pure Python hotkey hook, with thanks to pyHook and pyhk
155 stars 28 forks source link

WindowsError: exception: access violation reading 0x0000000000000008 #8

Closed panofish closed 8 years ago

panofish commented 8 years ago

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.

ethanhs commented 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.

panofish commented 8 years ago

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()
panofish commented 8 years ago

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_())
ethanhs commented 8 years ago

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.

panofish commented 8 years ago

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.

ethanhs commented 8 years ago

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.

ethanhs commented 8 years ago

@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?