mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-06-08 12:52:24 +00:00
Switched to qmlformat.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user