Repository: nvaccess/nvda

Issue:
Task: Fix commands don't work in dialogs or context menu's with wxPython 4.1.1
As reported on issue Upgrade wxPython to 4.1.1 #11056. After updating to wxPython 4.1.1 NVDA commands no longer work when the NVDA menu (a context menu) or a dialog is open. It appears that the main thread can no longer be woken by the background thread using wx. This can be confirmed by executing some commands, then pressing a key that is just passed through eg left arrow, the commands will now run.

Instead we will need to implement an alternative mechanism to wake the main thread. One potential solution would be to look for an appropriate Windows message to send to signal that there is work to be done.


Discussion:
Findings so far:
When in either a popup menu (E.g. the NVDA menu) or a wx Message box (e.g. NVDA's About dialog), No NVDA-specific key commands can execute (E.g. report time NVDA+f12, read current line numpad8 etc). Yet, key commands that are not trapped by NVDA (E.g. pressing downArrow in a menu) still work okay.
Normally, when pressing a key, NVDA's winInputHook thread detects the key press, passes it to keyboardHandler which makes a KeyboardGesture and either finds an NVDA script for it and queues that with scriptHandler.queueScript, or it lets the key through to the application.
The act of calling scripthandler.queueScript calls core.requestPump, which in tern (as the key is handled on a non-main thread) calls wx.CallAfter to request our core pump timer to execute on the main thread (to process our queue, thereby executing scripts etc).
However, In recent versions of wxWidgets, if in a menu or message box, calling CallAfter does not execute the given callback until the menu or message box is dismissed (I.e. the wx main loop is again running). There are some exceptions to this, such as if a key or other input is sent to the application. But of course in the case of trying to execute an NVDA-specific command, NVDA blocks the key from getting to the application and therefore the wx event loop never wakes up.
In wxWidgets, it looks like CallAfter calls QueueEvent, which calls WakeupIdle. 
Further investigation shows that queuing any kind of wx event (command, idle etc) does not wake up the wx event loop.
Next steps are to look more closely at wxWakeupIdle to see what it does / how it has changed in recent wxWidgets versions. 

Resolving patch:
diff --git a/source/gui/__init__.py b/source/gui/__init__.py
--- a/source/gui/__init__.py
+++ b/source/gui/__init__.py
@@ -597,6 +597,21 @@ def initialize():
 		raise RuntimeError("GUI already initialized")
 	mainFrame = MainFrame()
 	wx.GetApp().SetTopWindow(mainFrame)
+	# In wxPython >= 4.1,
+	# wx.CallAfter no longer executes callbacks while NVDA's main thread is within apopup menu or message box.
+	# To work around this,
+	# Monkeypatch wx.CallAfter to
+	# post a WM_NULL message to our top-level window after calling the original CallAfter,
+	# which causes wx's event loop to wake up enough to execute the callback.
+	old_wx_CallAfter = wx.CallAfter
+
+	def wx_CallAfter_wrapper(func, *args, **kwargs):
+		old_wx_CallAfter(func, *args, **kwargs)
+		# mainFrame may be None as NVDA could be terminating.
+		topHandle = mainFrame.Handle if mainFrame else None
+		if topHandle:
+			winUser.PostMessage(topHandle, winUser.WM_NULL, 0, 0)
+	wx.CallAfter = wx_CallAfter_wrapper
 
 def terminate():
 	import brailleViewer


Test patch: