2020import os
2121import time
2222import ctypes
23+ from enum import Enum
2324import logHandler
2425import languageHandler
2526import globalVars
@@ -52,7 +53,16 @@ def __getattr__(attrName: str) -> Any:
5253mainThreadId = 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
866911class NVDANotInitializedError (Exception ):
0 commit comments