diff --git a/Commons/Color.qml b/Commons/Color.qml index 6e940f520..ada4c63a5 100644 --- a/Commons/Color.qml +++ b/Commons/Color.qml @@ -20,6 +20,28 @@ Singleton { property bool reloadColors: false + // Debounce external reload requests (file watcher + directory watcher) + // so atomic replacements only trigger one reload. + Timer { + id: externalColorReloadTimer + running: false + interval: 200 + onTriggered: { + if (customColorsFile.path !== undefined) { + Logger.d("Color", "Reloading colors from disk"); + reloadColors = true; + customColorsFile.reload(); + } + } + } + + function scheduleExternalColorReload() { + if (!Settings.directoriesCreated || customColorsFile.path === undefined) { + return; + } + externalColorReloadTimer.restart(); + } + // Suppress transition animations until the first colors.json load completes property bool skipTransition: true @@ -400,11 +422,7 @@ Singleton { path: Settings.directoriesCreated ? (Settings.configDir + "colors.json") : undefined printErrors: false watchChanges: true - onFileChanged: { - Logger.d("Color", "Reloading colors from disk"); - reloadColors = true; - reload(); - } + onFileChanged: scheduleExternalColorReload() onAdapterUpdated: { Logger.d("Color", "Writing colors to disk"); writeAdapter(); @@ -470,4 +488,14 @@ Singleton { property color mOnHover: defaultColors.mOnHover } } + + // Watch parent config directory as a fallback for declarative setups where + // colors.json may be replaced atomically (e.g., symlink/store-path swap). + FileView { + id: colorsDirWatcher + path: Settings.directoriesCreated ? Settings.configDir : undefined + printErrors: false + watchChanges: true + onFileChanged: scheduleExternalColorReload() + } } diff --git a/Commons/Settings.qml b/Commons/Settings.qml index d4deed9e8..aecf87835 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -40,6 +40,28 @@ Singleton { signal settingsSaved signal settingsReloaded + // Debounce external reload requests (file watcher + directory watcher) + // so atomic replacements only trigger one reload. + Timer { + id: externalReloadTimer + running: false + interval: 200 + onTriggered: { + if (settingsFileView.path !== undefined) { + Logger.d("Settings", "Reloading settings after external change detection"); + reloadSettings = true; + settingsFileView.reload(); + } + } + } + + function scheduleExternalReload() { + if (!directoriesCreated || settingsFileView.path === undefined) { + return; + } + externalReloadTimer.restart(); + } + // ----------------------------------------------------- // ----------------------------------------------------- // Ensure directories exist before FileView tries to read files @@ -87,10 +109,7 @@ Singleton { watchChanges: true onAdapterUpdated: saveTimer.start() - onFileChanged: { - reloadSettings = true; - reload(); - } + onFileChanged: scheduleExternalReload() // Trigger initial load when path changes from empty to actual path onPathChanged: { @@ -142,6 +161,16 @@ Singleton { } } + // Watch parent config directory as a fallback for declarative setups where + // settings.json may be replaced atomically (e.g., symlink/store-path swap). + FileView { + id: settingsDirWatcher + path: directoriesCreated ? configDir : undefined + printErrors: false + watchChanges: true + onFileChanged: scheduleExternalReload() + } + // FileView to load default settings for comparison FileView { id: defaultSettingsFileView