mltony / nvda-bluetooth-audio

Bluetooth Audio NVDA add-on improves sound quality when working with bluetooth headphones or speakers.
GNU General Public License v2.0
10 stars 10 forks source link

Unable to use ui.message in another thread #16

Closed CyrilleB79 closed 8 months ago

CyrilleB79 commented 10 months ago

Issue

Consider the following code to paste in NVDA's console

import threading
import ui
def test():
 threading.Thread(target=thread).start()

def thread():
 ui.message("Hello world")

Then, in the console, type: test()

Actual result

With this add-on, an error is logged and the message is not spoken.

Expected result

ui.message should be usable from any thread as when this add-on is not installed in NVDA.

cartertemm commented 9 months ago

@mltony

This issue completely breaks AI Content Describer, and I suspect other add-ons too as reported by three people in the last few days. there are situations where it is necessary to call ui.message or any speech.speak* method from within another thread. However, until this is resolved, users have to basically choose between Bluetooth audio and other pieces of their workflow.

After executing the code above, we get:

ERROR - stderr (11:50:32.891) - Thread-2 (11304):
Exception in thread Thread-2:
Traceback (most recent call last):
  File "threading.pyc", line 926, in _bootstrap_inner
  File "threading.pyc", line 870, in run
  File "C:\Users\cartertemm\AppData\Roaming\nvda\addons\AIContentDescriber\globalPlugins\AIContentDescriber\__init__.py", line 220, in describe_image
    ui.message(_("Retrieving description..."))
  File "ui.pyc", line 132, in message
  File "speech\speech.pyc", line 193, in speakMessage
  File "C:\Users\cartertemm\AppData\Roaming\nvda\addons\bluetoothaudio\globalPlugins\bluetoothAudio.py", line 185, in wrapperFunc
    hookFunc()
  File "C:\Users\cartertemm\AppData\Roaming\nvda\addons\bluetoothaudio\globalPlugins\bluetoothAudio.py", line 66, in resetCounter
    beepThread.play(t, reopen)
  File "C:\Users\cartertemm\AppData\Roaming\nvda\addons\bluetoothaudio\globalPlugins\bluetoothAudio.py", line 165, in play
    self.timer.Start(timerDuration, wx.TIMER_ONE_SHOT)
wx._core.wxAssertionError: C++ assertion "wxThread::IsMain()" failed at ..\..\src\common\timerimpl.cpp(57) in wxTimerImpl::Start(): timer can only be started from the main thread
ERROR - comtypes._comobject.call_without_this (11:50:36.862) - Dummy-1 (2688):
Exception in IUIAutomationEventHandler.HandleAutomationEvent implementation:
Traceback (most recent call last):
  File "comtypes\_comobject.pyc", line 154, in call_without_this
  File "UIAHandler\__init__.pyc", line 714, in IUIAutomationEventHandler_HandleAutomationEvent
  File "comtypes\__init__.pyc", line 278, in __getattr__
  File "monkeyPatches\comtypesMonkeyPatches.pyc", line 32, in __call__
_ctypes.COMError: (-2147220991, 'An event was unable to invoke any of the subscribers', (None, None, None, 0, None))
ERROR - comtypes._comobject.call_without_this (11:50:37.162) - Dummy-1 (2688):
Exception in IUIAutomationEventHandler.HandleAutomationEvent implementation:
Traceback (most recent call last):
  File "comtypes\_comobject.pyc", line 154, in call_without_this
  File "UIAHandler\__init__.pyc", line 714, in IUIAutomationEventHandler_HandleAutomationEvent
  File "comtypes\__init__.pyc", line 278, in __getattr__
  File "monkeyPatches\comtypesMonkeyPatches.pyc", line 32, in __call__
_ctypes.COMError: (-2147220991, 'An event was unable to invoke any of the subscribers', (None, None, None, 0, None))
ERROR - comtypes._comobject.call_without_this (11:50:37.164) - Dummy-1 (2688):
Exception in IUIAutomationEventHandler.HandleAutomationEvent implementation:
Traceback (most recent call last):
  File "comtypes\_comobject.pyc", line 154, in call_without_this
  File "UIAHandler\__init__.pyc", line 714, in IUIAutomationEventHandler_HandleAutomationEvent
  File "comtypes\__init__.pyc", line 278, in __getattr__
  File "monkeyPatches\comtypesMonkeyPatches.pyc", line 32, in __call__
_ctypes.COMError: (-2147220991, 'An event was unable to invoke any of the subscribers', (None, None, None, 0, None))

To fix, we simply need to ensure that the WX timer is always only called on the main thread. In bluetoothAudio.py (line 165):

wx.CallAfter(self.timer.Start, timerDuration, wx.TIMER_ONE_SHOT)

I have created a pull request and confirmed that it solves the problem without introducing others.

Please review and merge at your earliest convenience.