diff --git a/source/audioDucking.py b/source/audioDucking.py index 106175052f3..d8355c77553 100644 --- a/source/audioDucking.py +++ b/source/audioDucking.py @@ -124,8 +124,13 @@ def _unensureDucked(delay=True): import core if _isDebug(): log.debug("Queuing _unensureDucked") - core.callLater(1000,_unensureDucked,False) - return + try: + core.callLater(1000, _unensureDucked, False) + return + except core.NVDANotInitializedError: + # If the wx.App has not been initialized, audio ducking callbacks + # will fail as they rely on wx.CallLater/wx.CallAfter + log.debugWarning("wx App not initialized, unducking immediately") with _duckingRefCountLock: _duckingRefCount-=1 if _isDebug(): diff --git a/source/core.py b/source/core.py index 221c91abe74..59f144117c1 100644 --- a/source/core.py +++ b/source/core.py @@ -866,13 +866,22 @@ def requestPump(): import wx wx.CallAfter(_pump.Start,PUMP_MAX_DELAY, True) + +class NVDANotInitializedError(Exception): + pass + + def callLater(delay, callable, *args, **kwargs): """Call a callable once after the specified number of milliseconds. As the call is executed within NVDA's core queue, it is possible that execution will take place slightly after the requested time. This function should never be used to execute code that brings up a modal UI as it will cause NVDA's core to block. - This function can be safely called from any thread. + This function can be safely called from any thread once NVDA has been initialized. """ import wx + if wx.GetApp() is None: + # If NVDA has not fully initialized yet, the wxApp may not be initialized. + # wx.CallLater and wx.CallAfter requires the wxApp to be initialized. + raise NVDANotInitializedError("Cannot schedule callable, wx.App is not initialized") if threading.get_ident() == mainThreadId: return wx.CallLater(delay, _callLaterExec, callable, args, kwargs) else: