Skip to content

Commit ee20a6c

Browse files
authored
Merge f964501 into fa89f05
2 parents fa89f05 + f964501 commit ee20a6c

7 files changed

Lines changed: 96 additions & 29 deletions

File tree

source/IAccessibleHandler/internalWinEventHandler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def winEventCallback(handle, eventID, window, objectID, childID, threadID, times
177177
f"Adding winEvent to limiter: {getWinEventLogInfo(window, objectID, childID, eventID, threadID)}"
178178
)
179179
if winEventLimiter.addEvent(eventID, window, objectID, childID, threadID):
180-
core.requestPump()
180+
core.requestPump(immediate=eventID == winUser.EVENT_OBJECT_FOCUS)
181181
except Exception:
182182
log.error("winEventCallback", exc_info=True)
183183

source/NVDAObjects/behaviors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ def _monitor(self):
446446
# so ignore it.
447447
del outLines[0]
448448
if outLines:
449-
queueHandler.queueFunction(queueHandler.eventQueue, self._reportNewLines, outLines)
449+
queueHandler.queueFunction(queueHandler.eventQueue, self._reportNewLines, outLines, _immediate=True)
450450
oldText = newText
451451
except:
452452
log.exception("Error getting or calculating new text")

source/core.py

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import os
2222
import time
2323
import ctypes
24+
from enum import Enum
2425
import logHandler
2526
import languageHandler
2627
import globalVars
@@ -54,7 +55,16 @@ def __getattr__(attrName: str) -> Any:
5455
mainThreadId = threading.get_ident()
5556

5657
_pump = None
57-
_isPumpPending = False
58+
59+
60+
class _PumpPending(Enum):
61+
NONE = 0
62+
DELAYED = 1
63+
IMMEDIATE = 2
64+
65+
def __bool__(self):
66+
return self is not self.NONE
67+
5868

5969
_hasShutdownBeenTriggered = False
6070
_shuttingDownFlagLock = threading.Lock()
@@ -705,11 +715,29 @@ def onEndSession(evt):
705715
# Doing this here is a bit ugly, but we don't want these modules imported
706716
# at module level, including wx.
707717
log.debug("Initializing core pump")
708-
class CorePump(gui.NonReEntrantTimer):
718+
719+
class CorePump(wx.Timer):
709720
"Checks the queues and executes functions."
710-
def run(self):
711-
global _isPumpPending
712-
_isPumpPending = False
721+
pending = _PumpPending.NONE
722+
isPumping = False
723+
724+
def request(self):
725+
if self.isPumping:
726+
return # Prevent re-entry.
727+
if self.pending == _PumpPending.IMMEDIATE:
728+
# A delayed pump might have been scheduled. If so, cancel it.
729+
self.Stop()
730+
self.Notify()
731+
elif self.pending == _PumpPending.DELAYED:
732+
self.Start(PUMP_MAX_DELAY, True)
733+
734+
def Notify(self):
735+
if self.isPumping:
736+
log.error("Pumping while already pumping", stack_info=True)
737+
if not self.pending:
738+
log.error("Pumping but pump wasn't pending", stack_info=True)
739+
self.isPumping = True
740+
self.pending = _PumpPending.NONE
713741
watchdog.alive()
714742
try:
715743
if touchHandler.handler:
@@ -725,10 +753,11 @@ def run(self):
725753
log.exception("errors in this core pump cycle")
726754
baseObject.AutoPropertyObject.invalidateCaches()
727755
watchdog.asleep()
728-
if _isPumpPending and not _pump.IsRunning():
756+
self.isPumping = False
757+
if self.pending:
729758
# #3803: Another pump was requested during this pump execution.
730759
# As our pump is not re-entrant, schedule another pump.
731-
_pump.Start(PUMP_MAX_DELAY, True)
760+
self.request()
732761
global _pump
733762
_pump = CorePump()
734763
requestPump()
@@ -834,23 +863,28 @@ def _terminate(module, name=None):
834863
except:
835864
log.exception("Error terminating %s" % name)
836865

837-
def requestPump():
866+
867+
def requestPump(immediate: bool = False):
838868
"""Request a core pump.
839869
This will perform any queued activity.
840-
It is delayed slightly so that queues can implement rate limiting,
841-
filter extraneous events, etc.
870+
@param immediate: If True, the pump will happen as soon as possible. This
871+
should be used where response time is most important; e.g. user input or
872+
focus events.
873+
If False, it is delayed slightly so that queues can implement rate limiting,
874+
filter extraneous events, etc.
842875
"""
843-
global _isPumpPending
844-
if not _pump or _isPumpPending:
845-
return
846-
_isPumpPending = True
847-
if threading.get_ident() == mainThreadId:
848-
_pump.Start(PUMP_MAX_DELAY, True)
876+
if not _pump:
849877
return
850-
# This isn't the main thread. wx timers cannot be run outside the main thread.
851-
# Therefore, Have wx start it in the main thread with a CallAfter.
852-
import wx
853-
wx.CallAfter(_pump.Start,PUMP_MAX_DELAY, True)
878+
# We only need to do something if:
879+
if (
880+
# There is no pending pump.
881+
_pump.pending == _PumpPending.NONE
882+
# There is a pending delayed pump but an immediate pump was just requested.
883+
or (immediate and _pump.pending == _PumpPending.DELAYED)
884+
):
885+
_pump.pending = _PumpPending.IMMEDIATE if immediate else _PumpPending.DELAYED
886+
import wx
887+
wx.CallAfter(_pump.request)
854888

855889

856890
class NVDANotInitializedError(Exception):

source/eventHandler.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,14 @@ def queueEvent(eventName,obj,**kwargs):
4747
_pendingEventCountsByName[eventName]=_pendingEventCountsByName.get(eventName,0)+1
4848
_pendingEventCountsByObj[obj]=_pendingEventCountsByObj.get(obj,0)+1
4949
_pendingEventCountsByNameAndObj[(eventName,obj)]=_pendingEventCountsByNameAndObj.get((eventName,obj),0)+1
50-
queueHandler.queueFunction(queueHandler.eventQueue,_queueEventCallback,eventName,obj,kwargs)
50+
queueHandler.queueFunction(
51+
queueHandler.eventQueue,
52+
_queueEventCallback,
53+
eventName,
54+
obj,
55+
kwargs,
56+
_immediate=eventName == "gainFocus"
57+
)
5158

5259

5360
def _queueEventCallback(eventName,obj,kwargs):

source/inputCore.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ def executeGesture(self, gesture):
506506

507507
speechEffect = gesture.speechEffectWhenExecuted
508508
if speechEffect == gesture.SPEECHEFFECT_CANCEL:
509-
queueHandler.queueFunction(queueHandler.eventQueue, speech.cancelSpeech)
509+
queueHandler.queueFunction(queueHandler.eventQueue, speech.cancelSpeech, _immediate=True)
510510
elif speechEffect in (gesture.SPEECHEFFECT_PAUSE, gesture.SPEECHEFFECT_RESUME):
511511
queueHandler.queueFunction(queueHandler.eventQueue, speech.pauseSpeech, speechEffect == gesture.SPEECHEFFECT_PAUSE)
512512

@@ -529,7 +529,12 @@ def executeGesture(self, gesture):
529529
raise NoInputGestureAction
530530

531531
if config.conf["keyboard"]["speakCommandKeys"] and gesture.shouldReportAsCommand:
532-
queueHandler.queueFunction(queueHandler.eventQueue, speech.speakMessage, gesture.displayName)
532+
queueHandler.queueFunction(
533+
queueHandler.eventQueue,
534+
speech.speakMessage,
535+
gesture.displayName,
536+
_immediate=True
537+
)
533538

534539
gesture.reportExtra()
535540

@@ -562,7 +567,13 @@ def _set_isInputHelpActive(self, enable):
562567

563568
def _inputHelpCaptor(self, gesture):
564569
bypass = gesture.bypassInputHelp or getattr(gesture.script, "bypassInputHelp", False)
565-
queueHandler.queueFunction(queueHandler.eventQueue, self._handleInputHelp, gesture, onlyLog=bypass or not gesture.reportInInputHelp)
570+
queueHandler.queueFunction(
571+
queueHandler.eventQueue,
572+
self._handleInputHelp,
573+
gesture,
574+
onlyLog=bypass or not gesture.reportInInputHelp,
575+
_immediate=True
576+
)
566577
return bypass
567578

568579
def _handleInputHelp(self, gesture, onlyLog=False):

source/queueHandler.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,18 @@ def cancelGeneratorObject(generatorObjID):
3838
except KeyError:
3939
pass
4040

41-
def queueFunction(queue,func,*args,**kwargs):
41+
42+
def queueFunction(queue, func, *args, _immediate: bool = False, **kwargs):
43+
"""Queue a function to be executed in a specific queue.
44+
@param queue: The queue to use. Currently, this can only be
45+
L{queueHandler.eventQueue}.
46+
@param func: The function to run.
47+
@param _immediate: Whether to run this as soon as possible (e.g. input) or
48+
to delay it slightly (e.g. events). See the immediate argument to
49+
L{core.requestPump}.
50+
"""
4251
queue.put_nowait((func,args,kwargs))
43-
core.requestPump()
52+
core.requestPump(immediate=_immediate)
4453

4554
def isRunningGenerators():
4655
res=len(generators)>0

source/scriptHandler.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,13 @@ def queueScript(script,gesture):
249249
_numScriptsQueued+=1
250250
if _isInterceptedCommandScript(script):
251251
_numIncompleteInterceptedCommandScripts+=1
252-
queueHandler.queueFunction(queueHandler.eventQueue,_queueScriptCallback,script,gesture)
252+
queueHandler.queueFunction(
253+
queueHandler.eventQueue,
254+
_queueScriptCallback,
255+
script,
256+
gesture,
257+
_immediate=True
258+
)
253259

254260
def willSayAllResume(gesture):
255261
return (

0 commit comments

Comments
 (0)