Read a list of applications that should always be started in sleep mode from sleep.toml.

This commit is contained in:
Storm Dragon
2026-02-17 07:57:14 -05:00
parent 4dba0ec0cd
commit 40e63150a6
3 changed files with 132 additions and 5 deletions
+1
View File
@@ -286,6 +286,7 @@ class ScriptManager:
try: try:
from . import sleep_mode_manager from . import sleep_mode_manager
sleepModeManager = sleep_mode_manager.getManager() sleepModeManager = sleep_mode_manager.getManager()
sleepModeManager.refreshAutoSleepConfig()
if sleepModeManager and sleepModeManager.isActiveForApp(app): if sleepModeManager and sleepModeManager.isActiveForApp(app):
return self.get_or_create_sleep_mode_script(app) return self.get_or_create_sleep_mode_script(app)
except Exception as error: except Exception as error:
+19
View File
@@ -214,6 +214,25 @@ class SettingsManager(object):
if not os.path.exists(userCustomFile): if not os.path.exists(userCustomFile):
os.close(os.open(userCustomFile, os.O_CREAT, 0o700)) os.close(os.open(userCustomFile, os.O_CREAT, 0o700))
sleepConfigFile = os.path.join(cthulhuDir, "sleep.toml")
if not os.path.exists(sleepConfigFile):
sleepTemplate = (
"# Cthulhu auto-sleep apps\n"
"#\n"
"# List current app names with:\n"
"# cthulhu --list-apps\n"
"# Use the middle app-name column from that output.\n"
"#\n"
"# Add app names to auto-enable sleep mode:\n"
"# apps = [\"qemu\"]\n"
"#\n"
"# Or use a section:\n"
"# [sleep]\n"
"# apps = [\"qemu\"]\n"
)
with open(sleepConfigFile, "w", encoding="utf-8") as configFile:
configFile.write(sleepTemplate)
if self.isFirstStart() and self._backend: if self.isFirstStart() and self._backend:
self._backend.saveDefaultSettings(self.defaultGeneral, self._backend.saveDefaultSettings(self.defaultGeneral,
self.defaultPronunciations, self.defaultPronunciations,
+112 -5
View File
@@ -31,7 +31,9 @@ __copyright__ = "Copyright (c) 2024 Stormux"
__license__ = "LGPL" __license__ = "LGPL"
import time import time
import os
from gi.repository import GLib from gi.repository import GLib
from tomlkit import parse
import cthulhu.braille as braille import cthulhu.braille as braille
import cthulhu.cmdnames as cmdnames import cthulhu.cmdnames as cmdnames
import cthulhu.debug as debug import cthulhu.debug as debug
@@ -47,7 +49,12 @@ class SleepModeManager:
def __init__(self): def __init__(self):
self._handlers = self.getHandlers(True) self._handlers = self.getHandlers(True)
self._bindings = keybindings.KeyBindings() self._bindings = keybindings.KeyBindings()
self._apps = [] self._apps = set()
self._disabledAutoSleepApps = set()
self._autoSleepAppNames = set()
self._autoSleepPath = self._getAutoSleepPath()
self._autoSleepConfigMTime = None
self._loadAutoSleepConfig()
self._lastToggleTime = 0 self._lastToggleTime = 0
self._toggleDebounceDelay = 0.1 # 100ms debounce (reduced for better responsiveness) self._toggleDebounceDelay = 0.1 # 100ms debounce (reduced for better responsiveness)
@@ -76,12 +83,106 @@ class SleepModeManager:
def isActiveForApp(self, app): def isActiveForApp(self, app):
"""Returns True if sleep mode is active for app.""" """Returns True if sleep mode is active for app."""
result = bool(app and hash(app) in self._apps) if not app:
return False
appHash = hash(app)
result = appHash in self._apps
if not result and self._isAutoSleepConfiguredForApp(app):
result = appHash not in self._disabledAutoSleepApps
if result: if result:
tokens = ["SLEEP MODE MANAGER: Is active for", app] tokens = ["SLEEP MODE MANAGER: Is active for", app]
debug.printTokens(debug.LEVEL_INFO, tokens, True) debug.printTokens(debug.LEVEL_INFO, tokens, True)
return result return result
def _getAutoSleepPath(self):
prefsDir = os.path.join(GLib.get_user_data_dir(), "cthulhu")
try:
from . import cthulhu
app = cthulhu.cthulhuApp
if app and app.settingsManager:
configuredDir = app.settingsManager.getPrefsDir()
if configuredDir:
prefsDir = configuredDir
except Exception:
pass
return os.path.join(prefsDir, "sleep.toml")
def _refreshAutoSleepPath(self):
latestPath = self._getAutoSleepPath()
if latestPath != self._autoSleepPath:
self._autoSleepPath = latestPath
self._autoSleepConfigMTime = None
self._loadAutoSleepConfig()
return
try:
latestMTime = os.path.getmtime(self._autoSleepPath)
except OSError:
latestMTime = None
if latestMTime != self._autoSleepConfigMTime:
self._loadAutoSleepConfig()
def refreshAutoSleepConfig(self):
"""Refresh auto-sleep config if prefs directory has changed."""
self._refreshAutoSleepPath()
def _loadAutoSleepConfig(self):
self._autoSleepAppNames = set()
self._autoSleepConfigMTime = None
if not os.path.isfile(self._autoSleepPath):
msg = f"SLEEP MODE MANAGER: No sleep config at {self._autoSleepPath}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return
try:
self._autoSleepConfigMTime = os.path.getmtime(self._autoSleepPath)
except OSError:
self._autoSleepConfigMTime = None
try:
with open(self._autoSleepPath, "r", encoding="utf-8") as configFile:
config = parse(configFile.read() or "")
except Exception as error:
tokens = ["SLEEP MODE MANAGER: Failed to parse", self._autoSleepPath, ":", error]
debug.printTokens(debug.LEVEL_WARNING, tokens, True)
return
appNames = []
topLevelApps = config.get("apps", [])
if isinstance(topLevelApps, list):
appNames.extend(topLevelApps)
sleepSection = config.get("sleep", {})
if isinstance(sleepSection, dict):
sectionApps = sleepSection.get("apps", [])
if isinstance(sectionApps, list):
appNames.extend(sectionApps)
for appName in appNames:
if not isinstance(appName, str):
continue
normalizedName = appName.strip().lower()
if normalizedName:
self._autoSleepAppNames.add(normalizedName)
msg = f"SLEEP MODE MANAGER: Loaded {len(self._autoSleepAppNames)} auto-sleep apps from {self._autoSleepPath}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
def _isAutoSleepConfiguredForApp(self, app):
if not app:
return False
if not self._autoSleepAppNames:
return False
appName = (AXObject.get_name(app) or "").strip().lower()
return bool(appName and appName in self._autoSleepAppNames)
def _setupHandlers(self): def _setupHandlers(self):
"""Sets up and returns the sleep-mode-manager input event handlers.""" """Sets up and returns the sleep-mode-manager input event handlers."""
@@ -132,12 +233,16 @@ class SleepModeManager:
if not (script and script.app): if not (script and script.app):
return True return True
from . import cthulhu_state self.refreshAutoSleepConfig()
scriptManager = script_manager.get_manager() scriptManager = script_manager.get_manager()
if self.isActiveForApp(script.app): if self.isActiveForApp(script.app):
# Turning OFF sleep mode # Turning OFF sleep mode
self._apps.remove(hash(script.app)) appHash = hash(script.app)
self._apps.discard(appHash)
if self._isAutoSleepConfiguredForApp(script.app):
self._disabledAutoSleepApps.add(appHash)
newScript = scriptManager.get_script(script.app) newScript = scriptManager.get_script(script.app)
if notifyUser: if notifyUser:
newScript.presentMessage( newScript.presentMessage(
@@ -177,7 +282,9 @@ class SleepModeManager:
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Active script set successfully", True) debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Active script set successfully", True)
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Adding app to sleep list", True) debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Adding app to sleep list", True)
self._apps.append(hash(script.app)) appHash = hash(script.app)
self._disabledAutoSleepApps.discard(appHash)
self._apps.add(appHash)
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Enabled for {AXObject.get_name(script.app)} (delayed)", True) debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Enabled for {AXObject.get_name(script.app)} (delayed)", True)
# Reset debounce timer after successful toggle # Reset debounce timer after successful toggle
self._lastToggleTime = 0 self._lastToggleTime = 0