Skip to content

Commit

Permalink
Changes to how NVDA hooks the keyboard and mouse, to fix problems suc…
Browse files Browse the repository at this point in the history
…h as:

*The keyboard or mouse hook dying in NVDA on Windows 7 leaving the user unable to give NVDA commands (including shutting up speech), or getting feedback when moving the mouse.
*Sometimes no ability to control the Operating System with the mouse or keyboard if NVDA had frozen.
*the insert or other modifier keys seeming to get stuck down, if pressed while NVDA was extremely busy.
Specifically the changes are:
*Merge keyHook and mouseHook together and call it winInputHook.
*winInputHook: hook the keyboard and mouse in a separate thread to NVDA's main thread, so that if NVDA's main thread blocks, the hooks can still run. winInputHook.initialize and winInputHook.terminate are ref counted, so that it can be initialized more than once, and terminated more than once, quite safely. initialize and terminate do not take any parameters. use winInputHook.setCallbacks to set callback functions for  the hooks.
*keyboardHandler and mouseHandler: Use winInputHook rather than keyHook and mouseHook.
  • Loading branch information
michaelDCurran committed May 25, 2009
1 parent 2717d33 commit 68e7a5c
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 102 deletions.
50 changes: 0 additions & 50 deletions source/keyHook.py

This file was deleted.

7 changes: 4 additions & 3 deletions source/keyboardHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import config
import _winreg
import api
import keyHook
import winInputHook

keyUpIgnoreSet=set()
passKeyThroughCount=-1 #If 0 or higher then key downs and key ups will be passed straight through
Expand Down Expand Up @@ -240,7 +240,8 @@ def internal_keyUpEvent(vkCode,scanCode,extended,injected):

def initialize():
"""Initialises keyboard support."""
keyHook.initialize(internal_keyDownEvent,internal_keyUpEvent)
winInputHook.initialize()
winInputHook.setCallbacks(keyDown=internal_keyDownEvent,keyUp=internal_keyUpEvent)

def terminate():
keyHook.terminate()
winInputHook.terminate()
7 changes: 4 additions & 3 deletions source/mouseHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import eventHandler
from logHandler import log
import config
import mouseHook
import winInputHook

WM_MOUSEMOVE=0x0200
WM_LBUTTONDOWN=0x0201
Expand Down Expand Up @@ -117,7 +117,8 @@ def initialize():
mouseObject=api.getDesktopObject()
api.setMouseObject(mouseObject)
curMousePos=(x,y)
mouseHook.initialize(internal_mouseEvent)
winInputHook.initialize()
winInputHook.setCallbacks(mouse=internal_mouseEvent)

def pumpAll():
global mouseMoved, curMousePos, mouseShapeChanged, curMouseShape
Expand All @@ -133,4 +134,4 @@ def pumpAll():
mouseShapeChanged+=1

def terminate():
mouseHook.terminate()
winInputHook.terminate()
46 changes: 0 additions & 46 deletions source/mouseHook.py

This file was deleted.

101 changes: 101 additions & 0 deletions source/winInputHook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#winInputHook.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2008 NVDA Contributors <http://www.nvda-project.org/>
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

import threading
import comtypes.client
import time
from ctypes import *
from ctypes.wintypes import *
from win32con import WM_QUIT, HC_ACTION, WH_KEYBOARD_LL, LLKHF_UP, LLKHF_EXTENDED, LLKHF_INJECTED, WH_MOUSE_LL, LLMHF_INJECTED

class KBDLLHOOKSTRUCT(Structure):
_fields_=[
('vkCode',DWORD),
('scanCode',DWORD),
('flags',DWORD),
('time',DWORD),
('dwExtraInfo',DWORD),
]

class MSLLHOOKSTRUCT(Structure):
_fields_=[
('pt',POINT),
('mouseData',DWORD),
('flags',DWORD),
('time',DWORD),
('dwExtraInfo',DWORD),
]

keyDownCallback=None
keyUpCallback=None
mouseCallback=None

@WINFUNCTYPE(c_long,c_int,WPARAM,LPARAM)
def keyboardHook(code,wParam,lParam):
if code!=HC_ACTION:
return windll.user32.CallNextHookEx(0,code,wParam,lParam)
kbd=KBDLLHOOKSTRUCT.from_address(lParam)
if keyUpCallback and kbd.flags&LLKHF_UP:
if not keyUpCallback(kbd.vkCode,kbd.scanCode,bool(kbd.flags&LLKHF_EXTENDED),bool(kbd.flags&LLKHF_INJECTED)):
return 1
elif keyDownCallback:
if not keyDownCallback(kbd.vkCode,kbd.scanCode,bool(kbd.flags&LLKHF_EXTENDED),bool(kbd.flags&LLKHF_INJECTED)):
return 1
return windll.user32.CallNextHookEx(0,code,wParam,lParam)

@WINFUNCTYPE(c_long,c_int,WPARAM,LPARAM)
def mouseHook(code,wParam,lParam):
if code!=HC_ACTION:
return windll.user32.CallNextHookEx(0,code,wParam,lParam)
msll=MSLLHOOKSTRUCT.from_address(lParam)
if mouseCallback:
if not mouseCallback(wParam,msll.pt.x,msll.pt.y,msll.flags&LLMHF_INJECTED):
return 1
return windll.user32.CallNextHookEx(0,code,wParam,lParam)

hookThread=None
hookThreadRefCount=0

def hookThreadFunc():
keyHookID=windll.user32.SetWindowsHookExW(WH_KEYBOARD_LL,keyboardHook,windll.kernel32.GetModuleHandleW(None),0)
if keyHookID==0:
raise OSError("Could not register keyboard hook")
mouseHookID=windll.user32.SetWindowsHookExW(WH_MOUSE_LL,mouseHook,windll.kernel32.GetModuleHandleW(None),0)
if mouseHookID==0:
raise OSError("Could not register mouse hook")
msg=MSG()
while windll.user32.GetMessageW(byref(msg),None,0,0):
pass
if windll.user32.UnhookWindowsHookEx(keyHookID)==0:
raise OSError("could not unregister key hook %s"%keyHookID)
if windll.user32.UnhookWindowsHookEx(mouseHookID)==0:
raise OSError("could not unregister mouse hook %s"%mouseHookID)

def initialize():
global hookThread, hookThreadRefCount
hookThreadRefCount+=1
if hookThreadRefCount==1:
hookThread=threading.Thread(target=hookThreadFunc)
hookThread.start()

def setCallbacks(keyUp=None,keyDown=None,mouse=None):
global keyUpCallback, keyDownCallback, mouseCallback
if keyUp:
keyUpCallback=keyUp
if keyDown:
keyDownCallback=keyDown
if mouse:
mouseCallback=mouse

def terminate():
global hookThread, hookThreadRefCount
if not hookThread:
raise RuntimeError("winInputHook not running")
hookThreadRefCount-=1
if hookThreadRefCount==0:
windll.user32.PostThreadMessageW(hookThread.ident,WM_QUIT,0,0)
hookThread.join()
hookThread=None

0 comments on commit 68e7a5c

Please sign in to comment.