MatugenService: more changes to the predefined theme usage

This commit is contained in:
lysec
2025-10-06 19:36:34 +02:00
parent 95a89445ff
commit e564ce50be
+207 -500
View File
@@ -11,14 +11,69 @@ Singleton {
id: root
readonly property string colorsApplyScript: Quickshell.shellDir + '/Bin/colors-apply.sh'
readonly property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml"
property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml"
readonly property var templateConfigs: ({
"gtk": {
"input": "gtk.css",
"outputs": [{
"path": "~/.config/gtk-3.0/gtk.css"
}, {
"path": "~/.config/gtk-4.0/gtk.css"
}],
"postProcess": mode => `gsettings set org.gnome.desktop.interface color-scheme prefer-${mode}\n`
},
"qt": {
"input": "qtct.conf",
"outputs": [{
"path": "~/.config/qt5ct/colors/noctalia.conf"
}, {
"path": "~/.config/qt6ct/colors/noctalia.conf"
}]
},
"fuzzel": {
"input": "fuzzel.conf",
"outputs": [{
"path": "~/.config/fuzzel/themes/noctalia"
}],
"postProcess": () => `${colorsApplyScript} fuzzel\n`
},
"pywalfox": {
"input": "pywalfox.json",
"outputs": [{
"path": "~/.cache/wal/colors.json"
}],
"postProcess": () => `${colorsApplyScript} pywalfox\n`
},
"vesktop": {
"input": "vesktop.css",
"outputs": [{
"path": "~/.config/vesktop/themes/noctalia.theme.css"
}]
}
})
// External state management
readonly property var terminalPaths: ({
"foot": "~/.config/foot/themes/noctalia",
"ghostty": "~/.config/ghostty/themes/noctalia",
"kitty": "~/.config/kitty/themes/noctalia.conf"
})
readonly property var schemeNameMap: ({
"Noctalia (default)": "Noctalia-default",
"Noctalia (legacy)": "Noctalia-legacy",
"Tokyo Night": "Tokyo-Night"
})
// ===== Lifecycle =====
function init() {
Logger.log("Matugen", "Service started")
}
// ===== External Connections =====
Connections {
target: WallpaperService
function onWallpaperChanged(screenName, path) {
// Only detect changes on main screen
if (screenName === Screen.name && Settings.data.colorSchemes.useWallpaperColors) {
generateFromWallpaper()
}
@@ -30,582 +85,234 @@ Singleton {
function onDarkModeChanged() {
Logger.log("Matugen", "Detected dark mode change")
if (Settings.data.colorSchemes.useWallpaperColors) {
MatugenService.generateFromWallpaper()
generateFromWallpaper()
}
}
}
// --------------------------------
function init() {
// does nothing but ensure the singleton is created
// do not remove
Logger.log("Matugen", "Service started")
}
// --------------------------------
// Generate colors using current wallpaper and settings
// ===== Wallpaper Generation =====
function generateFromWallpaper() {
Logger.log("Matugen", "Generating from wallpaper on screen:", Screen.name)
var wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''")
if (wp === "") {
Logger.error("Matugen", "No wallpaper was found")
const wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''")
if (!wp) {
Logger.error("Matugen", "No wallpaper found")
return
}
var content = MatugenTemplates.buildConfigToml()
if (content === "") {
const content = MatugenTemplates.buildConfigToml()
if (!content)
return
}
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
var pathEsc = dynamicConfigPath.replace(/'/g, "'\\''")
var script = buildMatugenScript(content, pathEsc, wp, mode)
const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
const script = buildMatugenScript(content, wp, mode)
generateProcess.command = ["bash", "-lc", script]
generateProcess.running = true
}
// --------------------------------
function buildMatugenScript(content, pathEsc, wallpaper, mode) {
// Use a unique delimiter to avoid conflicts with config content
var delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9)
var script = "cat > '" + pathEsc + "' << '" + delimiter + "'\n" + content + "\n" + delimiter + "\n"
function buildMatugenScript(content, wallpaper, mode) {
const delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9)
const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''")
// Main matugen command
script += "matugen image '" + wallpaper + "' --config '" + pathEsc + "' --mode " + mode + " --type " + Settings.data.colorSchemes.matugenSchemeType
// Add user template execution if enabled
script += addUserTemplateExecution(wallpaper, mode)
let script = `cat > '${pathEsc}' << '${delimiter}'\n${content}\n${delimiter}\n`
script += `matugen image '${wallpaper}' --config '${pathEsc}' --mode ${mode} --type ${Settings.data.colorSchemes.matugenSchemeType}`
script += buildUserTemplateCommand(wallpaper, mode)
return script + "\n"
}
// --------------------------------
function addUserTemplateExecution(input, mode) {
if (!Settings.data.templates.enableUserTemplates) {
return ""
}
var userConfigPath = getUserConfigPath()
var script = "\n# Execute user config if it exists\n"
script += "if [ -f '" + userConfigPath + "' ]; then\n"
script += " matugen image '" + input + "' --config '" + userConfigPath + "' --mode " + mode + " --type " + Settings.data.colorSchemes.matugenSchemeType + "\n"
script += "fi"
return script
}
// --------------------------------
function getUserConfigPath() {
return (Quickshell.env("HOME") + "/.config/matugen/config.toml").replace(/'/g, "'\\''")
}
// --------------------------------
function selectVibrantColor(schemeData, mode) {
var colors = []
colors.push(schemeData[mode]["mPrimary"])
colors.push(schemeData[mode]["mSecondary"])
colors.push(schemeData[mode]["mTertiary"])
var bestScore = 0
var bestScoreIndex = -1
for (var i = 0; i < colors.length; i++) {
var hsl = ColorsConvert.hexToHSL(colors[i])
var score = hsl['s']
if (score > bestScore) {
bestScore = score
bestScoreIndex = i
}
}
return colors[bestScoreIndex]
}
// --------------------------------
// Generate templates from predefined color scheme
// ===== Predefined Scheme Generation =====
function generateFromPredefinedScheme(schemeData) {
Logger.log("Matugen", "Generating templates from predefined color scheme")
// Handle terminal theme copying for predefined schemes
handleTerminalThemes()
var content = MatugenTemplates.buildConfigToml()
if (content === "") {
return
}
const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
const colors = schemeData[mode] || schemeData.dark || schemeData.light
const matugenColors = buildMatugenColorObject(colors)
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
var pathEsc = dynamicConfigPath.replace(/'/g, "'\\''")
const script = processAllTemplates(matugenColors, mode)
// Use the full predefined color scheme instead of generating from a single color
var script = buildPredefinedSchemeScriptWithFullColors(content, pathEsc, schemeData, mode)
generateProcess.command = ["bash", "-lc", script]
generateProcess.running = true
}
// --------------------------------
function buildPredefinedSchemeScriptWithFullColors(content, pathEsc, schemeData, mode) {
// Instead of using matugen color generation, directly process templates with predefined colors
var colors = schemeData[mode] || schemeData.dark || schemeData.light
var script = ""
function buildMatugenColorObject(colors) {
// Helper with fallback support
const c = (color, fallback) => ({
"default": {
"hex": colors[color] || colors[fallback] || "#000000"
}
})
// Process each enabled template directly with predefined colors
script += processTemplatesWithPredefinedColors(colors, mode)
// Add user template execution if enabled
script += addUserTemplateExecutionForPredefinedColors(colors, mode)
return script
}
// --------------------------------
function processTemplatesWithPredefinedColors(colors, mode) {
var script = ""
// Create a colors object that matches matugen's expected format
var matugenColors = {
"primary": {
"default": {
"hex": colors.mPrimary
}
},
"on_primary": {
"default": {
"hex": colors.mOnPrimary
}
},
"primary_container": {
"default": {
"hex": colors.mPrimary
}
},
"on_primary_container": {
"default": {
"hex": colors.mOnPrimary
}
},
"secondary": {
"default": {
"hex": colors.mSecondary
}
},
"on_secondary": {
"default": {
"hex": colors.mOnSecondary
}
},
"secondary_container": {
"default": {
"hex": colors.mSecondary
}
},
"on_secondary_container": {
"default": {
"hex": colors.mOnSecondary
}
},
"tertiary": {
"default": {
"hex": colors.mTertiary
}
},
"on_tertiary": {
"default": {
"hex": colors.mOnTertiary
}
},
"tertiary_container": {
"default": {
"hex": colors.mTertiary
}
},
"on_tertiary_container": {
"default": {
"hex": colors.mOnTertiary
}
},
"error": {
"default": {
"hex": colors.mError
}
},
"on_error": {
"default": {
"hex": colors.mOnError
}
},
"error_container": {
"default": {
"hex": colors.mError
}
},
"on_error_container": {
"default": {
"hex": colors.mOnError
}
},
"background": {
"default": {
"hex": colors.mSurface
}
},
"on_background": {
"default": {
"hex": colors.mOnSurface
}
},
"surface": {
"default": {
"hex": colors.mSurface
}
},
"on_surface": {
"default": {
"hex": colors.mOnSurface
}
},
"surface_variant": {
"default": {
"hex": colors.mSurfaceVariant
}
},
"on_surface_variant": {
"default": {
"hex": colors.mOnSurfaceVariant
}
},
"outline": {
"default": {
"hex": colors.mOutline
}
},
"outline_variant": {
"default": {
"hex": colors.mOutline
}
},
"shadow": {
"default": {
"hex": colors.mShadow
}
},
"surface_container": {
"default": {
"hex": colors.mSurfaceVariant
}
},
"surface_container_low": {
"default": {
"hex": colors.mSurface
}
},
"surface_container_lowest": {
"default": {
"hex": colors.mSurface
}
},
"surface_container_high": {
"default": {
"hex": colors.mSurfaceVariant
}
},
"surface_container_highest": {
"default": {
"hex": colors.mOutline
}
}
return {
"primary": c("mPrimary"),
"on_primary": c("mOnPrimary"),
"primary_container": c("mPrimaryContainer", "mPrimary"),
"on_primary_container": c("mOnPrimaryContainer", "mOnPrimary"),
"secondary": c("mSecondary"),
"on_secondary": c("mOnSecondary"),
"secondary_container": c("mSecondaryContainer", "mSecondary"),
"on_secondary_container": c("mOnSecondaryContainer", "mOnSecondary"),
"tertiary": c("mTertiary"),
"on_tertiary": c("mOnTertiary"),
"tertiary_container": c("mTertiaryContainer", "mTertiary"),
"on_tertiary_container": c("mOnTertiaryContainer", "mOnTertiary"),
"error": c("mError"),
"on_error": c("mOnError"),
"error_container": c("mErrorContainer", "mError"),
"on_error_container": c("mOnErrorContainer", "mOnError"),
"background": c("mBackground", "mSurface"),
"on_background": c("mOnBackground", "mOnSurface"),
"surface": c("mSurface"),
"on_surface": c("mOnSurface"),
"surface_variant": c("mSurfaceVariant", "mSurface"),
"on_surface_variant": c("mOnSurfaceVariant", "mOnSurface"),
"surface_container_lowest": c("mSurfaceContainerLowest", "mSurface"),
"surface_container_low": c("mSurfaceContainerLow", "mSurface"),
"surface_container": c("mSurfaceContainer", "mSurfaceVariant"),
"surface_container_high": c("mSurfaceContainerHigh", "mSurfaceVariant"),
"surface_container_highest": c("mSurfaceContainerHighest", "mOutline"),
"outline": c("mOutline"),
"outline_variant": c("mOutlineVariant", "mOutline"),
"shadow": c("mShadow")
}
}
// Process each enabled template using the same structure as MatugenTemplates
var applications = [{
"name": "gtk",
"templates": [{
"version": "gtk3",
"output": "gtk3"
}, {
"version": "gtk4",
"output": "gtk4"
}],
"input": "gtk.css"
}, {
"name": "qt",
"templates": [{
"version": "qt5",
"output": "qt5"
}, {
"version": "qt6",
"output": "qt6"
}],
"input": "qtct.conf"
}, {
"name": "fuzzel",
"templates": [{
"version": "fuzzel",
"output": "fuzzel"
}],
"input": "fuzzel.conf"
}, {
"name": "pywalfox",
"templates": [{
"version": "pywalfox",
"output": "pywalfox"
}],
"input": "pywalfox.json"
}, {
"name": "vesktop",
"templates": [{
"version": "vesktop",
"output": "vesktop"
}],
"input": "vesktop.css"
}]
function processAllTemplates(colors, mode) {
let script = ""
const homeDir = Quickshell.env("HOME")
applications.forEach(function (app) {
if (Settings.data.templates[app.name]) {
script += processTemplateForApp(app.name, matugenColors, mode)
}
})
Object.keys(templateConfigs).forEach(appName => {
if (Settings.data.templates[appName]) {
script += processTemplate(appName, colors, mode, homeDir)
}
})
return script
}
// --------------------------------
function processTemplateForApp(appName, colors, mode) {
var script = ""
function processTemplate(appName, colors, mode, homeDir) {
const config = templateConfigs[appName]
const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${config.input}`
let script = ""
switch (appName) {
case "gtk":
script += processGtkTemplate(colors, mode)
break
case "qt":
script += processQtTemplate(colors, mode)
break
case "fuzzel":
script += processFuzzelTemplate(colors, mode)
break
case "pywalfox":
script += processPywalfoxTemplate(colors, mode)
break
case "vesktop":
script += processVesktopTemplate(colors, mode)
break
config.outputs.forEach(output => {
const outputPath = output.path.replace("~", homeDir)
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'))
script += `mkdir -p ${outputDir}\n`
script += `cp '${templatePath}' '${outputPath}'\n`
script += replaceColorsInFile(outputPath, colors)
})
if (config.postProcess) {
script += config.postProcess(mode)
}
return script
}
// --------------------------------
function processGtkTemplate(colors, mode) {
var script = ""
var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/gtk.css"
var homeDir = Quickshell.env("HOME")
var outputPath3 = homeDir + "/.config/gtk-3.0/gtk.css"
var outputPath4 = homeDir + "/.config/gtk-4.0/gtk.css"
// Process GTK3 template
script += "mkdir -p " + homeDir + "/.config/gtk-3.0\n"
script += "cp '" + templatePath + "' '" + outputPath3 + "'\n"
script += replaceColorsInFile(outputPath3, colors)
// Process GTK4 template
script += "mkdir -p " + homeDir + "/.config/gtk-4.0\n"
script += "cp '" + templatePath + "' '" + outputPath4 + "'\n"
script += replaceColorsInFile(outputPath4, colors)
script += "gsettings set org.gnome.desktop.interface color-scheme prefer-" + mode + "\n"
return script
}
// --------------------------------
function processQtTemplate(colors, mode) {
var script = ""
var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/qtct.conf"
var homeDir = Quickshell.env("HOME")
var outputPath5 = homeDir + "/.config/qt5ct/colors/noctalia.conf"
var outputPath6 = homeDir + "/.config/qt6ct/colors/noctalia.conf"
// Process Qt5 template
script += "mkdir -p " + homeDir + "/.config/qt5ct/colors\n"
script += "cp '" + templatePath + "' '" + outputPath5 + "'\n"
script += replaceColorsInFile(outputPath5, colors)
// Process Qt6 template
script += "mkdir -p " + homeDir + "/.config/qt6ct/colors\n"
script += "cp '" + templatePath + "' '" + outputPath6 + "'\n"
script += replaceColorsInFile(outputPath6, colors)
return script
}
// --------------------------------
function processFuzzelTemplate(colors, mode) {
var script = ""
var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/fuzzel.conf"
var homeDir = Quickshell.env("HOME")
var outputPath = homeDir + "/.config/fuzzel/themes/noctalia"
script += "mkdir -p " + homeDir + "/.config/fuzzel/themes\n"
script += "cp '" + templatePath + "' '" + outputPath + "'\n"
script += replaceColorsInFile(outputPath, colors)
script += MatugenService.colorsApplyScript + " fuzzel\n"
return script
}
// --------------------------------
function processPywalfoxTemplate(colors, mode) {
var script = ""
var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/pywalfox.json"
var homeDir = Quickshell.env("HOME")
var outputPath = homeDir + "/.cache/wal/colors.json"
script += "mkdir -p " + homeDir + "/.cache/wal\n"
script += "cp '" + templatePath + "' '" + outputPath + "'\n"
script += replaceColorsInFile(outputPath, colors)
script += MatugenService.colorsApplyScript + " pywalfox\n"
return script
}
// --------------------------------
function processVesktopTemplate(colors, mode) {
var script = ""
var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/vesktop.css"
var homeDir = Quickshell.env("HOME")
var outputPath = homeDir + "/.config/vesktop/themes/noctalia.theme.css"
script += "mkdir -p " + homeDir + "/.config/vesktop/themes\n"
script += "cp '" + templatePath + "' '" + outputPath + "'\n"
script += replaceColorsInFile(outputPath, colors)
return script
}
// --------------------------------
function replaceColorsInFile(filePath, colors) {
var script = ""
// Replace all color placeholders with actual colors
Object.keys(colors).forEach(function (colorKey) {
var colorValue = colors[colorKey].default.hex
// Escape special characters in the color value for sed
var escapedColor = colorValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
script += "sed -i 's/{{colors\\." + colorKey + "\\.default\\.hex}}/" + escapedColor + "/g' '" + filePath + "'\n"
})
let script = ""
Object.keys(colors).forEach(colorKey => {
const colorValue = colors[colorKey].default.hex
const escapedColor = colorValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex}}/${escapedColor}/g' '${filePath}'\n`
})
return script
}
// --------------------------------
function addUserTemplateExecutionForPredefinedColors(colors, mode) {
if (!Settings.data.templates.enableUserTemplates) {
return ""
}
var userConfigPath = getUserConfigPath()
var script = "\n# Execute user config if it exists\n"
script += "if [ -f '" + userConfigPath + "' ]; then\n"
script += " # Process user templates with predefined colors\n"
script += " echo 'User templates processing not implemented for predefined colors yet'\n"
script += "fi"
return script
}
// --------------------------------
function buildPredefinedSchemeScript(content, pathEsc, color, mode) {
// Use a unique delimiter to avoid conflicts with config content
var delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9)
var script = "cat > '" + pathEsc + "' << '" + delimiter + "'\n" + content + "\n" + delimiter + "\n\n"
script += "matugen color hex '" + color + "' --config '" + pathEsc + "' --mode " + mode + "\n"
// Add user template execution if enabled
script += addUserTemplateExecutionForColor(color, mode)
return script
}
// --------------------------------
function addUserTemplateExecutionForColor(color, mode) {
if (!Settings.data.templates.enableUserTemplates) {
return ""
}
var userConfigPath = getUserConfigPath()
var script = "\n# Execute user config if it exists\n"
script += "if [ -f '" + userConfigPath + "' ]; then\n"
script += " matugen color hex '" + color + "' --config '" + userConfigPath + "' --mode " + mode + "\n"
script += "fi"
return script
}
// --------------------------------
// ===== Terminal Themes =====
function handleTerminalThemes() {
var terminals = {
"foot": "~/.config/foot/themes/noctalia",
"ghostty": "~/.config/ghostty/themes/noctalia",
"kitty": "~/.config/kitty/themes/noctalia.conf"
}
const commands = []
var copyCmd = Object.keys(terminals).filter(function (terminal) {
return Settings.data.templates[terminal]
}).map(function (terminal) {
var colorsPath = terminals[terminal]
var colorsPathParent = colorsPath.replace(/[^\/]*$/, "")
var terminalColorsTemplate = getTerminalColorsTemplate(terminal)
return ['mkdir -p ' + colorsPathParent, 'cp -f ' + terminalColorsTemplate + ' ' + colorsPath, colorsApplyScript + ' ' + terminal]
}).reduce(function (arr1, arr2) {
return arr1.concat(arr2)
}, []).join('; ')
Object.keys(terminalPaths).forEach(terminal => {
if (Settings.data.templates[terminal]) {
const outputPath = terminalPaths[terminal]
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'))
const templatePath = getTerminalColorsTemplate(terminal)
if (copyCmd !== "") {
copyProcess.command = ["bash", "-lc", copyCmd]
commands.push(`mkdir -p ${outputDir}`)
commands.push(`cp -f ${templatePath} ${outputPath}`)
commands.push(`${colorsApplyScript} ${terminal}`)
}
})
if (commands.length > 0) {
copyProcess.command = ["bash", "-lc", commands.join('; ')]
copyProcess.running = true
}
}
// --------------------------------
function getTerminalColorsTemplate(terminal) {
var colorScheme = Settings.data.colorSchemes.predefinedScheme
const darkLight = Settings.data.colorSchemes.darkMode ? 'dark' : 'light'
let colorScheme = Settings.data.colorSchemes.predefinedScheme
const mode = Settings.data.colorSchemes.darkMode ? 'dark' : 'light'
// Convert display names back to folder names
var schemeMap = {
"Noctalia (default)": "Noctalia-default",
"Noctalia (legacy)": "Noctalia-legacy",
"Tokyo Night": "Tokyo-Night"
}
colorScheme = schemeNameMap[colorScheme] || colorScheme
const extension = terminal === 'kitty' ? ".conf" : ""
colorScheme = schemeMap[colorScheme] || colorScheme
var extension = terminal === 'kitty' ? ".conf" : ""
return `${Quickshell.shellDir}/Assets/ColorScheme/${colorScheme}/terminal/${terminal}/${colorScheme}-${darkLight}${extension}`
return `${Quickshell.shellDir}/Assets/ColorScheme/${colorScheme}/terminal/${terminal}/${colorScheme}-${mode}${extension}`
}
// --------------------------------
// ===== User Templates =====
function buildUserTemplateCommand(input, mode) {
if (!Settings.data.templates.enableUserTemplates) {
return ""
}
const userConfigPath = getUserConfigPath()
let script = "\n# Execute user config if it exists\n"
script += `if [ -f '${userConfigPath}' ]; then\n`
script += ` matugen image '${input}' --config '${userConfigPath}' --mode ${mode} --type ${Settings.data.colorSchemes.matugenSchemeType}\n`
script += "fi"
return script
}
function getUserConfigPath() {
return (Quickshell.env("HOME") + "/.config/matugen/config.toml").replace(/'/g, "'\\''")
}
// ===== Utilities =====
function selectVibrantColor(schemeData, mode) {
const colors = [schemeData[mode]["mPrimary"], schemeData[mode]["mSecondary"], schemeData[mode]["mTertiary"]]
let bestScore = 0
let bestIndex = 0
colors.forEach((color, i) => {
const hsl = ColorsConvert.hexToHSL(color)
if (hsl.s > bestScore) {
bestScore = hsl.s
bestIndex = i
}
})
return colors[bestIndex]
}
// ===== Processes =====
Process {
id: generateProcess
workingDirectory: Quickshell.shellDir
running: false
stderr: StdioCollector {
onStreamFinished: {
if (this.text !== "") {
if (this.text) {
Logger.warn("MatugenService", "GenerateProcess stderr:", this.text)
}
}
}
}
// --------------------------------
Process {
id: copyProcess
running: false
stderr: StdioCollector {
onStreamFinished: {
if (this.text !== "") {
if (this.text) {
Logger.warn("MatugenService", "CopyProcess stderr:", this.text)
}
}