Skip to content

No longer freeze or restart when pressing control+shift+downArrow in MS Word#11478

Merged
michaelDCurran merged 1 commit into
masterfrom
i9463-3
Aug 11, 2020
Merged

No longer freeze or restart when pressing control+shift+downArrow in MS Word#11478
michaelDCurran merged 1 commit into
masterfrom
i9463-3

Conversation

@michaelDCurran

@michaelDCurran michaelDCurran commented Aug 10, 2020

Copy link
Copy Markdown
Member

An alternative of pr #11477
This time, send the VK_NONE from the main thread, if the script does not send the gesture. This completely avoids deliberate reentrancy in the keyboard hook, and also avoids a time.sleep and the call to wx.yield in the winInputHook thread (which causes a wx assertion). Users report that no only does this fix the issue but this also massively speeds up holding down of control+shift+downArrow in MS Word, no doubt due to no longer sleeping or yielding in the keyboard hook.

Link to issue number:

Fixes #9463

Summary of the issue:

background

As NVDA detects key presses in its key hook, it may block a certain key from continuing through to the Operating System if that key press is bound to a particular NvDA script. The script may or may not then choose to send the key onto the Operating system later, depending on the action it is taking.
However, certain key combinations such as control+shift, may cause an action in Windows such as changing the input language, if a main key is not also included with that key combination. E.g. control+shift by itself changes input languages, but control+shift+downArrow does not. But, if NVDA blocks control+shift+downArrow, and does not send any key, then the input language is still changed. To get around this, in its key hook, NVDA sends a special VK_NONE (0xff) when modifiers like control+shift are pressed, (whether or not NVDA scripts later send a key) to ensure that the Operating System does not change input languages.

Actual problem

To send the VK_NONE key press in its key hook, currently NVDA uses KeyboardInputGesture.send, which among other things, enters a lock and sets a global variable which flags that NVDA should ignore any injected keys at the moment (I.e. this one).
The assumption was that during the call to KeyboardInputGesture.send (or keybd_event more specifically) only the key press NVDA sent would be executed in NVDA's key hook. And because the OS would have marked it as injected, and because ignoreInjected was true, then we would exit from the reentered key hook early.
But, on some Operating systems (E.g. users in #9463 ) other key presses currently pending, may also execute in the key hook inside keybd_event. In this case though, they are not injected, and therefore the entire key hook runs, and as these key presses are most likely repeats of modifier keys that require us to send the VK_NONE, then we do this, but cause a deadlock in the winInputHook thread as our ignoreInjected lock is not reentrant. Further to this, NVDA's main thread may then actually itself call KeyboardInputGesture.send in a script (perhaps for control+shift+downArrow) which also tries to take the lock, and waits for ever as well.

See the following Python stacks:

Python stack for thread 10376 (winInputHook):
  File "threading.pyc", line 890, in _bootstrap
  File "threading.pyc", line 926, in _bootstrap_inner
  File "threading.pyc", line 870, in run
  File "winInputHook.pyc", line 79, in hookThreadFunc
  File "winInputHook.pyc", line 54, in keyboardHook
  File "keyboardHandler.pyc", line 208, in internal_keyDownEvent
  File "keyboardHandler.pyc", line 528, in send
  File "winUser.pyc", line 494, in keybd_event
  File "winInputHook.pyc", line 56, in keyboardHook
  File "winInputHook.pyc", line 54, in keyboardHook
  File "keyboardHandler.pyc", line 208, in internal_keyDownEvent
  File "keyboardHandler.pyc", line 521, in send
  File "contextlib.pyc", line 112, in __enter__
  File "keyboardHandler.pyc", line 70, in ignoreInjection 

and

Python stack for thread 11700 (MainThread):
  File "nvda.pyw", line 215, in <module>
  File "core.pyc", line 545, in main
  File "wx\core.pyc", line 2134, in MainLoop
  File "gui\__init__.pyc", line 1049, in Notify
  File "core.pyc", line 515, in run
  File "queueHandler.pyc", line 88, in pumpAll
  File "queueHandler.pyc", line 55, in flushQueue
  File "scriptHandler.pyc", line 166, in _queueScriptCallback
  File "scriptHandler.pyc", line 208, in executeScript
  File "editableText.pyc", line 385, in script_caret_changeSelection
  File "keyboardHandler.pyc", line 521, in send
  File "contextlib.pyc", line 112, in __enter__
  File "keyboardHandler.pyc", line 70, in ignoreInjection

Description of how this pull request fixes the issue:

Rather than sending the VK_NONE from the keyboard hook itself, instead send it from the main thread, by doing the following:

  • inputCore.InputGesture: add an executeScript method which executes the given script passing it this gesture, by using scriptHandler.executeScript. this is implemented to allow Gesture subclasses to perform actions before / after script execution.
  • scriptHandler._queueScriptCallback: call Gesture.executeScript rather than executeScript directly.
  • keyboardHandler.KeyboardInputGesture: track the number of times this Gesture instance is sent as input onto the Operating System (I.e. how many times Gesture.send is called). this is recorded on a sendCount instance variable. This is useful to see if a script has actually sent its gesture on or not.
  • keyboardHandler.KeyboardInputGesture: implement executeScript, which after executing the script normally, checks to see if the gesture was never actually sent, and that the gesture's modifiers could possibly perform an OS action (such as an input language change) and if so then sends key down and key up for the special VK_NONE key. This code was moved from internal_keyDownEvent. Having this code execute from the main thread as opposed to the keyboard hook, stops a deadlock with the ignoreInjection hook and stops deliberate reentrancy of the keyboard hook.

Testing performed:

Ran the steps in #9463 locally, confirming the freeze / exit cannot be reproduced. Also provided a try build on that issue and had multiple users confirm the freeze / exit is gone for them.

Known issues with pull request:

None.

Change log entry:

Bug fixes:

…the given script passing it this gesture, by using scriptHandler.executeScript. this is implemented to allow Gesture subclasses to perform actions before / after script execution.

* scriptHandler._queueScriptCallback: call Gesture.executeScript rather than executeScript directly.
* keyboardHandler.KeyboardInputGesture: track the number of times this Gesture instance is sent as input onto the Operating System (I.e. how many times Gesture.send is called). this is recorded on a sendCount instance variable. This is useful to see if a script has actually sent its gesture on or not.
* keyboardHandler.KeyboardInputGesture: implement executeScript, which after executing the script normally, checks to see if the gesture was never actually sent, and that the gesture's modifiers could possibly perform an OS action (such as an input language change) and if so then sends key down and key up for the special VK_NONE key. This code was moved from internal_keyDownEvent. Having this code execute from the main thread as opposed to the keyboard hook, stops a deadlock with the ignoreInjection hook and stops deliberate reentrancy of the keyboard hook.
@michaelDCurran michaelDCurran marked this pull request as ready for review August 10, 2020 18:50
@michaelDCurran michaelDCurran changed the title Alternative 1: No longer freeze or restart when pressing control+shift+downArrow in MS Word No longer freeze or restart when pressing control+shift+downArrow in MS Word Aug 10, 2020
@LeonarddeR

Copy link
Copy Markdown
Collaborator

I'm slightly worried that add-ons call scriptHandler.executeScript directly. Pretty sure NVDA Remote does, may be Braille Extender (@Andre9642) and stuff @JulienCochuyt wrote as well. I'd like to propose the following:

  1. Rename scriptHandler.executeScript to _executeScript. I know it isn't a private method to the scriptHandler, but it at least should be private to NVDA itself.
  2. Make InputGesture execute scriptHandler._executeScript
  3. Create scriptHandler.ExecuteScript and make that execute gesture.ExecuteScript

@michaelDCurran

michaelDCurran commented Aug 11, 2020 via email

Copy link
Copy Markdown
Member Author

Comment thread source/keyboardHandler.py
Comment on lines +329 to +330
#: All normal modifier keys, where modifier vk codes are mapped to a more general modifier vk code
# or C{None} if not applicable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation appears incorrect here, missing a tab?

@LeonarddeR

Copy link
Copy Markdown
Collaborator

NVDA Remote only uses it for braille display gestures, so shouldn't be hurt in that case.

Wonder what @feerrenrut thinks.

@feerrenrut

Copy link
Copy Markdown
Contributor

@LeonarddeR it took me a while to unwind this, and I'm not 100% that I have interpreted correctly, apologies if I'm way off track.

The PR currently has the following call chain: scriptHandler._queueScriptCallback calls gesture.executeScript (keyboard gestures have special behavior) calls scriptHandler.executeScript

Looking at the suggestion:

  1. Rename scriptHandler.executeScript to _executeScript. I know it isn't a private method to the scriptHandler, but it at least should be private to NVDA itself.
  2. Make InputGesture execute scriptHandler._executeScript
  3. Create scriptHandler.ExecuteScript and make that execute gesture.ExecuteScript

If implemented then you have following call chain: addon calls scriptHandler.executeScript calls gesture.ExecuteScript calls scriptHandler._executeScript

I think your concern is about addons who currently call scriptHandler.executeScript directly. They won't get this new behavior in KeyboardInputGesture.executeScript. If this new behavior is important in that case, then there may be a problem. Mick can probably answer this better, but my understanding is that this code is only important when there was actually keyboard input, and we want the application to react to it even though we intercepted it.

If an addon is currently calling scriptHandler.executeScript directly, I don't think anything has changed for them. Sure, they may already have bugs because they AREN'T already doing what KeyboardInputGesture.executeScript introduces in this PR, but that won't be a regression due to this PR. I'm not sure what they should actually do when an addon wants to trigger an action normally only due to keyboard input when there was no keyboard input. I think that is another question.

@LeonarddeR

Copy link
Copy Markdown
Collaborator

@feerrenrut Thanks for summing this all up. I just wanted to make sure that we're not going to break backwards compat with this.

I had a look at brailleExtender, and my suggestion actually introduces a regression, as it calls scriptHandler.executeScript with gesture set to None, so that's definitely not the solution here.
NVDA Remote uses inputCore.manager.executeGesture, not executeScript.
WebAccessForNVDA by Aces Solutions doesn't use executeScript either.

I think it's safest to go with this change as is.

@michaelDCurran michaelDCurran merged commit 9ab0ef8 into master Aug 11, 2020
@michaelDCurran michaelDCurran deleted the i9463-3 branch August 11, 2020 18:31
@nvaccessAuto nvaccessAuto added this to the 2020.3 milestone Aug 11, 2020
michaelDCurran added a commit that referenced this pull request Aug 11, 2020
feerrenrut pushed a commit that referenced this pull request Dec 22, 2020
* keyboardHandler.injectRawKeyboardInput:
Previously, NVDA would send the input manually to its keyboard input hook, and then if the hook didn't handle the input, calling keybd_event. This now avoids assumptions about how NVDA's input hook worked, which are no longer true since #11478 and #11597 were merged.

* Papenmeier braille driver: 
More gesture bindings and fix detection of keyboard input.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NVDA exits automatically in MS Word when working with this document

4 participants