Switched to qmlformat.

This commit is contained in:
ItsLemmy
2025-11-16 17:07:03 -05:00
parent 32905224b9
commit 3ff5b7639f
223 changed files with 9970 additions and 9658 deletions
+14 -14
View File
@@ -16,7 +16,7 @@ Singleton {
// When the wallpaper changes, regenerate with Matugen if necessary
function onWallpaperChanged(screenName, path) {
if (screenName === Screen.name && Settings.data.colorSchemes.useWallpaperColors) {
generateFromWallpaper()
generateFromWallpaper();
}
}
}
@@ -24,38 +24,38 @@ Singleton {
Connections {
target: Settings.data.colorSchemes
function onDarkModeChanged() {
Logger.i("AppThemeService", "Detected dark mode change")
generate()
Logger.i("AppThemeService", "Detected dark mode change");
generate();
}
}
// PUBLIC FUNCTIONS
function init() {
Logger.i("AppThemeService", "Service started")
Logger.i("AppThemeService", "Service started");
}
function generate() {
if (Settings.data.colorSchemes.useWallpaperColors) {
generateFromWallpaper()
generateFromWallpaper();
} else {
// applyScheme will trigger template generation via schemeReader.onLoaded
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
}
}
function generateFromWallpaper() {
const wp = WallpaperService.getWallpaper(Screen.name)
const wp = WallpaperService.getWallpaper(Screen.name);
if (!wp) {
Logger.e("AppThemeService", "No wallpaper found")
return
Logger.e("AppThemeService", "No wallpaper found");
return;
}
const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
TemplateProcessor.processWallpaperColors(wp, mode)
const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
TemplateProcessor.processWallpaperColors(wp, mode);
}
function generateFromPredefinedScheme(schemeData) {
Logger.i("AppThemeService", "Generating templates from predefined color scheme")
const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
TemplateProcessor.processPredefinedScheme(schemeData, mode)
Logger.i("AppThemeService", "Generating templates from predefined color scheme");
const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
TemplateProcessor.processPredefinedScheme(schemeData, mode);
}
}
+32 -33
View File
@@ -7,62 +7,61 @@ import "../../Helpers/ColorsConvert.js" as ColorsConvert
Singleton {
id: root
/**
* Generate Material Design 3 color palette from base colors
* @param colors - Object with mPrimary, mSecondary, mTertiary, mError, mSurface, etc.
* @param isDarkMode - Boolean indicating dark or light mode
* @param isStrict - Boolean; if true, use mSurfaceVariant/mOnSurfaceVariant/mOutline directly
* @returns Object with all MD3 color roles in matugen format
*/
* Generate Material Design 3 color palette from base colors
* @param colors - Object with mPrimary, mSecondary, mTertiary, mError, mSurface, etc.
* @param isDarkMode - Boolean indicating dark or light mode
* @param isStrict - Boolean; if true, use mSurfaceVariant/mOnSurfaceVariant/mOutline directly
* @returns Object with all MD3 color roles in matugen format
*/
function generatePalette(colors, isDarkMode, isStrict) {
const c = hex => ({
"default": {
"hex": hex,
"hex_stripped": hex.replace(/^#/, "")
}
})
});
// Generate container colors
const primaryContainer = ColorsConvert.generateContainerColor(colors.mPrimary, isDarkMode)
const secondaryContainer = ColorsConvert.generateContainerColor(colors.mSecondary, isDarkMode)
const tertiaryContainer = ColorsConvert.generateContainerColor(colors.mTertiary, isDarkMode)
const primaryContainer = ColorsConvert.generateContainerColor(colors.mPrimary, isDarkMode);
const secondaryContainer = ColorsConvert.generateContainerColor(colors.mSecondary, isDarkMode);
const tertiaryContainer = ColorsConvert.generateContainerColor(colors.mTertiary, isDarkMode);
// Generate "on" colors
const onPrimary = ColorsConvert.generateOnColor(colors.mPrimary, isDarkMode)
const onSecondary = ColorsConvert.generateOnColor(colors.mSecondary, isDarkMode)
const onTertiary = ColorsConvert.generateOnColor(colors.mTertiary, isDarkMode)
const onPrimary = ColorsConvert.generateOnColor(colors.mPrimary, isDarkMode);
const onSecondary = ColorsConvert.generateOnColor(colors.mSecondary, isDarkMode);
const onTertiary = ColorsConvert.generateOnColor(colors.mTertiary, isDarkMode);
const onPrimaryContainer = ColorsConvert.generateOnColor(primaryContainer, isDarkMode)
const onSecondaryContainer = ColorsConvert.generateOnColor(secondaryContainer, isDarkMode)
const onTertiaryContainer = ColorsConvert.generateOnColor(tertiaryContainer, isDarkMode)
const onPrimaryContainer = ColorsConvert.generateOnColor(primaryContainer, isDarkMode);
const onSecondaryContainer = ColorsConvert.generateOnColor(secondaryContainer, isDarkMode);
const onTertiaryContainer = ColorsConvert.generateOnColor(tertiaryContainer, isDarkMode);
// Generate error colors (standard red-based)
const errorContainer = ColorsConvert.generateContainerColor(colors.mError, isDarkMode)
const onError = ColorsConvert.generateOnColor(colors.mError, isDarkMode)
const onErrorContainer = ColorsConvert.generateOnColor(errorContainer, isDarkMode)
const errorContainer = ColorsConvert.generateContainerColor(colors.mError, isDarkMode);
const onError = ColorsConvert.generateOnColor(colors.mError, isDarkMode);
const onErrorContainer = ColorsConvert.generateOnColor(errorContainer, isDarkMode);
// Surface is same as background in Material Design 3
const surface = colors.mSurface
const onSurface = isStrict ? colors.mOnSurface : ColorsConvert.generateOnColor(colors.mSurface, isDarkMode)
const surface = colors.mSurface;
const onSurface = isStrict ? colors.mOnSurface : ColorsConvert.generateOnColor(colors.mSurface, isDarkMode);
// Generate surface variant (slightly different tone)
const surfaceVariant = isStrict ? colors.mSurfaceVariant : ColorsConvert.adjustLightness(colors.mSurface, isDarkMode ? 5 : -3)
const onSurfaceVariant = isStrict ? colors.mOnSurfaceVariant : ColorsConvert.generateOnColor(surfaceVariant, isDarkMode)
const surfaceVariant = isStrict ? colors.mSurfaceVariant : ColorsConvert.adjustLightness(colors.mSurface, isDarkMode ? 5 : -3);
const onSurfaceVariant = isStrict ? colors.mOnSurfaceVariant : ColorsConvert.generateOnColor(surfaceVariant, isDarkMode);
// Generate surface containers (progressive elevation)
const surfaceContainerLowest = ColorsConvert.generateSurfaceVariant(surface, 0, isDarkMode)
const surfaceContainerLow = ColorsConvert.generateSurfaceVariant(surface, 1, isDarkMode)
const surfaceContainer = ColorsConvert.generateSurfaceVariant(surface, 2, isDarkMode)
const surfaceContainerHigh = ColorsConvert.generateSurfaceVariant(surface, 3, isDarkMode)
const surfaceContainerHighest = ColorsConvert.generateSurfaceVariant(surface, 4, isDarkMode)
const surfaceContainerLowest = ColorsConvert.generateSurfaceVariant(surface, 0, isDarkMode);
const surfaceContainerLow = ColorsConvert.generateSurfaceVariant(surface, 1, isDarkMode);
const surfaceContainer = ColorsConvert.generateSurfaceVariant(surface, 2, isDarkMode);
const surfaceContainerHigh = ColorsConvert.generateSurfaceVariant(surface, 3, isDarkMode);
const surfaceContainerHighest = ColorsConvert.generateSurfaceVariant(surface, 4, isDarkMode);
// Generate outline colors (for borders/dividers)
const outline = isStrict ? colors.mOutline : ColorsConvert.adjustLightnessAndSaturation(colors.mOnSurface, isDarkMode ? -30 : 30, isDarkMode ? -30 : 30)
const outlineVariant = ColorsConvert.adjustLightness(outline, isDarkMode ? -20 : 20)
const outline = isStrict ? colors.mOutline : ColorsConvert.adjustLightnessAndSaturation(colors.mOnSurface, isDarkMode ? -30 : 30, isDarkMode ? -30 : 30);
const outlineVariant = ColorsConvert.adjustLightness(outline, isDarkMode ? -20 : 20);
// Shadow is always pitch black
const shadow = "#000000"
const shadow = "#000000";
return {
"primary": c(colors.mPrimary),
@@ -95,6 +94,6 @@ Singleton {
"outline": c(outline),
"outline_variant": c(outlineVariant),
"shadow": c(shadow)
}
};
}
}
+94 -94
View File
@@ -1,7 +1,7 @@
pragma Singleton
import Qt.labs.folderlistmodel
import QtQuick
import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
import qs.Commons
@@ -18,16 +18,16 @@ Singleton {
Connections {
target: Settings.data.colorSchemes
function onDarkModeChanged() {
Logger.i("ColorScheme", "Detected dark mode change")
Logger.i("ColorScheme", "Detected dark mode change");
if (!Settings.data.colorSchemes.useWallpaperColors && Settings.data.colorSchemes.predefinedScheme) {
// Re-apply current scheme to pick the right variant
applyScheme(Settings.data.colorSchemes.predefinedScheme)
applyScheme(Settings.data.colorSchemes.predefinedScheme);
}
// Toast: dark/light mode switched
const enabled = !!Settings.data.colorSchemes.darkMode
const label = enabled ? "Dark mode" : "Light mode"
const description = enabled ? "Enabled" : "Enabled"
ToastService.showNotice(label, description, "dark-mode")
const enabled = !!Settings.data.colorSchemes.darkMode;
const label = enabled ? "Dark mode" : "Light mode";
const description = enabled ? "Enabled" : "Enabled";
ToastService.showNotice(label, description, "dark-mode");
}
}
@@ -35,84 +35,84 @@ Singleton {
function init() {
// does nothing but ensure the singleton is created
// do not remove
Logger.i("ColorScheme", "Service started")
loadColorSchemes()
Logger.i("ColorScheme", "Service started");
loadColorSchemes();
}
function loadColorSchemes() {
Logger.d("ColorScheme", "Load colorScheme")
scanning = true
schemes = []
Logger.d("ColorScheme", "Load colorScheme");
scanning = true;
schemes = [];
// Use find command to locate all scheme.json files
findProcess.command = ["find", schemesDirectory, "-name", "*.json", "-type", "f"]
findProcess.running = true
findProcess.command = ["find", schemesDirectory, "-name", "*.json", "-type", "f"];
findProcess.running = true;
}
function getBasename(path) {
if (!path)
return ""
var chunks = path.split("/")
return "";
var chunks = path.split("/");
// Get the filename without extension
var filename = chunks[chunks.length - 1]
var schemeName = filename.replace(".json", "")
var filename = chunks[chunks.length - 1];
var schemeName = filename.replace(".json", "");
// Convert back to display names for special cases
if (schemeName === "Noctalia-default") {
return "Noctalia (default)"
return "Noctalia (default)";
} else if (schemeName === "Noctalia-legacy") {
return "Noctalia (legacy)"
return "Noctalia (legacy)";
} else if (schemeName === "Tokyo-Night") {
return "Tokyo Night"
return "Tokyo Night";
}
return schemeName
return schemeName;
}
function resolveSchemePath(nameOrPath) {
if (!nameOrPath)
return ""
return "";
if (nameOrPath.indexOf("/") !== -1) {
return nameOrPath
return nameOrPath;
}
// Handle special cases for Noctalia schemes
var schemeName = nameOrPath.replace(".json", "")
var schemeName = nameOrPath.replace(".json", "");
if (schemeName === "Noctalia (default)") {
schemeName = "Noctalia-default"
schemeName = "Noctalia-default";
} else if (schemeName === "Noctalia (legacy)") {
schemeName = "Noctalia-legacy"
schemeName = "Noctalia-legacy";
} else if (schemeName === "Tokyo Night") {
schemeName = "Tokyo-Night"
schemeName = "Tokyo-Night";
}
return schemesDirectory + "/" + schemeName + "/" + schemeName + ".json"
return schemesDirectory + "/" + schemeName + "/" + schemeName + ".json";
}
function applyScheme(nameOrPath) {
// Force reload by bouncing the path
var filePath = resolveSchemePath(nameOrPath)
schemeReader.path = ""
schemeReader.path = filePath
var filePath = resolveSchemePath(nameOrPath);
schemeReader.path = "";
schemeReader.path = filePath;
}
function setPredefinedScheme(schemeName) {
Logger.i("ColorScheme", "Attempting to set predefined scheme to:", schemeName)
Logger.i("ColorScheme", "Attempting to set predefined scheme to:", schemeName);
var resolvedPath = resolveSchemePath(schemeName)
var basename = getBasename(schemeName)
var resolvedPath = resolveSchemePath(schemeName);
var basename = getBasename(schemeName);
// Check if the scheme actually exists in the loaded schemes list
var schemeExists = false
var schemeExists = false;
for (var i = 0; i < schemes.length; i++) {
if (getBasename(schemes[i]) === basename) {
schemeExists = true
break
schemeExists = true;
break;
}
}
if (schemeExists) {
Settings.data.colorSchemes.predefinedScheme = basename
applyScheme(schemeName)
ToastService.showNotice("Color Scheme", `Set to ${basename}`, "settings-color-scheme")
Settings.data.colorSchemes.predefinedScheme = basename;
applyScheme(schemeName);
ToastService.showNotice("Color Scheme", `Set to ${basename}`, "settings-color-scheme");
} else {
Logger.e("ColorScheme", "Scheme not found:", schemeName)
ToastService.showError("Color Scheme", `Scheme '${basename}' not found!`)
Logger.e("ColorScheme", "Scheme not found:", schemeName);
ToastService.showError("Color Scheme", `Scheme '${basename}' not found!`);
}
}
@@ -122,33 +122,33 @@ Singleton {
onExited: function (exitCode) {
if (exitCode === 0) {
var output = stdout.text.trim()
var output = stdout.text.trim();
var files = output.split('\n').filter(function (line) {
return line.length > 0
})
return line.length > 0;
});
files.sort(function (a, b) {
var nameA = getBasename(a).toLowerCase()
var nameB = getBasename(b).toLowerCase()
return nameA.localeCompare(nameB)
})
schemes = files
scanning = false
Logger.d("ColorScheme", "Listed", schemes.length, "schemes")
var nameA = getBasename(a).toLowerCase();
var nameB = getBasename(b).toLowerCase();
return nameA.localeCompare(nameB);
});
schemes = files;
scanning = false;
Logger.d("ColorScheme", "Listed", schemes.length, "schemes");
// Normalize stored scheme to basename and re-apply if necessary
var stored = Settings.data.colorSchemes.predefinedScheme
var stored = Settings.data.colorSchemes.predefinedScheme;
if (stored) {
var basename = getBasename(stored)
var basename = getBasename(stored);
if (basename !== stored) {
Settings.data.colorSchemes.predefinedScheme = basename
Settings.data.colorSchemes.predefinedScheme = basename;
}
if (!Settings.data.colorSchemes.useWallpaperColors) {
applyScheme(basename)
applyScheme(basename);
}
}
} else {
Logger.e("ColorScheme", "Failed to find color scheme files")
schemes = []
scanning = false
Logger.e("ColorScheme", "Failed to find color scheme files");
schemes = [];
scanning = false;
}
}
@@ -161,48 +161,48 @@ Singleton {
id: schemeReader
onLoaded: {
try {
var data = JSON.parse(text())
var variant = data
var data = JSON.parse(text());
var variant = data;
// If scheme provides dark/light variants, pick based on settings
if (data && (data.dark || data.light)) {
if (Settings.data.colorSchemes.darkMode) {
variant = data.dark || data.light
variant = data.dark || data.light;
} else {
variant = data.light || data.dark
variant = data.light || data.dark;
}
}
writeColorsToDisk(variant)
Logger.i("ColorScheme", "Applying color scheme:", getBasename(path))
writeColorsToDisk(variant);
Logger.i("ColorScheme", "Applying color scheme:", getBasename(path));
// Generate Matugen templates if any are enabled and setting allows it
if (Settings.data.colorSchemes.generateTemplatesForPredefined && hasEnabledTemplates()) {
AppThemeService.generateFromPredefinedScheme(data)
AppThemeService.generateFromPredefinedScheme(data);
}
} catch (e) {
Logger.e("ColorScheme", "Failed to parse scheme JSON:", path, e)
Logger.e("ColorScheme", "Failed to parse scheme JSON:", path, e);
}
}
}
// Check if any templates are enabled
function hasEnabledTemplates() {
const templates = Settings.data.templates
const templates = Settings.data.templates;
for (const key in templates) {
if (templates[key]) {
return true
return true;
}
}
return false
return false;
}
// Writer to colors.json using a JsonAdapter for safety
FileView {
id: colorsWriter
path: colorsJsonFilePath
onSaved: {
onSaved:
// Logger.i("ColorScheme", "Colors saved")
}
// Logger.i("ColorScheme", "Colors saved")
{}
JsonAdapter {
id: out
property color mPrimary: "#000000"
@@ -226,28 +226,28 @@ Singleton {
function writeColorsToDisk(obj) {
function pick(o, a, b, fallback) {
return (o && (o[a] || o[b])) || fallback
return (o && (o[a] || o[b])) || fallback;
}
out.mPrimary = pick(obj, "mPrimary", "primary", out.mPrimary)
out.mOnPrimary = pick(obj, "mOnPrimary", "onPrimary", out.mOnPrimary)
out.mSecondary = pick(obj, "mSecondary", "secondary", out.mSecondary)
out.mOnSecondary = pick(obj, "mOnSecondary", "onSecondary", out.mOnSecondary)
out.mTertiary = pick(obj, "mTertiary", "tertiary", out.mTertiary)
out.mOnTertiary = pick(obj, "mOnTertiary", "onTertiary", out.mOnTertiary)
out.mError = pick(obj, "mError", "error", out.mError)
out.mOnError = pick(obj, "mOnError", "onError", out.mOnError)
out.mSurface = pick(obj, "mSurface", "surface", out.mSurface)
out.mOnSurface = pick(obj, "mOnSurface", "onSurface", out.mOnSurface)
out.mSurfaceVariant = pick(obj, "mSurfaceVariant", "surfaceVariant", out.mSurfaceVariant)
out.mOnSurfaceVariant = pick(obj, "mOnSurfaceVariant", "onSurfaceVariant", out.mOnSurfaceVariant)
out.mOutline = pick(obj, "mOutline", "outline", out.mOutline)
out.mShadow = pick(obj, "mShadow", "shadow", out.mShadow)
out.mHover = pick(obj, "mHover", "hover", out.mHover)
out.mOnHover = pick(obj, "mOnHover", "onHover", out.mOnHover)
out.mPrimary = pick(obj, "mPrimary", "primary", out.mPrimary);
out.mOnPrimary = pick(obj, "mOnPrimary", "onPrimary", out.mOnPrimary);
out.mSecondary = pick(obj, "mSecondary", "secondary", out.mSecondary);
out.mOnSecondary = pick(obj, "mOnSecondary", "onSecondary", out.mOnSecondary);
out.mTertiary = pick(obj, "mTertiary", "tertiary", out.mTertiary);
out.mOnTertiary = pick(obj, "mOnTertiary", "onTertiary", out.mOnTertiary);
out.mError = pick(obj, "mError", "error", out.mError);
out.mOnError = pick(obj, "mOnError", "onError", out.mOnError);
out.mSurface = pick(obj, "mSurface", "surface", out.mSurface);
out.mOnSurface = pick(obj, "mOnSurface", "onSurface", out.mOnSurface);
out.mSurfaceVariant = pick(obj, "mSurfaceVariant", "surfaceVariant", out.mSurfaceVariant);
out.mOnSurfaceVariant = pick(obj, "mOnSurfaceVariant", "onSurfaceVariant", out.mOnSurfaceVariant);
out.mOutline = pick(obj, "mOutline", "outline", out.mOutline);
out.mShadow = pick(obj, "mShadow", "shadow", out.mShadow);
out.mHover = pick(obj, "mHover", "hover", out.mHover);
out.mOnHover = pick(obj, "mOnHover", "onHover", out.mOnHover);
// Force a rewrite by updating the path
colorsWriter.path = ""
colorsWriter.path = colorsJsonFilePath
colorsWriter.writeAdapter()
colorsWriter.path = "";
colorsWriter.path = colorsJsonFilePath;
colorsWriter.writeAdapter();
}
}
+172 -177
View File
@@ -26,78 +26,75 @@ Singleton {
"wezterm": "~/.config/wezterm/colors/Noctalia.toml"
})
/**
* Process wallpaper colors using matugen CLI
* Dual-path architecture (wallpaper uses matugen CLI)
*/
* Process wallpaper colors using matugen CLI
* Dual-path architecture (wallpaper uses matugen CLI)
*/
function processWallpaperColors(wallpaperPath, mode) {
const content = buildMatugenConfig()
const content = buildMatugenConfig();
if (!content)
return
return;
const wp = wallpaperPath.replace(/'/g, "'\\''");
const script = buildMatugenScript(content, wp, mode);
const wp = wallpaperPath.replace(/'/g, "'\\''")
const script = buildMatugenScript(content, wp, mode)
generateProcess.generator = "matugen"
generateProcess.command = ["bash", "-lc", script]
generateProcess.running = true
generateProcess.generator = "matugen";
generateProcess.command = ["bash", "-lc", script];
generateProcess.running = true;
}
/**
* Process predefined color scheme using sed scripts
* Dual-path architecture (predefined uses sed scripts)
*/
* Process predefined color scheme using sed scripts
* Dual-path architecture (predefined uses sed scripts)
*/
function processPredefinedScheme(schemeData, mode) {
handleTerminalThemes(mode)
handleTerminalThemes(mode);
const colors = schemeData[mode]
let script = processAllTemplates(colors, mode)
const colors = schemeData[mode];
let script = processAllTemplates(colors, mode);
// Add user templates if enabled (requirement #1)
script += buildUserTemplateCommandForPredefined(schemeData, mode)
script += buildUserTemplateCommandForPredefined(schemeData, mode);
generateProcess.generator = "predefined"
generateProcess.command = ["bash", "-lc", script]
generateProcess.running = true
generateProcess.generator = "predefined";
generateProcess.command = ["bash", "-lc", script];
generateProcess.running = true;
}
// ================================================================================
// WALLPAPER-BASED GENERATION (matugen CLI)
// ================================================================================
function buildMatugenConfig() {
var lines = []
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
var lines = [];
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
if (Settings.data.colorSchemes.useWallpaperColors) {
addWallpaperTemplates(lines, mode)
addWallpaperTemplates(lines, mode);
}
addApplicationTemplates(lines, mode)
addApplicationTemplates(lines, mode);
if (lines.length > 0) {
return ["[config]"].concat(lines).join("\n") + "\n"
return ["[config]"].concat(lines).join("\n") + "\n";
}
return ""
return "";
}
function addWallpaperTemplates(lines, mode) {
// Noctalia colors JSON
lines.push("[templates.noctalia]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/MatugenTemplates/noctalia.json"')
lines.push('output_path = "' + Settings.configDir + 'colors.json"')
lines.push("[templates.noctalia]");
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/MatugenTemplates/noctalia.json"');
lines.push('output_path = "' + Settings.configDir + 'colors.json"');
// Terminal templates
TemplateRegistry.terminals.forEach(terminal => {
if (Settings.data.templates[terminal.id]) {
lines.push(`\n[templates.${terminal.id}]`)
lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${terminal.matugenPath}"`)
lines.push(`output_path = "${terminal.outputPath}"`)
const postHook = terminal.postHook || `${TemplateRegistry.colorsApplyScript} ${terminal.id}`
lines.push(`post_hook = "${postHook}"`)
lines.push(`\n[templates.${terminal.id}]`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${terminal.matugenPath}"`);
lines.push(`output_path = "${terminal.outputPath}"`);
const postHook = terminal.postHook || `${TemplateRegistry.colorsApplyScript} ${terminal.id}`;
lines.push(`post_hook = "${postHook}"`);
}
})
});
}
function addApplicationTemplates(lines, mode) {
@@ -108,11 +105,11 @@ Singleton {
app.clients.forEach(client => {
// Check if this specific client is detected
if (isDiscordClientEnabled(client.name)) {
lines.push(`\n[templates.discord_${client.name}]`)
lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`)
lines.push(`output_path = "${client.path}/themes/noctalia.theme.css"`)
lines.push(`\n[templates.discord_${client.name}]`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`);
lines.push(`output_path = "${client.path}/themes/noctalia.theme.css"`);
}
})
});
}
} else if (app.id === "code") {
// Handle Code clients specially
@@ -120,221 +117,219 @@ Singleton {
app.clients.forEach(client => {
// Check if this specific client is detected
if (isCodeClientEnabled(client.name)) {
lines.push(`\n[templates.code_${client.name}]`)
lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`)
lines.push(`output_path = "${client.path}"`)
lines.push(`\n[templates.code_${client.name}]`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`);
lines.push(`output_path = "${client.path}"`);
}
})
});
}
} else {
// Handle regular apps
if (Settings.data.templates[app.id]) {
app.outputs.forEach((output, idx) => {
lines.push(`\n[templates.${app.id}_${idx}]`)
lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`)
lines.push(`output_path = "${output.path}"`)
lines.push(`\n[templates.${app.id}_${idx}]`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`);
lines.push(`output_path = "${output.path}"`);
if (app.postProcess) {
lines.push(`post_hook = "${app.postProcess(mode)}"`)
lines.push(`post_hook = "${app.postProcess(mode)}"`);
}
})
});
}
}
})
});
}
function isDiscordClientEnabled(clientName) {
// Check ProgramCheckerService to see if client is detected
for (var i = 0; i < ProgramCheckerService.availableDiscordClients.length; i++) {
if (ProgramCheckerService.availableDiscordClients[i].name === clientName) {
return true
return true;
}
}
return false
return false;
}
function isCodeClientEnabled(clientName) {
// Check ProgramCheckerService to see if client is detected
for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) {
if (ProgramCheckerService.availableCodeClients[i].name === clientName) {
return true
return true;
}
}
return false
return false;
}
function buildMatugenScript(content, wallpaper, mode) {
const delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9)
const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''")
const delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9);
const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''");
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)
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"
return script + "\n";
}
// ================================================================================
// PREDEFINED SCHEME GENERATION (sed scripts)
// ================================================================================
function processAllTemplates(colors, mode) {
let script = ""
const homeDir = Quickshell.env("HOME")
let script = "";
const homeDir = Quickshell.env("HOME");
TemplateRegistry.applications.forEach(app => {
if (app.id === "discord") {
if (Settings.data.templates.discord) {
script += processDiscordClients(app, colors, mode, homeDir)
script += processDiscordClients(app, colors, mode, homeDir);
}
} else if (app.id === "code") {
if (Settings.data.templates.code) {
script += processCodeClients(app, colors, mode, homeDir)
script += processCodeClients(app, colors, mode, homeDir);
}
} else {
if (Settings.data.templates[app.id]) {
script += processTemplate(app, colors, mode, homeDir)
script += processTemplate(app, colors, mode, homeDir);
}
}
})
return script
});
return script;
}
function processDiscordClients(discordApp, colors, mode, homeDir) {
let script = ""
const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false)
let script = "";
const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false);
discordApp.clients.forEach(client => {
if (!isDiscordClientEnabled(client.name))
return
return;
const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${discordApp.input}`;
const outputPath = `${client.path}/themes/noctalia.theme.css`.replace("~", homeDir);
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
const baseConfigDir = outputDir.replace("/themes", "");
const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${discordApp.input}`
const outputPath = `${client.path}/themes/noctalia.theme.css`.replace("~", homeDir)
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'))
const baseConfigDir = outputDir.replace("/themes", "")
script += `\n`;
script += `if [ -d "${baseConfigDir}" ]; then\n`;
script += ` mkdir -p ${outputDir}\n`;
script += ` cp '${templatePath}' '${outputPath}'\n`;
script += ` ${replaceColorsInFile(outputPath, palette)}`;
script += `else\n`;
script += ` echo "Discord client ${client.name} not found at ${baseConfigDir}, skipping"\n`;
script += `fi\n`;
});
script += `\n`
script += `if [ -d "${baseConfigDir}" ]; then\n`
script += ` mkdir -p ${outputDir}\n`
script += ` cp '${templatePath}' '${outputPath}'\n`
script += ` ${replaceColorsInFile(outputPath, palette)}`
script += `else\n`
script += ` echo "Discord client ${client.name} not found at ${baseConfigDir}, skipping"\n`
script += `fi\n`
})
return script
return script;
}
function processCodeClients(codeApp, colors, mode, homeDir) {
let script = ""
const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false)
let script = "";
const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false);
codeApp.clients.forEach(client => {
if (!isCodeClientEnabled(client.name))
return
const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${codeApp.input}`
const outputPath = client.path.replace("~", homeDir)
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'))
return;
const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${codeApp.input}`;
const outputPath = client.path.replace("~", homeDir);
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
// Extract base config directory for checking
var baseConfigDir = ""
var baseConfigDir = "";
if (client.name === "code") {
baseConfigDir = "~/.vscode".replace("~", homeDir)
baseConfigDir = "~/.vscode".replace("~", homeDir);
} else if (client.name === "codium") {
baseConfigDir = "~/.vscode-oss".replace("~", homeDir)
baseConfigDir = "~/.vscode-oss".replace("~", homeDir);
}
script += `\n`
script += `if [ -d "${baseConfigDir}" ]; then\n`
script += ` mkdir -p ${outputDir}\n`
script += ` cp '${templatePath}' '${outputPath}'\n`
script += ` ${replaceColorsInFile(outputPath, palette)}`
script += `else\n`
script += ` echo "Code client ${client.name} not found at ${baseConfigDir}, skipping"\n`
script += `fi\n`
})
script += `\n`;
script += `if [ -d "${baseConfigDir}" ]; then\n`;
script += ` mkdir -p ${outputDir}\n`;
script += ` cp '${templatePath}' '${outputPath}'\n`;
script += ` ${replaceColorsInFile(outputPath, palette)}`;
script += `else\n`;
script += ` echo "Code client ${client.name} not found at ${baseConfigDir}, skipping"\n`;
script += `fi\n`;
});
return script
return script;
}
function processTemplate(app, colors, mode, homeDir) {
const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, app.strict || false)
let script = ""
const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, app.strict || false);
let script = "";
app.outputs.forEach(output => {
const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}`
const outputPath = output.path.replace("~", homeDir)
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'))
const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}`;
const outputPath = output.path.replace("~", homeDir);
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
script += `\n`
script += `mkdir -p ${outputDir}\n`
script += `cp '${templatePath}' '${outputPath}'\n`
script += replaceColorsInFile(outputPath, palette)
script += `\n`
})
script += `\n`;
script += `mkdir -p ${outputDir}\n`;
script += `cp '${templatePath}' '${outputPath}'\n`;
script += replaceColorsInFile(outputPath, palette);
script += `\n`;
});
if (app.postProcess) {
script += app.postProcess(mode)
script += app.postProcess(mode);
}
return script
return script;
}
function replaceColorsInFile(filePath, colors) {
let script = ""
let script = "";
Object.keys(colors).forEach(colorKey => {
const hexValue = colors[colorKey].default.hex
const hexStrippedValue = colors[colorKey].default.hex_stripped
const hexValue = colors[colorKey].default.hex;
const hexStrippedValue = colors[colorKey].default.hex_stripped;
const escapedHex = hexValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const escapedHexStripped = hexStrippedValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const escapedHex = hexValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const escapedHexStripped = hexStrippedValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// replace hex_stripped
script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex_stripped}}/${escapedHexStripped}/g' '${filePath}'\n`
script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex_stripped}}/${escapedHexStripped}/g' '${filePath}'\n`;
// replace hex
script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex}}/${escapedHex}/g' '${filePath}'\n`
})
return script
script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex}}/${escapedHex}/g' '${filePath}'\n`;
});
return script;
}
// ================================================================================
// TERMINAL THEMES (predefined schemes use pre-rendered files)
// ================================================================================
function handleTerminalThemes(mode) {
const commands = []
const homeDir = Quickshell.env("HOME")
const commands = [];
const homeDir = Quickshell.env("HOME");
Object.keys(terminalPaths).forEach(terminal => {
if (Settings.data.templates[terminal]) {
const outputPath = terminalPaths[terminal].replace("~", homeDir)
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'))
const templatePath = getTerminalColorsTemplate(terminal, mode)
const outputPath = terminalPaths[terminal].replace("~", homeDir);
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
const templatePath = getTerminalColorsTemplate(terminal, mode);
commands.push(`mkdir -p ${outputDir}`)
commands.push(`cp -f ${templatePath} ${outputPath}`)
commands.push(`${TemplateRegistry.colorsApplyScript} ${terminal}`)
commands.push(`mkdir -p ${outputDir}`);
commands.push(`cp -f ${templatePath} ${outputPath}`);
commands.push(`${TemplateRegistry.colorsApplyScript} ${terminal}`);
}
})
});
if (commands.length > 0) {
copyProcess.command = ["bash", "-lc", commands.join('; ')]
copyProcess.running = true
copyProcess.command = ["bash", "-lc", commands.join('; ')];
copyProcess.running = true;
}
}
function getTerminalColorsTemplate(terminal, mode) {
let colorScheme = Settings.data.colorSchemes.predefinedScheme
colorScheme = schemeNameMap[colorScheme] || colorScheme
let colorScheme = Settings.data.colorSchemes.predefinedScheme;
colorScheme = schemeNameMap[colorScheme] || colorScheme;
let extension = ""
let extension = "";
if (terminal === 'kitty') {
extension = ".conf"
extension = ".conf";
} else if (terminal === 'wezterm') {
extension = ".toml"
extension = ".toml";
}
return `${Quickshell.shellDir}/Assets/ColorScheme/${colorScheme}/terminal/${terminal}/${colorScheme}-${mode}${extension}`
return `${Quickshell.shellDir}/Assets/ColorScheme/${colorScheme}/terminal/${terminal}/${colorScheme}-${mode}${extension}`;
}
// ================================================================================
@@ -342,43 +337,43 @@ Singleton {
// ================================================================================
function buildUserTemplateCommand(input, mode) {
if (!Settings.data.templates.enableUserTemplates)
return ""
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"
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
return script;
}
function buildUserTemplateCommandForPredefined(schemeData, mode) {
if (!Settings.data.templates.enableUserTemplates)
return ""
return "";
const userConfigPath = getUserConfigPath()
const colors = schemeData[mode]
const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false)
const userConfigPath = getUserConfigPath();
const colors = schemeData[mode];
const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false);
const tempJsonPath = Settings.cacheDir + "predefined-colors.json"
const tempJsonPathEsc = tempJsonPath.replace(/'/g, "'\\''")
const tempJsonPath = Settings.cacheDir + "predefined-colors.json";
const tempJsonPathEsc = tempJsonPath.replace(/'/g, "'\\''");
let script = "\n# Execute user templates with predefined scheme colors\n"
script += `if [ -f '${userConfigPath}' ]; then\n`
script += ` cat > '${tempJsonPathEsc}' << 'EOF'\n`
let script = "\n# Execute user templates with predefined scheme colors\n";
script += `if [ -f '${userConfigPath}' ]; then\n`;
script += ` cat > '${tempJsonPathEsc}' << 'EOF'\n`;
script += JSON.stringify({
"colors": palette
}, null, 2) + "\n"
script += "EOF\n"
script += ` matugen json '${tempJsonPathEsc}' --config '${userConfigPath}' --mode ${mode}\n`
script += "fi"
}, null, 2) + "\n";
script += "EOF\n";
script += ` matugen json '${tempJsonPathEsc}' --config '${userConfigPath}' --mode ${mode}\n`;
script += "fi";
return script
return script;
}
function getUserConfigPath() {
return (Settings.configDir + "user-templates.toml").replace(/'/g, "'\\''")
return (Settings.configDir + "user-templates.toml").replace(/'/g, "'\\''");
}
// ================================================================================
@@ -393,30 +388,30 @@ Singleton {
property string generator: ""
function buildErrorMessage() {
const description = (stderr.text && stderr.text.trim() !== "") ? stderr.text.trim() : ((stdout.text && stdout.text.trim() !== "") ? stdout.text.trim() : I18n.tr("toast.theming-processor-failed.desc-generic"))
const title = I18n.tr(`toast.theming-processor-failed.title-${generator}`)
return description
const description = (stderr.text && stderr.text.trim() !== "") ? stderr.text.trim() : ((stdout.text && stdout.text.trim() !== "") ? stdout.text.trim() : I18n.tr("toast.theming-processor-failed.desc-generic"));
const title = I18n.tr(`toast.theming-processor-failed.title-${generator}`);
return description;
}
onExited: function (exitCode) {
if (exitCode !== 0) {
const description = generateProcess.buildErrorMessage()
Logger.e("TemplateProcessor", "Process failed with exit code", exitCode, description)
const description = generateProcess.buildErrorMessage();
Logger.e("TemplateProcessor", "Process failed with exit code", exitCode, description);
}
}
stdout: StdioCollector {
onStreamFinished: {
if (this.text)
Logger.d("TemplateProcessor", "stdout:", this.text)
Logger.d("TemplateProcessor", "stdout:", this.text);
}
}
stderr: StdioCollector {
onStreamFinished: {
if (this.text) {
const description = generateProcess.buildErrorMessage()
Logger.e("TemplateProcessor", "Process failed", description)
const description = generateProcess.buildErrorMessage();
Logger.e("TemplateProcessor", "Process failed", description);
}
}
}
@@ -430,7 +425,7 @@ Singleton {
stderr: StdioCollector {
onStreamFinished: {
if (this.text) {
Logger.e("TemplateProcessor", "copyProcess stderr:", this.text)
Logger.e("TemplateProcessor", "copyProcess stderr:", this.text);
}
}
}
+139 -93
View File
@@ -11,164 +11,210 @@ Singleton {
readonly property string colorsApplyScript: Quickshell.shellDir + '/Bin/colors-apply.sh'
// Terminal configurations (for wallpaper-based matugen templates)
readonly property var terminals: [{
readonly property var terminals: [
{
"id": "foot",
"name": "Foot",
"matugenPath": "Terminal/foot",
"outputPath": "~/.config/foot/themes/noctalia"
}, {
},
{
"id": "ghostty",
"name": "Ghostty",
"matugenPath": "Terminal/ghostty",
"outputPath": "~/.config/ghostty/themes/noctalia",
"postHook": "bash -c 'pgrep -f ghostty >/dev/null && pkill -SIGUSR2 ghostty || true'"
}, {
},
{
"id": "kitty",
"name": "Kitty",
"matugenPath": "Terminal/kitty.conf",
"outputPath": "~/.config/kitty/themes/noctalia.conf"
}, {
},
{
"id": "alacritty",
"name": "Alacritty",
"matugenPath": "Terminal/alacritty.toml",
"outputPath": "~/.config/alacritty/themes/noctalia.toml"
}, {
},
{
"id": "wezterm",
"name": "Wezterm",
"matugenPath": "Terminal/wezterm.toml",
"outputPath": "~/.config/wezterm/colors/Noctalia.toml",
"postHook": "touch ~/.config/wezterm/wezterm.lua"
}]
}
]
// Application configurations - consolidated from MatugenTemplates + AppThemeService
readonly property var applications: [{
readonly property var applications: [
{
"id": "gtk",
"name": "GTK",
"category": "ui",
"input": "gtk.css",
"outputs": [{
"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}`
}, {
},
{
"id": "qt",
"name": "Qt",
"category": "ui",
"input": "qtct.conf",
"outputs": [{
"outputs": [
{
"path": "~/.config/qt5ct/colors/noctalia.conf"
}, {
},
{
"path": "~/.config/qt6ct/colors/noctalia.conf"
}]
}, {
}
]
},
{
"id": "kcolorscheme",
"name": "KColorScheme",
"category": "ui",
"input": "kcolorscheme.colors",
"outputs": [{
"outputs": [
{
"path": "~/.local/share/color-schemes/noctalia.colors"
}]
}, {
}
]
},
{
"id": "fuzzel",
"name": "Fuzzel",
"category": "launchers",
"input": "fuzzel.conf",
"outputs": [{
"outputs": [
{
"path": "~/.config/fuzzel/themes/noctalia"
}],
}
],
"postProcess": () => `${colorsApplyScript} fuzzel`
}, {
},
{
"id": "vicinae",
"name": "Vicinae",
"category": "launchers",
"input": "vicinae.toml",
"outputs": [{
"outputs": [
{
"path": "~/.local/share/vicinae/themes/matugen.toml"
}],
}
],
"postProcess": () => `cp --update=none ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae`
}, {
},
{
"id": "walker",
"name": "Walker",
"category": "launchers",
"input": "walker.css",
"outputs": [{
"outputs": [
{
"path": "~/.config/walker/themes/noctalia/style.css"
}],
}
],
"postProcess": () => `${colorsApplyScript} walker`,
"strict": true // Use strict mode for palette generation (preserves custom surface/outline values)
}, {
},
{
"id": "pywalfox",
"name": "Pywalfox",
"category": "applications",
"input": "pywalfox.json",
"outputs": [{
"outputs": [
{
"path": "~/.cache/wal/colors.json"
}],
}
],
"postProcess": () => `${colorsApplyScript} pywalfox`
}, // CONSOLIDATED DISCORD CLIENTS
} // CONSOLIDATED DISCORD CLIENTS
,
{
"id": "discord",
"name": "Discord",
"category": "applications",
"input": "vesktop.css",
"clients": [{
"clients": [
{
"name": "vesktop",
"path": "~/.config/vesktop",
"requiresThemesFolder": false
}, {
},
{
"name": "webcord",
"path": "~/.config/webcord",
"requiresThemesFolder": false
}, {
},
{
"name": "armcord",
"path": "~/.config/armcord",
"requiresThemesFolder": false
}, {
},
{
"name": "equibop",
"path": "~/.config/equibop",
"requiresThemesFolder": false
}, {
},
{
"name": "lightcord",
"path": "~/.config/lightcord",
"requiresThemesFolder": false
}, {
},
{
"name": "dorion",
"path": "~/.config/dorion",
"requiresThemesFolder": false
}, {
},
{
"name": "vencord",
"path": "~/.config/Vencord",
"requiresThemesFolder": false
}]
}, {
}
]
},
{
"id": "code",
"name": "VSCode",
"category": "applications",
"input": "code.json",
"clients": [{
"clients": [
{
"name": "code",
"path": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json"
}, {
},
{
"name": "codium",
"path": "~/.vscode-oss/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json"
}]
}, {
}
]
},
{
"id": "spicetify",
"name": "Spicetify",
"category": "applications",
"input": "spicetify.ini",
"outputs": [{
"outputs": [
{
"path": "~/.config/spicetify/Themes/Comfy/color.ini"
}],
}
],
"postProcess": () => `spicetify -q apply --no-restart`
}]
}
]
// Extract Discord clients for ProgramCheckerService compatibility
readonly property var discordClients: {
var clients = []
var discordApp = applications.find(app => app.id === "discord")
var clients = [];
var discordApp = applications.find(app => app.id === "discord");
if (discordApp && discordApp.clients) {
discordApp.clients.forEach(client => {
clients.push({
@@ -176,87 +222,87 @@ Singleton {
"configPath": client.path,
"themePath": `${client.path}/themes/noctalia.theme.css`,
"requiresThemesFolder": client.requiresThemesFolder || false
})
})
});
});
}
return clients
return clients;
}
// Extract Code clients for ProgramCheckerService compatibility
readonly property var codeClients: {
var clients = []
var codeApp = applications.find(app => app.id === "code")
var clients = [];
var codeApp = applications.find(app => app.id === "code");
if (codeApp && codeApp.clients) {
codeApp.clients.forEach(client => {
// Extract base config directory from theme path
var themePath = client.path
var baseConfigDir = ""
var themePath = client.path;
var baseConfigDir = "";
if (client.name === "code") {
// For VSCode: ~/.vscode/extensions/... -> ~/.vscode
baseConfigDir = "~/.vscode"
baseConfigDir = "~/.vscode";
} else if (client.name === "codium") {
// For VSCodium: ~/.vscode-oss/extensions/... -> ~/.vscode-oss
baseConfigDir = "~/.vscode-oss"
baseConfigDir = "~/.vscode-oss";
}
clients.push({
"name": client.name,
"configPath": baseConfigDir,
"themePath": themePath,
"requiresThemesFolder": false
})
})
});
});
}
return clients
return clients;
}
// Build user templates TOML content
function buildUserTemplatesToml() {
var lines = []
lines.push("[config]")
lines.push("")
lines.push("[templates]")
lines.push("")
lines.push("# User-defined templates")
lines.push("# Add your custom templates below")
lines.push("# Example:")
lines.push("# [templates.myapp]")
lines.push("# input_path = \"~/.config/noctalia/templates/myapp.css\"")
lines.push("# output_path = \"~/.config/myapp/theme.css\"")
lines.push("# post_hook = \"myapp --reload-theme\"")
lines.push("")
lines.push("# Remove this section and add your own templates")
lines.push("#[templates.placeholder]")
lines.push("#input_path = \"" + Quickshell.shellDir + "/Assets/MatugenTemplates/noctalia.json\"")
lines.push("#output_path = \"" + Settings.cacheDir + "placeholder.json\"")
lines.push("")
var lines = [];
lines.push("[config]");
lines.push("");
lines.push("[templates]");
lines.push("");
lines.push("# User-defined templates");
lines.push("# Add your custom templates below");
lines.push("# Example:");
lines.push("# [templates.myapp]");
lines.push("# input_path = \"~/.config/noctalia/templates/myapp.css\"");
lines.push("# output_path = \"~/.config/myapp/theme.css\"");
lines.push("# post_hook = \"myapp --reload-theme\"");
lines.push("");
lines.push("# Remove this section and add your own templates");
lines.push("#[templates.placeholder]");
lines.push("#input_path = \"" + Quickshell.shellDir + "/Assets/MatugenTemplates/noctalia.json\"");
lines.push("#output_path = \"" + Settings.cacheDir + "placeholder.json\"");
lines.push("");
return lines.join("\n") + "\n"
return lines.join("\n") + "\n";
}
// Write user templates TOML file (moved from MatugenTemplates)
function writeUserTemplatesToml() {
var userConfigPath = Settings.configDir + "user-templates.toml"
var userConfigPath = Settings.configDir + "user-templates.toml";
// Check if file already exists
fileCheckProcess.command = ["test", "-f", userConfigPath]
fileCheckProcess.running = true
fileCheckProcess.command = ["test", "-f", userConfigPath];
fileCheckProcess.running = true;
}
function doWriteUserTemplatesToml() {
var userConfigPath = Settings.configDir + "user-templates.toml"
var configContent = buildUserTemplatesToml()
var userConfigPathEsc = userConfigPath.replace(/'/g, "'\\''")
var userConfigPath = Settings.configDir + "user-templates.toml";
var configContent = buildUserTemplatesToml();
var userConfigPathEsc = userConfigPath.replace(/'/g, "'\\''");
// Ensure directory exists (should already exist but just in case)
Quickshell.execDetached(["mkdir", "-p", Settings.configDir])
Quickshell.execDetached(["mkdir", "-p", Settings.configDir]);
// Write the config file using heredoc to avoid escaping issues
var script = `cat > '${userConfigPathEsc}' << 'EOF'\n`
script += configContent
script += "EOF\n"
Quickshell.execDetached(["sh", "-c", script])
var script = `cat > '${userConfigPathEsc}' << 'EOF'\n`;
script += configContent;
script += "EOF\n";
Quickshell.execDetached(["sh", "-c", script]);
Logger.d("TemplateRegistry", "User templates config written to:", userConfigPath)
Logger.d("TemplateRegistry", "User templates config written to:", userConfigPath);
}
// Process for checking if user templates file exists
@@ -267,10 +313,10 @@ Singleton {
onExited: function (exitCode) {
if (exitCode === 0) {
// File exists, skip creation
Logger.d("TemplateRegistry", "User templates config already exists, skipping creation")
Logger.d("TemplateRegistry", "User templates config already exists, skipping creation");
} else {
// File doesn't exist, create it
doWriteUserTemplatesToml()
doWriteUserTemplatesToml();
}
}
}