Attempt to fix bug where speech could not be interrupted during some race conditions.
This commit is contained in:
+37
-15
@@ -196,6 +196,8 @@ class Parser(argparse.ArgumentParser):
|
||||
help=messages.CLI_DEBUG_FILE, metavar=messages.CLI_DEBUG_FILE_NAME)
|
||||
self.add_argument(
|
||||
"--debug", action="store_true", help=messages.CLI_ENABLE_DEBUG)
|
||||
self.add_argument(
|
||||
"--debug-startup", action="store_true", help=messages.CLI_ENABLE_STARTUP_DEBUG)
|
||||
|
||||
self._optionals.title = messages.CLI_OPTIONAL_ARGUMENTS
|
||||
|
||||
@@ -323,6 +325,8 @@ def cleanup(sigval):
|
||||
time.sleep(0.5)
|
||||
|
||||
def main():
|
||||
debug.reset_startup_timing()
|
||||
|
||||
setProcessName('cthulhu')
|
||||
|
||||
parser = Parser()
|
||||
@@ -333,16 +337,26 @@ def main():
|
||||
debug.eventDebugLevel = debug.LEVEL_OFF
|
||||
debug.debugFile = open(args.debug_file, 'w')
|
||||
|
||||
debug.set_startup_timing_enabled(args.debug or args.debug_startup)
|
||||
debug.print_startup_timing("launcher main start")
|
||||
|
||||
if args.replace:
|
||||
cleanup(signal.SIGKILL)
|
||||
with debug.startup_timing("replace cleanup"):
|
||||
cleanup(signal.SIGKILL)
|
||||
|
||||
settingsDict = getattr(args, 'settings', {})
|
||||
|
||||
if not inGraphicalDesktop():
|
||||
with debug.startup_timing("graphical desktop check"):
|
||||
graphicalDesktop = inGraphicalDesktop()
|
||||
if not graphicalDesktop:
|
||||
print(messages.CLI_NO_DESKTOP_ERROR)
|
||||
return 1
|
||||
|
||||
sessionType = getSessionType()
|
||||
with debug.startup_timing("session check"):
|
||||
sessionType = getSessionType()
|
||||
xServerVendor = None
|
||||
if sessionType == "x11":
|
||||
xServerVendor = getXServerVendor()
|
||||
sessionDetails = []
|
||||
xdgSessionType = os.environ.get("XDG_SESSION_TYPE")
|
||||
if xdgSessionType:
|
||||
@@ -355,9 +369,8 @@ def main():
|
||||
display = os.environ.get("DISPLAY")
|
||||
if display:
|
||||
sessionDetails.append(f"DISPLAY={display}")
|
||||
vendor = getXServerVendor()
|
||||
if vendor:
|
||||
sessionDetails.append(f"X server vendor={vendor}")
|
||||
if xServerVendor:
|
||||
sessionDetails.append(f"X server vendor={xServerVendor}")
|
||||
|
||||
if sessionDetails:
|
||||
msg = f"INFO: Session: {sessionType} ({', '.join(sessionDetails)})"
|
||||
@@ -367,7 +380,9 @@ def main():
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "INFO: Preparing to launch.", True)
|
||||
|
||||
debug.print_startup_timing("cthulhu import start")
|
||||
from cthulhu import cthulhu
|
||||
debug.print_startup_timing("cthulhu import complete")
|
||||
manager = cthulhu.cthulhuApp.settingsManager
|
||||
|
||||
if not manager:
|
||||
@@ -375,25 +390,32 @@ def main():
|
||||
return 1
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "INFO: About to activate settings manager.", True)
|
||||
manager.activate(args.user_prefs, settingsDict)
|
||||
with debug.startup_timing("settings manager launcher activation"):
|
||||
manager.activate(args.user_prefs, settingsDict)
|
||||
sys.path.insert(0, manager.getPrefsDir())
|
||||
|
||||
if args.profile:
|
||||
try:
|
||||
manager.setProfile(args.profile)
|
||||
except Exception:
|
||||
print(messages.CLI_LOAD_PROFILE_ERROR % args.profile)
|
||||
manager.setProfile()
|
||||
with debug.startup_timing("profile selection"):
|
||||
try:
|
||||
manager.setProfile(args.profile)
|
||||
except Exception:
|
||||
print(messages.CLI_LOAD_PROFILE_ERROR % args.profile)
|
||||
manager.setProfile()
|
||||
|
||||
if args.setup:
|
||||
cleanup(signal.SIGKILL)
|
||||
cthulhu.showPreferencesGUI()
|
||||
with debug.startup_timing("setup cleanup"):
|
||||
cleanup(signal.SIGKILL)
|
||||
with debug.startup_timing("preferences GUI startup"):
|
||||
cthulhu.showPreferencesGUI()
|
||||
|
||||
if otherCthulhus():
|
||||
with debug.startup_timing("other instance check"):
|
||||
otherInstances = otherCthulhus()
|
||||
if otherInstances:
|
||||
print(messages.CLI_OTHER_CTHULHUS_ERROR)
|
||||
return 1
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "INFO: About to launch Cthulhu.", True)
|
||||
debug.print_startup_timing("cthulhu main handoff")
|
||||
return cthulhu.main()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+153
-105
@@ -402,53 +402,59 @@ def loadUserSettings(script: Optional[Any] = None, inputEvent: Optional[Any] = N
|
||||
"""
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Loading User Settings', True)
|
||||
debug.print_startup_timing("load user settings start")
|
||||
|
||||
global _userSettings
|
||||
|
||||
# Shutdown the output drivers and give them a chance to die.
|
||||
|
||||
player = sound.getPlayer()
|
||||
player.shutdown()
|
||||
speech.shutdown()
|
||||
braille.shutdown()
|
||||
with debug.startup_timing("output driver shutdown"):
|
||||
player = sound.getPlayer()
|
||||
player.shutdown()
|
||||
speech.shutdown()
|
||||
braille.shutdown()
|
||||
|
||||
cthulhuApp.scriptManager.deactivate()
|
||||
cthulhuApp.getSignalManager().emitSignal('load-setting-begin')
|
||||
with debug.startup_timing("script manager deactivate for settings load"):
|
||||
cthulhuApp.scriptManager.deactivate()
|
||||
cthulhuApp.getSignalManager().emitSignal('load-setting-begin')
|
||||
|
||||
reloaded: bool = False
|
||||
if _userSettings:
|
||||
_profile = cthulhuApp.settingsManager.getSetting('activeProfile')[1]
|
||||
try:
|
||||
_userSettings = cthulhuApp.settingsManager.getGeneralSettings(_profile)
|
||||
cthulhuApp.settingsManager.setProfile(_profile)
|
||||
reloaded = True
|
||||
except ImportError:
|
||||
debug.printException(debug.LEVEL_INFO)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_SEVERE)
|
||||
else:
|
||||
_profile = cthulhuApp.settingsManager.profile
|
||||
try:
|
||||
_userSettings = cthulhuApp.settingsManager.getGeneralSettings(_profile)
|
||||
except ImportError:
|
||||
debug.printException(debug.LEVEL_INFO)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_SEVERE)
|
||||
with debug.startup_timing("settings profile load"):
|
||||
if _userSettings:
|
||||
_profile = cthulhuApp.settingsManager.getSetting('activeProfile')[1]
|
||||
try:
|
||||
_userSettings = cthulhuApp.settingsManager.getGeneralSettings(_profile)
|
||||
cthulhuApp.settingsManager.setProfile(_profile)
|
||||
reloaded = True
|
||||
except ImportError:
|
||||
debug.printException(debug.LEVEL_INFO)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_SEVERE)
|
||||
else:
|
||||
_profile = cthulhuApp.settingsManager.profile
|
||||
try:
|
||||
_userSettings = cthulhuApp.settingsManager.getGeneralSettings(_profile)
|
||||
except ImportError:
|
||||
debug.printException(debug.LEVEL_INFO)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_SEVERE)
|
||||
|
||||
if not script:
|
||||
script = cthulhuApp.scriptManager.get_default_script()
|
||||
|
||||
cthulhuApp.settingsManager.loadAppSettings(script)
|
||||
with debug.startup_timing("app settings load"):
|
||||
cthulhuApp.settingsManager.loadAppSettings(script)
|
||||
|
||||
if cthulhuApp.settingsManager.getSetting('enableSpeech'):
|
||||
msg = 'CTHULHU: About to enable speech'
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
try:
|
||||
speech.init()
|
||||
if reloaded and not skipReloadMessage:
|
||||
script.speakMessage(messages.SETTINGS_RELOADED)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_SEVERE)
|
||||
with debug.startup_timing("speech initialization"):
|
||||
try:
|
||||
speech.init()
|
||||
if reloaded and not skipReloadMessage:
|
||||
script.speakMessage(messages.SETTINGS_RELOADED)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_SEVERE)
|
||||
else:
|
||||
msg = 'CTHULHU: Speech is not enabled in settings'
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
@@ -456,31 +462,36 @@ def loadUserSettings(script: Optional[Any] = None, inputEvent: Optional[Any] = N
|
||||
if cthulhuApp.settingsManager.getSetting('enableBraille'):
|
||||
msg = 'CTHULHU: About to enable braille'
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
try:
|
||||
braille.init(_processBrailleEvent)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_WARNING)
|
||||
msg = 'CTHULHU: Could not initialize connection to braille.'
|
||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
||||
with debug.startup_timing("braille initialization"):
|
||||
try:
|
||||
braille.init(_processBrailleEvent)
|
||||
except Exception:
|
||||
debug.printException(debug.LEVEL_WARNING)
|
||||
msg = 'CTHULHU: Could not initialize connection to braille.'
|
||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
||||
else:
|
||||
msg = 'CTHULHU: Braille is not enabled in settings'
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
|
||||
if cthulhuApp.settingsManager.getSetting('enableMouseReview'):
|
||||
mouse_review.getReviewer().activate()
|
||||
else:
|
||||
mouse_review.getReviewer().deactivate()
|
||||
with debug.startup_timing("mouse review update"):
|
||||
if cthulhuApp.settingsManager.getSetting('enableMouseReview'):
|
||||
mouse_review.getReviewer().activate()
|
||||
else:
|
||||
mouse_review.getReviewer().deactivate()
|
||||
|
||||
if cthulhuApp.settingsManager.getSetting('enableSound'):
|
||||
player.init()
|
||||
with debug.startup_timing("sound initialization"):
|
||||
player.init()
|
||||
|
||||
cthulhu_modifier_manager.getManager().refreshCthulhuModifiers("Loading user settings.")
|
||||
with debug.startup_timing("cthulhu modifier refresh"):
|
||||
cthulhu_modifier_manager.getManager().refreshCthulhuModifiers("Loading user settings.")
|
||||
|
||||
# Activate core systems FIRST before loading plugins
|
||||
cthulhuApp.compositorStateAdapter.activate()
|
||||
cthulhuApp.scriptManager.activate()
|
||||
cthulhuApp.eventManager.activate()
|
||||
with debug.startup_timing("core manager activation"):
|
||||
cthulhuApp.compositorStateAdapter.activate()
|
||||
cthulhuApp.scriptManager.activate()
|
||||
cthulhuApp.eventManager.activate()
|
||||
|
||||
cthulhuApp.getSignalManager().emitSignal('load-setting-begin')
|
||||
|
||||
@@ -498,14 +509,17 @@ def loadUserSettings(script: Optional[Any] = None, inputEvent: Optional[Any] = N
|
||||
)
|
||||
activePlugins = list(activePluginsSetting)
|
||||
debug.printMessage(debug.LEVEL_INFO, f'CTHULHU: Loading active plugins: {activePlugins}', True)
|
||||
cthulhuApp.getPluginSystemManager().setActivePlugins(activePlugins)
|
||||
with debug.startup_timing("active plugin load"):
|
||||
cthulhuApp.getPluginSystemManager().setActivePlugins(activePlugins)
|
||||
|
||||
# Refresh keybindings to include plugin bindings (after script manager is active)
|
||||
cthulhuApp.getPluginSystemManager().refresh_active_script_keybindings()
|
||||
with debug.startup_timing("plugin keybinding refresh"):
|
||||
cthulhuApp.getPluginSystemManager().refresh_active_script_keybindings()
|
||||
|
||||
cthulhuApp.getSignalManager().emitSignal('load-setting-completed')
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: User Settings Loaded', True)
|
||||
debug.print_startup_timing("load user settings complete")
|
||||
|
||||
return True
|
||||
|
||||
@@ -612,6 +626,7 @@ def init() -> bool:
|
||||
"""
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Initializing', True)
|
||||
debug.print_startup_timing("cthulhu init start")
|
||||
|
||||
global _initialized
|
||||
|
||||
@@ -627,15 +642,18 @@ def init() -> bool:
|
||||
|
||||
# Activate settings manager before loading user settings
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Activating settings manager', True)
|
||||
cthulhuApp.settingsManager.activate()
|
||||
with debug.startup_timing("settings manager init activation"):
|
||||
cthulhuApp.settingsManager.activate()
|
||||
|
||||
loadUserSettings()
|
||||
with debug.startup_timing("load user settings"):
|
||||
loadUserSettings()
|
||||
|
||||
if settings.timeoutCallback and (settings.timeoutTime > 0):
|
||||
signal.alarm(0)
|
||||
|
||||
_initialized = True
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Initialized', True)
|
||||
debug.print_startup_timing("cthulhu init complete")
|
||||
|
||||
return True
|
||||
|
||||
@@ -672,6 +690,7 @@ def start() -> None:
|
||||
"""Starts Cthulhu."""
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Starting', True)
|
||||
debug.print_startup_timing("cthulhu start begin")
|
||||
|
||||
if not _initialized:
|
||||
init()
|
||||
@@ -689,21 +708,25 @@ def start() -> None:
|
||||
# Used to re-create the Xmodmap when a new keyboard is plugged in.
|
||||
# Necessary, because plugging in a new keyboard resets the Xmodmap
|
||||
# and stomps our changes
|
||||
display = Gdk.Display.get_default()
|
||||
devmanager=display.get_device_manager()
|
||||
devmanager.connect("device-added", deviceChangeHandler)
|
||||
devmanager.connect("device-removed", deviceChangeHandler)
|
||||
with debug.startup_timing("input device monitor setup"):
|
||||
display = Gdk.Display.get_default()
|
||||
devmanager = display.get_device_manager()
|
||||
devmanager.connect("device-added", deviceChangeHandler)
|
||||
devmanager.connect("device-removed", deviceChangeHandler)
|
||||
|
||||
Gdk.notify_startup_complete()
|
||||
with debug.startup_timing("desktop startup notification"):
|
||||
Gdk.notify_startup_complete()
|
||||
msg = 'CTHULHU: Startup complete notification made'
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Starting Atspi main event loop', True)
|
||||
|
||||
# Start D-Bus remote controller service after ATSPI is ready
|
||||
cthulhuApp.getMakoNotificationMonitor().start()
|
||||
GObject.idle_add(_start_dbus_service)
|
||||
with debug.startup_timing("startup idle services"):
|
||||
cthulhuApp.getMakoNotificationMonitor().start()
|
||||
GObject.idle_add(_start_dbus_service)
|
||||
|
||||
debug.print_startup_timing("AT-SPI main loop start")
|
||||
Atspi.event_main()
|
||||
|
||||
def die(exitCode: int = 1) -> None:
|
||||
@@ -851,11 +874,13 @@ def main() -> int:
|
||||
if session:
|
||||
msg += f" session: {session}"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
debug.print_startup_timing("cthulhu main start")
|
||||
|
||||
if debug.debugFile and os.path.exists(debug.debugFile.name):
|
||||
faulthandler.enable(file=debug.debugFile, all_threads=True)
|
||||
else:
|
||||
faulthandler.enable(all_threads=False)
|
||||
with debug.startup_timing("faulthandler setup"):
|
||||
if debug.debugFile and os.path.exists(debug.debugFile.name):
|
||||
faulthandler.enable(file=debug.debugFile, all_threads=True)
|
||||
else:
|
||||
faulthandler.enable(all_threads=False)
|
||||
|
||||
# Method to call when we think something might be hung.
|
||||
#
|
||||
@@ -863,53 +888,59 @@ def main() -> int:
|
||||
|
||||
# Various signal handlers we want to listen for.
|
||||
#
|
||||
signal.signal(signal.SIGHUP, shutdownOnSignal)
|
||||
signal.signal(signal.SIGINT, shutdownOnSignal)
|
||||
signal.signal(signal.SIGTERM, shutdownOnSignal)
|
||||
signal.signal(signal.SIGQUIT, shutdownOnSignal)
|
||||
signal.signal(signal.SIGSEGV, crashOnSignal)
|
||||
with debug.startup_timing("signal handler setup"):
|
||||
signal.signal(signal.SIGHUP, shutdownOnSignal)
|
||||
signal.signal(signal.SIGINT, shutdownOnSignal)
|
||||
signal.signal(signal.SIGTERM, shutdownOnSignal)
|
||||
signal.signal(signal.SIGQUIT, shutdownOnSignal)
|
||||
signal.signal(signal.SIGSEGV, crashOnSignal)
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "CTHULHU: Enabling accessibility (if needed).", True)
|
||||
if not cthulhuApp.settingsManager.isAccessibilityEnabled():
|
||||
cthulhuApp.settingsManager.setAccessibility(True)
|
||||
with debug.startup_timing("accessibility enable check"):
|
||||
if not cthulhuApp.settingsManager.isAccessibilityEnabled():
|
||||
cthulhuApp.settingsManager.setAccessibility(True)
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "CTHULHU: Initializing.", True)
|
||||
init()
|
||||
with debug.startup_timing("cthulhu init"):
|
||||
init()
|
||||
debug.printMessage(debug.LEVEL_INFO, "CTHULHU: Initialized.", True)
|
||||
|
||||
script = cthulhu_state.activeScript
|
||||
sound_theme_manager.getManager().playStartSound(wait=False)
|
||||
soundFailureReason = sound.getSoundSystemFailureReason()
|
||||
with debug.startup_timing("startup sound"):
|
||||
sound_theme_manager.getManager().playStartSound(wait=False)
|
||||
soundFailureReason = sound.getSoundSystemFailureReason()
|
||||
if soundFailureReason:
|
||||
debug.printMessage(
|
||||
debug.LEVEL_INFO,
|
||||
f"CTHULHU: Startup sound failed. Continuing without sound. Reason: {soundFailureReason}",
|
||||
True
|
||||
)
|
||||
cthulhuApp.getSignalManager().emitSignal('start-application-completed')
|
||||
if script:
|
||||
window = script.utilities.activeWindow()
|
||||
with debug.startup_timing("startup focus recovery"):
|
||||
cthulhuApp.getSignalManager().emitSignal('start-application-completed')
|
||||
if script:
|
||||
window = script.utilities.activeWindow()
|
||||
|
||||
if window and not cthulhu_state.locusOfFocus:
|
||||
app = AXObject.get_application(window)
|
||||
setActiveWindow(window, app, alsoSetLocusOfFocus=True, notifyScript=True)
|
||||
if window and not cthulhu_state.locusOfFocus:
|
||||
app = AXObject.get_application(window)
|
||||
setActiveWindow(window, app, alsoSetLocusOfFocus=True, notifyScript=True)
|
||||
|
||||
# setActiveWindow does some corrective work needed thanks to
|
||||
# mutter-x11-frames. So retrieve the window just in case.
|
||||
window = cthulhu_state.activeWindow
|
||||
cthulhuApp.scriptManager.activate_script_for_context(app, window, "startup: launch")
|
||||
# setActiveWindow does some corrective work needed thanks to
|
||||
# mutter-x11-frames. So retrieve the window just in case.
|
||||
window = cthulhu_state.activeWindow
|
||||
cthulhuApp.scriptManager.activate_script_for_context(app, window, "startup: launch")
|
||||
|
||||
focusedObject = AXUtilities.get_focused_object(window)
|
||||
tokens = ["CTHULHU: Focused object is:", focusedObject]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
if focusedObject:
|
||||
setLocusOfFocus(None, focusedObject)
|
||||
cthulhuApp.scriptManager.activate_script_for_context(
|
||||
AXObject.get_application(focusedObject), focusedObject, "startup: focused-object")
|
||||
focusedObject = AXUtilities.get_focused_object(window)
|
||||
tokens = ["CTHULHU: Focused object is:", focusedObject]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
if focusedObject:
|
||||
setLocusOfFocus(None, focusedObject)
|
||||
cthulhuApp.scriptManager.activate_script_for_context(
|
||||
AXObject.get_application(focusedObject), focusedObject, "startup: focused-object")
|
||||
|
||||
try:
|
||||
msg = "CTHULHU: Starting ATSPI registry."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
debug.print_startup_timing("AT-SPI registry start")
|
||||
start() # waits until we stop the registry
|
||||
except Exception:
|
||||
msg = "CTHULHU: Exception starting ATSPI registry."
|
||||
@@ -930,27 +961,44 @@ class Cthulhu(GObject.Object):
|
||||
"active-script-changed": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT,)), # New signal to indicate active script change
|
||||
}
|
||||
def __init__(self) -> None:
|
||||
debug.print_startup_timing("cthulhuApp construction start")
|
||||
GObject.Object.__init__(self)
|
||||
# add members
|
||||
self.resourceManager: resource_manager.ResourceManager = resource_manager.ResourceManager(self)
|
||||
self.settingsManager: SettingsManager = settings_manager.SettingsManager(self) # Directly instantiate
|
||||
self.eventManager: EventManager = event_manager.EventManager(self) # Directly instantiate
|
||||
self.compositorStateAdapter: CompositorStateAdapter = compositor_state_adapter.CompositorStateAdapter()
|
||||
self.eventManager.set_compositor_state_adapter(self.compositorStateAdapter)
|
||||
self.scriptManager: ScriptManager = script_manager.ScriptManager(self) # Directly instantiate
|
||||
script_manager._manager = self.scriptManager
|
||||
self.logger: logger.Logger = logger.Logger() # Directly instantiate
|
||||
self.signalManager: SignalManager = signal_manager.SignalManager(self)
|
||||
self.dynamicApiManager: DynamicApiManager = dynamic_api_manager.DynamicApiManager(self)
|
||||
self.translationManager: TranslationManager = translation_manager.TranslationManager(self)
|
||||
with debug.startup_timing("resource manager construction"):
|
||||
self.resourceManager: resource_manager.ResourceManager = resource_manager.ResourceManager(self)
|
||||
with debug.startup_timing("settings manager construction"):
|
||||
self.settingsManager: SettingsManager = settings_manager.SettingsManager(self)
|
||||
with debug.startup_timing("event manager construction"):
|
||||
self.eventManager: EventManager = event_manager.EventManager(self)
|
||||
with debug.startup_timing("compositor adapter construction"):
|
||||
self.compositorStateAdapter: CompositorStateAdapter = \
|
||||
compositor_state_adapter.CompositorStateAdapter()
|
||||
self.eventManager.set_compositor_state_adapter(self.compositorStateAdapter)
|
||||
with debug.startup_timing("script manager construction"):
|
||||
self.scriptManager: ScriptManager = script_manager.ScriptManager(self)
|
||||
script_manager._manager = self.scriptManager
|
||||
with debug.startup_timing("logger construction"):
|
||||
self.logger: logger.Logger = logger.Logger()
|
||||
with debug.startup_timing("signal manager construction"):
|
||||
self.signalManager: SignalManager = signal_manager.SignalManager(self)
|
||||
with debug.startup_timing("dynamic API manager construction"):
|
||||
self.dynamicApiManager: DynamicApiManager = dynamic_api_manager.DynamicApiManager(self)
|
||||
with debug.startup_timing("translation manager construction"):
|
||||
self.translationManager: TranslationManager = translation_manager.TranslationManager(self)
|
||||
self.debugManager: Any = debug
|
||||
self.APIHelper: APIHelper = APIHelper(self)
|
||||
self.makoNotificationMonitor: mako_notification_monitor.MakoNotificationMonitor = \
|
||||
mako_notification_monitor.MakoNotificationMonitor(self)
|
||||
self.createCompatAPI()
|
||||
self.pluginSystemManager: PluginSystemManager = plugin_system_manager.PluginSystemManager(self)
|
||||
with debug.startup_timing("API helper construction"):
|
||||
self.APIHelper: APIHelper = APIHelper(self)
|
||||
with debug.startup_timing("notification monitor construction"):
|
||||
self.makoNotificationMonitor: mako_notification_monitor.MakoNotificationMonitor = \
|
||||
mako_notification_monitor.MakoNotificationMonitor(self)
|
||||
with debug.startup_timing("compat API registration"):
|
||||
self.createCompatAPI()
|
||||
with debug.startup_timing("plugin manager construction"):
|
||||
self.pluginSystemManager: PluginSystemManager = plugin_system_manager.PluginSystemManager(self)
|
||||
# Scan for available plugins at startup
|
||||
self.pluginSystemManager.rescanPlugins()
|
||||
with debug.startup_timing("plugin scan"):
|
||||
self.pluginSystemManager.rescanPlugins()
|
||||
debug.print_startup_timing("cthulhuApp construction complete")
|
||||
|
||||
def getAPIHelper(self) -> APIHelper:
|
||||
return self.APIHelper
|
||||
|
||||
@@ -154,6 +154,8 @@ class Parser(argparse.ArgumentParser):
|
||||
help=messages.CLI_DEBUG_FILE, metavar=messages.CLI_DEBUG_FILE_NAME)
|
||||
self.add_argument(
|
||||
"--debug", action="store_true", help=messages.CLI_ENABLE_DEBUG)
|
||||
self.add_argument(
|
||||
"--debug-startup", action="store_true", help=messages.CLI_ENABLE_STARTUP_DEBUG)
|
||||
|
||||
self._optionals.title = messages.CLI_OPTIONAL_ARGUMENTS
|
||||
|
||||
@@ -281,6 +283,8 @@ def cleanup(sigval):
|
||||
time.sleep(0.5)
|
||||
|
||||
def main():
|
||||
debug.reset_startup_timing()
|
||||
|
||||
setProcessName('cthulhu')
|
||||
|
||||
parser = Parser()
|
||||
@@ -291,16 +295,26 @@ def main():
|
||||
debug.eventDebugLevel = debug.LEVEL_OFF
|
||||
debug.debugFile = open(args.debug_file, 'w')
|
||||
|
||||
debug.set_startup_timing_enabled(args.debug or args.debug_startup)
|
||||
debug.print_startup_timing("launcher main start")
|
||||
|
||||
if args.replace:
|
||||
cleanup(signal.SIGKILL)
|
||||
with debug.startup_timing("replace cleanup"):
|
||||
cleanup(signal.SIGKILL)
|
||||
|
||||
settingsDict = getattr(args, 'settings', {})
|
||||
|
||||
if not inGraphicalDesktop():
|
||||
with debug.startup_timing("graphical desktop check"):
|
||||
graphicalDesktop = inGraphicalDesktop()
|
||||
if not graphicalDesktop:
|
||||
print(messages.CLI_NO_DESKTOP_ERROR)
|
||||
return 1
|
||||
|
||||
sessionType = getSessionType()
|
||||
with debug.startup_timing("session check"):
|
||||
sessionType = getSessionType()
|
||||
xServerVendor = None
|
||||
if sessionType == "x11":
|
||||
xServerVendor = getXServerVendor()
|
||||
sessionDetails = []
|
||||
xdgSessionType = os.environ.get("XDG_SESSION_TYPE")
|
||||
if xdgSessionType:
|
||||
@@ -313,9 +327,8 @@ def main():
|
||||
display = os.environ.get("DISPLAY")
|
||||
if display:
|
||||
sessionDetails.append(f"DISPLAY={display}")
|
||||
vendor = getXServerVendor()
|
||||
if vendor:
|
||||
sessionDetails.append(f"X server vendor={vendor}")
|
||||
if xServerVendor:
|
||||
sessionDetails.append(f"X server vendor={xServerVendor}")
|
||||
|
||||
if sessionDetails:
|
||||
msg = f"INFO: Session: {sessionType} ({', '.join(sessionDetails)})"
|
||||
@@ -325,7 +338,9 @@ def main():
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "INFO: Preparing to launch.", True)
|
||||
|
||||
debug.print_startup_timing("cthulhu import start")
|
||||
from cthulhu import cthulhu
|
||||
debug.print_startup_timing("cthulhu import complete")
|
||||
manager = cthulhu.cthulhuApp.settingsManager
|
||||
|
||||
if not manager:
|
||||
@@ -333,25 +348,32 @@ def main():
|
||||
return 1
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "INFO: About to activate settings manager.", True)
|
||||
manager.activate(args.user_prefs, settingsDict)
|
||||
with debug.startup_timing("settings manager launcher activation"):
|
||||
manager.activate(args.user_prefs, settingsDict)
|
||||
sys.path.insert(0, manager.getPrefsDir())
|
||||
|
||||
if args.profile:
|
||||
try:
|
||||
manager.setProfile(args.profile)
|
||||
except Exception:
|
||||
print(messages.CLI_LOAD_PROFILE_ERROR % args.profile)
|
||||
manager.setProfile()
|
||||
with debug.startup_timing("profile selection"):
|
||||
try:
|
||||
manager.setProfile(args.profile)
|
||||
except Exception:
|
||||
print(messages.CLI_LOAD_PROFILE_ERROR % args.profile)
|
||||
manager.setProfile()
|
||||
|
||||
if args.setup:
|
||||
cleanup(signal.SIGKILL)
|
||||
cthulhu.showPreferencesGUI()
|
||||
with debug.startup_timing("setup cleanup"):
|
||||
cleanup(signal.SIGKILL)
|
||||
with debug.startup_timing("preferences GUI startup"):
|
||||
cthulhu.showPreferencesGUI()
|
||||
|
||||
if otherCthulhus():
|
||||
with debug.startup_timing("other instance check"):
|
||||
otherInstances = otherCthulhus()
|
||||
if otherInstances:
|
||||
print(messages.CLI_OTHER_CTHULHUS_ERROR)
|
||||
return 1
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "INFO: About to launch Cthulhu.", True)
|
||||
debug.print_startup_timing("cthulhu main handoff")
|
||||
return cthulhu.main()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+74
-1
@@ -37,13 +37,15 @@ __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
|
||||
__license__ = "LGPL"
|
||||
|
||||
import inspect
|
||||
from contextlib import contextmanager
|
||||
import traceback
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import types
|
||||
from typing import Any, Optional, TextIO, Pattern, TYPE_CHECKING
|
||||
from typing import Any, Generator, Optional, TextIO, Pattern, TYPE_CHECKING
|
||||
from datetime import datetime
|
||||
|
||||
import gi
|
||||
@@ -149,6 +151,13 @@ eventDebugFilter: Optional[Pattern[str]] = None
|
||||
#
|
||||
debugEventQueue: bool = False
|
||||
|
||||
# Startup timing is intentionally separate from normal event tracing. When
|
||||
# explicitly enabled, it can print concise phase timings without turning on the
|
||||
# full debug stream.
|
||||
startupTimingEnabled: bool = False
|
||||
_startupTimingStartTime: Optional[float] = None
|
||||
_startupTimingLastTime: Optional[float] = None
|
||||
|
||||
# What module(s) should be traced if traceit is being used. By default
|
||||
# we'll just attend to ourself. (And by default, we will not enable
|
||||
# traceit.) Note that enabling this functionality will drag your system
|
||||
@@ -282,6 +291,70 @@ def print_log_tokens(level: int, prefix: str, tokens: list[Any], reason: Optiona
|
||||
text = format_log_message(prefix, _format_tokens(tokens), reason)
|
||||
printMessage(level, text, timestamp, stack)
|
||||
|
||||
def reset_startup_timing() -> None:
|
||||
"""Reset the startup timing baseline."""
|
||||
|
||||
global _startupTimingStartTime
|
||||
global _startupTimingLastTime
|
||||
|
||||
now = time.monotonic()
|
||||
_startupTimingStartTime = now
|
||||
_startupTimingLastTime = now
|
||||
|
||||
def set_startup_timing_enabled(enabled: bool = True) -> None:
|
||||
"""Enable or disable concise startup phase timing output."""
|
||||
|
||||
global startupTimingEnabled
|
||||
|
||||
startupTimingEnabled = enabled
|
||||
if enabled and _startupTimingStartTime is None:
|
||||
reset_startup_timing()
|
||||
|
||||
def is_startup_timing_enabled() -> bool:
|
||||
"""Returns True when startup timing output should be emitted."""
|
||||
|
||||
return startupTimingEnabled or debugLevel <= LEVEL_INFO
|
||||
|
||||
def _startup_timing_level() -> int:
|
||||
if debugLevel <= LEVEL_INFO:
|
||||
return LEVEL_INFO
|
||||
return LEVEL_SEVERE
|
||||
|
||||
def print_startup_timing(phase: str) -> None:
|
||||
"""Print elapsed startup timing for a named phase."""
|
||||
|
||||
global _startupTimingStartTime
|
||||
global _startupTimingLastTime
|
||||
|
||||
if not is_startup_timing_enabled():
|
||||
return
|
||||
|
||||
now = time.monotonic()
|
||||
if _startupTimingStartTime is None or _startupTimingLastTime is None:
|
||||
_startupTimingStartTime = now
|
||||
_startupTimingLastTime = now
|
||||
|
||||
delta = now - _startupTimingLastTime
|
||||
total = now - _startupTimingStartTime
|
||||
_startupTimingLastTime = now
|
||||
|
||||
text = f"STARTUP TIMING: {phase}: +{delta:.3f}s, total {total:.3f}s"
|
||||
printMessage(_startup_timing_level(), text, True)
|
||||
|
||||
@contextmanager
|
||||
def startup_timing(phase: str) -> Generator[None, None, None]:
|
||||
"""Context manager for timing a startup phase."""
|
||||
|
||||
if not is_startup_timing_enabled():
|
||||
yield
|
||||
return
|
||||
|
||||
print_startup_timing(f"{phase} start")
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
print_startup_timing(f"{phase} end")
|
||||
|
||||
def printTokens(level: int, tokens: list[Any], timestamp: bool = False, stack: bool = False) -> None:
|
||||
if level < debugLevel:
|
||||
return
|
||||
|
||||
@@ -293,6 +293,11 @@ CLI_HELP = _("Show this help message and exit")
|
||||
# start with 'debug' and end with '.out', regardless of the locale.).
|
||||
CLI_ENABLE_DEBUG = _("Send debug output to debug-YYYY-MM-DD-HH:MM:SS.out")
|
||||
|
||||
# Translators: This is the description of command line option '--debug-startup'
|
||||
# which causes Cthulhu to print concise startup timing information without
|
||||
# enabling the full debug output stream.
|
||||
CLI_ENABLE_STARTUP_DEBUG = _("Send concise startup timing output")
|
||||
|
||||
# Translators: This is the description of command line option '--debug-file'
|
||||
# which allows the user to override the default date-based name of the debugging
|
||||
# output file.
|
||||
|
||||
@@ -545,31 +545,40 @@ class SpeechServer(speechserver.SpeechServer):
|
||||
- utteranceIterator: Iterator yielding [SayAllContext, acss] tuples
|
||||
- progressCallback: Called with progress updates
|
||||
"""
|
||||
GLib.idle_add(self._sayAllWorker, utteranceIterator, progressCallback)
|
||||
with self._lock:
|
||||
self._stopEvent.clear()
|
||||
self._speakGeneration += 1
|
||||
generation = self._speakGeneration
|
||||
GLib.idle_add(self._sayAllWorker, utteranceIterator, progressCallback, generation)
|
||||
|
||||
def _sayAllWorker(self, iterator, callback):
|
||||
def _sayAllWorker(self, iterator, callback, generation):
|
||||
"""Process one utterance at a time (called via GLib.idle_add).
|
||||
|
||||
Arguments:
|
||||
- iterator: Utterance iterator
|
||||
- callback: Progress callback
|
||||
"""
|
||||
with self._lock:
|
||||
if self._stopEvent.is_set() or generation != self._speakGeneration:
|
||||
return False
|
||||
|
||||
try:
|
||||
context, acss = next(iterator)
|
||||
except StopIteration:
|
||||
return False
|
||||
|
||||
def onComplete():
|
||||
with self._lock:
|
||||
if self._stopEvent.is_set() or generation != self._speakGeneration:
|
||||
return False
|
||||
context.currentOffset = context.endOffset
|
||||
callback(context.copy(), speechserver.SayAllContext.COMPLETED)
|
||||
GLib.idle_add(self._sayAllWorker, iterator, callback)
|
||||
GLib.idle_add(self._sayAllWorker, iterator, callback, generation)
|
||||
return False
|
||||
|
||||
context.currentOffset = context.startOffset
|
||||
callback(context.copy(), speechserver.SayAllContext.PROGRESS)
|
||||
|
||||
with self._lock:
|
||||
self._stopEvent.clear()
|
||||
generation = self._speakGeneration
|
||||
self._currentFuture = self._executor.submit(
|
||||
self._synthesizeAndPlay, context.utterance, acss, onComplete, generation
|
||||
)
|
||||
|
||||
@@ -165,6 +165,8 @@ class SpeechServer(speechserver.SpeechServer):
|
||||
SpeechServer._active_servers[serverId] = self
|
||||
|
||||
self._lastKeyEchoTime = None
|
||||
self._say_all_generation = 0
|
||||
self._cancelled_say_all_generation = None
|
||||
|
||||
def _init(self):
|
||||
self._client = client = speechd.SSIPClient('Cthulhu', component=self._id)
|
||||
@@ -489,22 +491,42 @@ class SpeechServer(speechserver.SpeechServer):
|
||||
self._debug_sd_values(f"Speaking '{ssml}' ")
|
||||
self._send_command(self._client.speak, ssml, **kwargs)
|
||||
|
||||
def _say_all(self, iterator, cthulhu_callback):
|
||||
def _say_all(self, iterator, cthulhu_callback, generation):
|
||||
"""Process another sayAll chunk.
|
||||
|
||||
Called by the gidle thread.
|
||||
|
||||
"""
|
||||
if generation != self._say_all_generation:
|
||||
return False
|
||||
|
||||
if generation == self._cancelled_say_all_generation:
|
||||
return False
|
||||
|
||||
try:
|
||||
context, acss = next(iterator)
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
def dispatch_callback(context_copy, progress_type, allow_interrupted=False):
|
||||
if not allow_interrupted and generation != self._say_all_generation:
|
||||
return False
|
||||
cthulhu_callback(context_copy, progress_type)
|
||||
return False
|
||||
|
||||
def callback(callbackType, index_mark=None):
|
||||
# This callback is called in Speech Dispatcher listener thread.
|
||||
# No subsequent Speech Dispatcher interaction is allowed here,
|
||||
# so we pass the calls to the gidle thread.
|
||||
t = self._CALLBACK_TYPE_MAP[callbackType]
|
||||
if generation != self._say_all_generation:
|
||||
return
|
||||
if generation == self._cancelled_say_all_generation:
|
||||
if t == speechserver.SayAllContext.INTERRUPTED:
|
||||
self._say_all_generation += 1
|
||||
self._cancelled_say_all_generation = None
|
||||
GLib.idle_add(dispatch_callback, context.copy(), t, True)
|
||||
return
|
||||
if t == speechserver.SayAllContext.PROGRESS:
|
||||
if index_mark:
|
||||
index = index_mark.split(':')
|
||||
@@ -524,14 +546,15 @@ class SpeechServer(speechserver.SpeechServer):
|
||||
elif t == speechserver.SayAllContext.COMPLETED:
|
||||
context.currentOffset = context.endOffset
|
||||
context.currentEndOffset = None
|
||||
GLib.idle_add(cthulhu_callback, context.copy(), t)
|
||||
GLib.idle_add(dispatch_callback, context.copy(), t)
|
||||
if t == speechserver.SayAllContext.COMPLETED:
|
||||
GLib.idle_add(self._say_all, iterator, cthulhu_callback)
|
||||
GLib.idle_add(self._say_all, iterator, cthulhu_callback, generation)
|
||||
self._speak(context.utterance, acss, callback=callback,
|
||||
event_types=list(self._CALLBACK_TYPE_MAP.keys()))
|
||||
return False # to indicate, that we don't want to be called again.
|
||||
|
||||
def _cancel(self):
|
||||
self._cancelled_say_all_generation = self._say_all_generation
|
||||
self._send_command(self._client.cancel)
|
||||
|
||||
def _change_default_speech_rate(self, step, decrease=False):
|
||||
@@ -657,7 +680,14 @@ class SpeechServer(speechserver.SpeechServer):
|
||||
self._speak(text, acss)
|
||||
|
||||
def sayAll(self, utteranceIterator, progressCallback):
|
||||
GLib.idle_add(self._say_all, utteranceIterator, progressCallback)
|
||||
self._say_all_generation += 1
|
||||
self._cancelled_say_all_generation = None
|
||||
GLib.idle_add(
|
||||
self._say_all,
|
||||
utteranceIterator,
|
||||
progressCallback,
|
||||
self._say_all_generation,
|
||||
)
|
||||
|
||||
def speakCharacter(self, character, acss=None):
|
||||
self._apply_acss(acss)
|
||||
|
||||
Reference in New Issue
Block a user