Skip to content

Commit 6b6dcb4

Browse files
authored
Merge 18458a2 into a5931dd
2 parents a5931dd + 18458a2 commit 6b6dcb4

8 files changed

Lines changed: 118 additions & 31 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: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import os
2121
import time
2222
import ctypes
23+
from enum import Enum
2324
import logHandler
2425
import languageHandler
2526
import globalVars
@@ -52,7 +53,16 @@ def __getattr__(attrName: str) -> Any:
5253
mainThreadId = threading.get_ident()
5354

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

5767
_hasShutdownBeenTriggered = False
5868
_shuttingDownFlagLock = threading.Lock()
@@ -710,11 +720,37 @@ def onEndSession(evt):
710720
# Doing this here is a bit ugly, but we don't want these modules imported
711721
# at module level, including wx.
712722
log.debug("Initializing core pump")
713-
class CorePump(gui.NonReEntrantTimer):
723+
724+
class CorePump(wx.Timer):
714725
"Checks the queues and executes functions."
715-
def run(self):
716-
global _isPumpPending
717-
_isPumpPending = False
726+
pending = _PumpPending.NONE
727+
isPumping = False
728+
729+
def queueRequest(self):
730+
isMainThread = threading.get_ident() == mainThreadId
731+
if self.pending == _PumpPending.DELAYED and isMainThread:
732+
# We just want to start a timer and we're already on the main thread, so we
733+
# don't need to queue that.
734+
self.processRequest()
735+
return
736+
wx.CallAfter(self.processRequest)
737+
738+
def processRequest(self):
739+
if self.isPumping:
740+
return # Prevent re-entry.
741+
if self.pending == _PumpPending.IMMEDIATE:
742+
# A delayed pump might have been scheduled. If so, cancel it.
743+
self.Stop()
744+
self.Notify()
745+
elif self.pending == _PumpPending.DELAYED:
746+
self.Start(PUMP_MAX_DELAY, True)
747+
748+
def Notify(self):
749+
assert not self.isPumping, "Must not pump while already pumping"
750+
if not self.pending:
751+
log.error("Pumping but pump wasn't pending", stack_info=True)
752+
self.isPumping = True
753+
self.pending = _PumpPending.NONE
718754
watchdog.alive()
719755
try:
720756
if touchHandler.handler:
@@ -730,10 +766,16 @@ def run(self):
730766
log.exception("errors in this core pump cycle")
731767
baseObject.AutoPropertyObject.invalidateCaches()
732768
watchdog.asleep()
733-
if _isPumpPending and not _pump.IsRunning():
734-
# #3803: Another pump was requested during this pump execution.
735-
# As our pump is not re-entrant, schedule another pump.
736-
_pump.Start(PUMP_MAX_DELAY, True)
769+
self.isPumping = False
770+
# #3803: If another pump was requested during this pump execution, we need
771+
# to trigger another pump, as our pump is not re-entrant.
772+
if self.pending == _PumpPending.IMMEDIATE:
773+
# We don't call processRequest directly because we don't want this to
774+
# recurse. Recursing can overflow the stack if there are a flood of
775+
# immediate pumps; e.g. touch exploration.
776+
self.queueRequest()
777+
elif self.pending == _PumpPending.DELAYED:
778+
self.processRequest()
737779
global _pump
738780
_pump = CorePump()
739781
requestPump()
@@ -844,23 +886,26 @@ def isMainThread() -> bool:
844886
return threading.get_ident() == mainThreadId
845887

846888

847-
def requestPump():
889+
def requestPump(immediate: bool = False):
848890
"""Request a core pump.
849891
This will perform any queued activity.
850-
It is delayed slightly so that queues can implement rate limiting,
851-
filter extraneous events, etc.
892+
@param immediate: If True, the pump will happen as soon as possible. This
893+
should be used where response time is most important; e.g. user input or
894+
focus events.
895+
If False, it is delayed slightly so that queues can implement rate limiting,
896+
filter extraneous events, etc.
852897
"""
853-
global _isPumpPending
854-
if not _pump or _isPumpPending:
855-
return
856-
_isPumpPending = True
857-
if threading.get_ident() == mainThreadId:
858-
_pump.Start(PUMP_MAX_DELAY, True)
898+
if not _pump:
859899
return
860-
# This isn't the main thread. wx timers cannot be run outside the main thread.
861-
# Therefore, Have wx start it in the main thread with a CallAfter.
862-
import wx
863-
wx.CallAfter(_pump.Start,PUMP_MAX_DELAY, True)
900+
# We only need to do something if:
901+
if (
902+
# There is no pending pump.
903+
_pump.pending == _PumpPending.NONE
904+
# There is a pending delayed pump but an immediate pump was just requested.
905+
or (immediate and _pump.pending == _PumpPending.DELAYED)
906+
):
907+
_pump.pending = _PumpPending.IMMEDIATE if immediate else _PumpPending.DELAYED
908+
_pump.queueRequest()
864909

865910

866911
class NVDANotInitializedError(Exception):

source/eventHandler.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@ def queueEvent(eventName,obj,**kwargs):
5454
_pendingEventCountsByName[eventName]=_pendingEventCountsByName.get(eventName,0)+1
5555
_pendingEventCountsByObj[obj]=_pendingEventCountsByObj.get(obj,0)+1
5656
_pendingEventCountsByNameAndObj[(eventName,obj)]=_pendingEventCountsByNameAndObj.get((eventName,obj),0)+1
57-
queueHandler.queueFunction(queueHandler.eventQueue,_queueEventCallback,eventName,obj,kwargs)
57+
queueHandler.queueFunction(
58+
queueHandler.eventQueue,
59+
_queueEventCallback,
60+
eventName,
61+
obj,
62+
kwargs,
63+
_immediate=eventName == "gainFocus"
64+
)
5865

5966

6067
def _queueEventCallback(eventName,obj,kwargs):

source/inputCore.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -522,9 +522,10 @@ def executeGesture(self, gesture):
522522
if wasInSayAll:
523523
gesture.wasInSayAll=True
524524

525+
immediate = getattr(gesture, "_immediate", True)
525526
speechEffect = gesture.speechEffectWhenExecuted
526527
if speechEffect == gesture.SPEECHEFFECT_CANCEL:
527-
queueHandler.queueFunction(queueHandler.eventQueue, speech.cancelSpeech)
528+
queueHandler.queueFunction(queueHandler.eventQueue, speech.cancelSpeech, _immediate=immediate)
528529
elif speechEffect in (gesture.SPEECHEFFECT_PAUSE, gesture.SPEECHEFFECT_RESUME):
529530
queueHandler.queueFunction(queueHandler.eventQueue, speech.pauseSpeech, speechEffect == gesture.SPEECHEFFECT_PAUSE)
530531

@@ -547,7 +548,12 @@ def executeGesture(self, gesture):
547548
raise NoInputGestureAction
548549

549550
if config.conf["keyboard"]["speakCommandKeys"] and gesture.shouldReportAsCommand:
550-
queueHandler.queueFunction(queueHandler.eventQueue, speech.speakMessage, gesture.displayName)
551+
queueHandler.queueFunction(
552+
queueHandler.eventQueue,
553+
speech.speakMessage,
554+
gesture.displayName,
555+
_immediate=True
556+
)
551557

552558
gesture.reportExtra()
553559

@@ -580,7 +586,14 @@ def _set_isInputHelpActive(self, enable):
580586

581587
def _inputHelpCaptor(self, gesture):
582588
bypass = gesture.bypassInputHelp or getattr(gesture.script, "bypassInputHelp", False)
583-
queueHandler.queueFunction(queueHandler.eventQueue, self._handleInputHelp, gesture, onlyLog=bypass or not gesture.reportInInputHelp)
589+
immediate = getattr(gesture, "_immediate", True)
590+
queueHandler.queueFunction(
591+
queueHandler.eventQueue,
592+
self._handleInputHelp,
593+
gesture,
594+
onlyLog=bypass or not gesture.reportInInputHelp,
595+
_immediate=immediate
596+
)
584597
return bypass
585598

586599
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=getattr(gesture, "_immediate", True)
258+
)
253259

254260
def willSayAllResume(gesture):
255261
return (

source/touchHandler.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,13 @@ def getDisplayTextForIdentifier(cls, identifier):
199199
source=u"{source}, {mode}".format(source=source,mode=touchModeLabels[mode])
200200
return source,u" + ".join(actions)
201201

202+
def _get__immediate(self):
203+
# Because touch may produce a hover gesture for every pump, an immediate pump
204+
# can result in exhaustion of the window message queue. Thus, don't do
205+
# immediate pumps for hover gestures.
206+
return not self.tracker.action == touchTracker.action_hover
207+
208+
202209
inputCore.registerGestureSource("ts", TouchInputGesture)
203210

204211
class TouchHandler(threading.Thread):

0 commit comments

Comments
 (0)