diff --git a/source/addonStore/install.py b/source/addonStore/install.py index 6e5fe0c7dc2..16cacc226cf 100644 --- a/source/addonStore/install.py +++ b/source/addonStore/install.py @@ -12,6 +12,7 @@ Optional, ) +import systemUtils from logHandler import log from .dataManager import ( @@ -83,7 +84,7 @@ def installAddon(addonPath: PathLike) -> None: prevAddon = _getPreviouslyInstalledAddonById(bundle) try: - installAddonBundle(bundle) + systemUtils.ExecAndPump(installAddonBundle, bundle) if prevAddon: prevAddon.requestRemove() except AddonError: # Handle other exceptions as they are known diff --git a/source/gui/__init__.py b/source/gui/__init__.py index a9357321052..67d11420154 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -5,9 +5,7 @@ # This file is covered by the GNU General Public License. # See the file COPYING for more details. -import time import os -import threading import ctypes import wx import wx.adv @@ -116,6 +114,15 @@ def __getattr__(attrName: str) -> Any: stack_info=True, ) return SettingsPanel + if attrName == "ExecAndPump" and NVDAState._allowDeprecatedAPI(): + log.warning( + "Importing ExecAndPump from here is deprecated. " + "Import ExecAndPump from systemUtils instead. ", + # Include stack info so testers can report warning to add-on author. + stack_info=True, + ) + import systemUtils + return systemUtils.ExecAndPump raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}") @@ -771,38 +778,6 @@ def run(): wx.CallAfter(run) -class ExecAndPump(threading.Thread): - """Executes the given function with given args and kwargs in a background thread while blocking and pumping in the current thread.""" - - def __init__(self,func,*args,**kwargs): - self.func=func - self.args=args - self.kwargs=kwargs - fname = repr(func) - super().__init__( - name=f"{self.__class__.__module__}.{self.__class__.__qualname__}({fname})" - ) - self.threadExc=None - self.start() - time.sleep(0.1) - threadHandle=ctypes.c_int() - threadHandle.value=ctypes.windll.kernel32.OpenThread(0x100000,False,self.ident) - msg=ctypes.wintypes.MSG() - while ctypes.windll.user32.MsgWaitForMultipleObjects(1,ctypes.byref(threadHandle),False,-1,255)==1: - while ctypes.windll.user32.PeekMessageW(ctypes.byref(msg),None,0,0,1): - ctypes.windll.user32.TranslateMessage(ctypes.byref(msg)) - ctypes.windll.user32.DispatchMessageW(ctypes.byref(msg)) - if self.threadExc: - raise self.threadExc - - def run(self): - try: - self.func(*self.args,**self.kwargs) - except Exception as e: - self.threadExc=e - log.debugWarning("task had errors",exc_info=True) - - class IndeterminateProgressDialog(wx.ProgressDialog): def __init__(self, parent: wx.Window, title: str, message: str): diff --git a/source/gui/addonGui.py b/source/gui/addonGui.py index ca7e8027c90..20ba94f16ed 100644 --- a/source/gui/addonGui.py +++ b/source/gui/addonGui.py @@ -20,6 +20,7 @@ from .dpiScalingHelper import DpiScalingHelperMixinWithoutInit import gui.contextHelp import ui +import systemUtils def promptUserForRestart(): @@ -217,7 +218,7 @@ def doneAndDestroy(window): try: # Use context manager to ensure that `done` and `Destroy` are called on the progress dialog afterwards with doneAndDestroy(progressDialog): - gui.ExecAndPump(addonHandler.installAddonBundle, bundle) + systemUtils.ExecAndPump(addonHandler.installAddonBundle, bundle) if prevAddon: from addonStore.dataManager import addonDataManager assert addonDataManager diff --git a/source/gui/installerGui.py b/source/gui/installerGui.py index 28e23c16d8c..911fe0cc40f 100644 --- a/source/gui/installerGui.py +++ b/source/gui/installerGui.py @@ -71,7 +71,7 @@ def doInstall( installedUserConfigPath=config.getInstalledUserConfigPath() if installedUserConfigPath: if _canPortableConfigBeCopied(): - gui.ExecAndPump(installer.copyUserConfig, installedUserConfigPath) + systemUtils.ExecAndPump(installer.copyUserConfig, installedUserConfigPath) except Exception as e: res=e log.error("Failed to execute installer",exc_info=True) @@ -457,7 +457,7 @@ def doCreatePortable( _("Please wait while a portable copy of NVDA is created.") ) try: - gui.ExecAndPump(installer.createPortableCopy, portableDirectory, copyUserConfig) + systemUtils.ExecAndPump(installer.createPortableCopy, portableDirectory, copyUserConfig) except Exception as e: log.error("Failed to create portable copy", exc_info=True) d.done() diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index a16c7f51eb0..3c4f0853319 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -36,6 +36,7 @@ ) import languageHandler import speech +import systemUtils import gui import gui.contextHelp import globalVars @@ -918,7 +919,7 @@ def onCopySettings(self,evt): ) while True: try: - gui.ExecAndPump(config.setSystemConfigToCurrentConfig) + systemUtils.ExecAndPump(config.setSystemConfigToCurrentConfig) res=True break except installer.RetriableFailure: diff --git a/source/systemUtils.py b/source/systemUtils.py index 8fe6a25342c..909e221653f 100644 --- a/source/systemUtils.py +++ b/source/systemUtils.py @@ -6,12 +6,20 @@ """ System related functions.""" import ctypes +import time +import threading +from collections.abc import ( + Callable, +) from ctypes import ( byref, create_unicode_buffer, sizeof, windll, ) +from typing import ( + Any, +) import winKernel import winreg import shellapi @@ -19,6 +27,7 @@ import functools import shlobj from os import startfile +from logHandler import log from NVDAState import WritePaths @@ -194,3 +203,37 @@ def _isSystemClockSecondsVisible() -> bool: return False except OSError: return False + + +class ExecAndPump(threading.Thread): + """Executes the given function with given args and kwargs in a background thread, + while blocking and pumping in the current thread. + """ + + def __init__(self, func: Callable[..., Any], *args, **kwargs) -> None: + self.func = func + self.args = args + self.kwargs = kwargs + fname = repr(func) + super().__init__( + name=f"{self.__class__.__module__}.{self.__class__.__qualname__}({fname})" + ) + self.threadExc: Exception | None = None + self.start() + time.sleep(0.1) + threadHandle = ctypes.c_int() + threadHandle.value = winKernel.kernel32.OpenThread(0x100000, False, self.ident) + msg = ctypes.wintypes.MSG() + while winUser.user32.MsgWaitForMultipleObjects(1, ctypes.byref(threadHandle), False, -1, 255) == 1: + while winUser.user32.PeekMessageW(ctypes.byref(msg), None, 0, 0, 1): + winUser.user32.TranslateMessage(ctypes.byref(msg)) + winUser.user32.DispatchMessageW(ctypes.byref(msg)) + if self.threadExc: + raise self.threadExc + + def run(self): + try: + self.func(*self.args, **self.kwargs) + except Exception as e: + self.threadExc = e + log.debugWarning("task had errors", exc_info=True) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 81bb9bd0cfa..025d61b15b8 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -58,6 +58,7 @@ It is also updated with UIA enabled, when typing text and braille is tethered to - NVDA no longer sometimes freezes when speaking a large amount of text. (#15752, @jcsteh) - More objects which contain useful text are detected, and text content is displayed in braille. (#15605) - When reinstalling an incompatible add-on it is no longer forcefully disabled. (#15584, @lukaszgo1) +- When installing add-ons in the Add-on Store, install prompts are no longer overlapped by the restart dialog. (#15613, @lukaszgo1) - When accessing Microsoft Edge using UI Automation, NVDA is able to activate more controls in browse mode. (#14612) - NVDA no longer freezes briefly when multiple sounds are played in rapid succession. (#15757, @jcsteh) - NVDA will not fail to start anymore when the configuration file is corrupted, but it will restore the configuration to default as it did in the past. (#15690, @CyrilleB79) @@ -218,6 +219,7 @@ Code which imports from one of them, should instead import from the replacement - The ``bdDetect.KEY_*`` constants have been deprecated. Use ``bdDetect.DeviceType.*`` instead. (#15772, @LeonarddeR). - The ``bdDetect.DETECT_USB`` and ``bdDetect.DETECT_BLUETOOTH`` constants have been deprecated with no public replacement. (#15772, @LeonarddeR). +- Using ``gui.ExecAndPump`` is deprecated - please use ``systemUtils.ExecAndPump`` instead. (#15852, @lukaszgo1) -