Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't block an app's main thread when sending typed characters to NVDA #11425

Merged
merged 10 commits into from
Jul 28, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@ interface NvdaControllerInternal {

/**
* Notifies NVDA that a character has been typed in this thread.
* @param threadID the thread the layout change occured in
* @param ch the character typed.
*/
error_status_t __stdcall typedCharacterNotify([in] const long threadID, [in] const wchar_t ch);
error_status_t __stdcall typedCharacterNotify([in] const wchar_t ch);

/**
* Notifies NVDA that text in the given rectangle (in screen coordinates), in the given window, has changed.
Expand Down
6 changes: 3 additions & 3 deletions nvdaHelper/local/nvdaControllerInternal.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ error_status_t __stdcall nvdaControllerInternal_inputLangChangeNotify(const long
return _nvdaControllerInternal_inputLangChangeNotify(threadID,hkl,layoutString);
}

error_status_t(__stdcall *_nvdaControllerInternal_typedCharacterNotify)(const long, const wchar_t);
error_status_t __stdcall nvdaControllerInternal_typedCharacterNotify(const long threadID, const wchar_t ch) {
return _nvdaControllerInternal_typedCharacterNotify(threadID,ch);
error_status_t(__stdcall *_nvdaControllerInternal_typedCharacterNotify)(const wchar_t);
error_status_t __stdcall nvdaControllerInternal_typedCharacterNotify(const wchar_t ch) {
return _nvdaControllerInternal_typedCharacterNotify(ch);
}


Expand Down
32 changes: 25 additions & 7 deletions nvdaHelper/remote/injection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,32 @@ DWORD WINAPI inprocMgrThreadFunc(LPVOID data) {
// Even though we only registered for in-context winEvents, we may still receive some out-of-context events; e.g. console events.
// Therefore, we must have a message loop.
// Otherwise, any out-of-context events will cause major lag which increases over time.
do {
// Consume and handle all pending messages.
MSG msg;
while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
// We must also wait in an alertable state so that any APC functions queued to this thread by other NVDAHelper code will be executed.
while(true) {
const long handleCount = 1;
const long WAIT_PENDING_MESSAGES = WAIT_OBJECT_0 + handleCount;
DWORD res = MsgWaitForMultipleObjectsEx(
handleCount,
&nvdaUnregisteredEvent,
INFINITE,
QS_ALLINPUT,
MWMO_ALERTABLE // wait in an alert state so queued APC functions are executed.
);
if(res == WAIT_PENDING_MESSAGES) {
// Consume and handle all pending messages.
MSG msg;
while(PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
continue;
} else if(res == WAIT_IO_COMPLETION) {
// Woke for a queued APC function. Keep going.
continue;
}
} while(MsgWaitForMultipleObjects(1,&nvdaUnregisteredEvent,FALSE,INFINITE,QS_ALLINPUT)==WAIT_OBJECT_0+1);
// anything else (the registrationEvent was set, there was an error) means we need to stop.
break;
}
nhAssert(inprocMgrThreadHandle);
inprocThreadsLock.acquire();
CloseHandle(inprocMgrThreadHandle);
Expand Down
3 changes: 3 additions & 0 deletions nvdaHelper/remote/nvdaHelperRemote.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,7 @@ bool registerWindowsHook(int hookType, HOOKPROC hookProc);
*/
bool unregisterWindowsHook(int hookType, HOOKPROC hookProc);

// The handle for NVDA's inproc manager thread
extern HANDLE inprocMgrThreadHandle;

#endif
10 changes: 9 additions & 1 deletion nvdaHelper/remote/typedCharacter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,22 @@ This license can be found at:

HWND typedCharacter_window=NULL;

void __stdcall typedCharacter_apcFunc(ULONG_PTR data) {
nvdaControllerInternal_typedCharacterNotify(static_cast<wchar_t>(data));
}

LRESULT CALLBACK typedCharacter_getMessageHook(int code, WPARAM wParam, LPARAM lParam) {
static WPARAM lastCharacter=0;
MSG* pmsg=(MSG*)lParam;
if(pmsg->message==WM_KEYDOWN) {
typedCharacter_window=pmsg->hwnd;
lastCharacter=0;
} else if((typedCharacter_window!=0)&&(pmsg->message==WM_CHAR)&&(pmsg->hwnd==typedCharacter_window)&&(pmsg->wParam!=lastCharacter)) {
nvdaControllerInternal_typedCharacterNotify(GetCurrentThreadId(),static_cast<wchar_t>(pmsg->wParam));
// Instruct NVDA's inproc manager thread to report the typed character to NVDA via rpc.

// If we were to call the rpc function directly, it might cause a deadlock
// if NvDA were to interact with this app's main thread while the rpc function was in progress.
QueueUserAPC(typedCharacter_apcFunc, inprocMgrThreadHandle, static_cast<ULONG_PTR>(pmsg->wParam));
lastCharacter=pmsg->wParam;
}
return 0;
Expand Down
10 changes: 8 additions & 2 deletions source/NVDAHelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
import config

from ctypes import *
from ctypes import (
WINFUNCTYPE,
c_long,
c_wchar,
)
from ctypes.wintypes import *
from comtypes import BSTR
import winUser
Expand Down Expand Up @@ -394,8 +399,9 @@ def nvdaControllerInternal_inputLangChangeNotify(threadID,hkl,layoutString):
queueHandler.queueFunction(queueHandler.eventQueue,ui.message,msg)
return 0

@WINFUNCTYPE(c_long,c_long,c_wchar)
def nvdaControllerInternal_typedCharacterNotify(threadID,ch):

@WINFUNCTYPE(c_long, c_wchar)
def nvdaControllerInternal_typedCharacterNotify(ch):
focus=api.getFocusObject()
if focus.windowClassName!="ConsoleWindowClass":
eventHandler.queueEvent("typedCharacter", focus, ch=ch)
Expand Down