This commit is contained in:
Lysec
2026-01-18 16:24:57 +01:00
parent 385b2d57f2
commit 1410269dfd
52 changed files with 51 additions and 67 deletions
@@ -1,4 +1,4 @@
;;; noctalia-theme.el --- Theme using Matugen SCSS variables
;;; noctalia-theme.el --- Theme using Template SCSS variables
;; Copyright (C) 2025
@@ -9,7 +9,7 @@
;;; Commentary:
;; A theme using Matugen SCSS variables with quality of life improvements:
;; A theme using Template SCSS variables with quality of life improvements:
;; - Better source block distinction
;; - Improved text visibility when selected
;; - Refined org-mode styling with hidden asterisks
@@ -18,7 +18,7 @@
;;; Code:
(deftheme noctalia "Theme using Matugen variables with quality of life improvements.")
(deftheme noctalia "Theme using Template variables with quality of life improvements.")
;; Define all the color variables (replaced by template processor)
(let* ((bg "{{colors.background.default.hex}}")
@@ -1,5 +1,5 @@
# Fuzzel Colors
# Generated with Matugen
# Generated with Template Processor
[colors]
background={{colors.background.default.hex_stripped}}CC
@@ -1,6 +1,6 @@
/*
* GTK Colors
* Generated with Matugen
* Generated with Template Processor
*/
@define-color accent_color {{colors.primary.default.hex}};
@@ -113,14 +113,14 @@ hint = "hint"
"diagnostic.unnecessary" = { modifiers = ["dim"] }
[palette]
# Constants to be used in syntax highlighting, not meant for matugen.
# Constants to be used in syntax highlighting, not meant for template processor.
warning = "#f9e2af"
info = "#89dceb"
hint = "#94e2d5"
plus = "#a6e3a1"
delta = "#89b4fa"
# Matugen colors
# Template colors
primary = "{{colors.primary.default.hex}}"
surfaceTint = "{{colors.primary.default.hex}}"
onPrimary = "{{colors.on_primary.default.hex}}"
@@ -2,7 +2,7 @@
contrast=4
[General]
ColorScheme=Matugen
ColorScheme=Noctalia
Name=noctalia
[ColorEffects:Disabled]
@@ -1,5 +1,5 @@
// Material You theme for Telegram Desktop
// Generated by matugen
// Generated by Template Processor
COLOR_GRAY: {{colors.outline.default.hex}};
COLOR_DARK: {{colors.surface_variant.default.hex}};
+1 -3
View File
@@ -381,13 +381,11 @@
"schedulingMode": "off",
"manualSunrise": "06:30",
"manualSunset": "18:30",
"matugenSchemeType": "scheme-fruit-salad",
"generationBackend": "matugen",
"internalThemerMode": "material"
},
"templates": {
"activeTemplates": [],
"enableUserTemplates": false
"enableUserTheming": false
},
"nightLight": {
"enabled": false,
-3
View File
@@ -24,9 +24,6 @@ Noctalia Shell is made possible by the incredible work of many open-source proje
- **[gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/)** - Hardware-accelerated screen recording
- **[Cava](https://github.com/karlstav/cava)** - Audio visualizer component
### Theming & Appearance
- **[Matugen](https://github.com/InioX/matugen)** - Material You color scheme generation from wallpapers
### Utilities
- **[cliphist](https://github.com/sentriz/cliphist)** - Clipboard history support
+1 -2
View File
@@ -602,7 +602,6 @@ Singleton {
property string schedulingMode: "off"
property string manualSunrise: "06:30"
property string manualSunset: "18:30"
property string generationBackend: "internal"
property string internalThemerMode: "material"
}
@@ -610,7 +609,7 @@ Singleton {
property JsonObject templates: JsonObject {
property list<var> activeTemplates: []
// Format: [{ "id": "gtk", "enabled": true }, { "id": "qt", "enabled": true }, ...]
property bool enableUserTemplates: false
property bool enableUserTheming: false
}
// night light
@@ -1457,6 +1457,9 @@ def main() -> int:
return 1
# Initialize result dictionary
result: dict[str, dict[str, str]] = {}
# Check if input is a JSON palette (Predefined Scheme bypass)
if args.image.suffix.lower() == '.json':
try:
@@ -1478,8 +1481,6 @@ def main() -> int:
# Best effort fallback
flat_colors[k] = str(v)
# Pre-populate result (bypass extraction)
result = {}
# Assign to both/all modes since predefined scheme usually provides the correct palette for the requested mode
result["dark"] = flat_colors
result["light"] = flat_colors
@@ -1531,7 +1532,6 @@ def main() -> int:
arg_light = True
arg_both = False
result: dict[str, dict[str, str]] = {}
if palette:
if arg_dark:
+3 -3
View File
@@ -19,9 +19,9 @@ Singleton {
property string cacheFile: Settings.cacheDir + "calendar.json"
// Python scripts
readonly property string checkCalendarAvailableScript: Quickshell.shellDir + '/Bin/calendar/check-calendar.py'
readonly property string listCalendarsScript: Quickshell.shellDir + '/Bin/calendar/list-calendars.py'
readonly property string calendarEventsScript: Quickshell.shellDir + '/Bin/calendar/calendar-events.py'
readonly property string checkCalendarAvailableScript: Quickshell.shellDir + '/Scripts/calendar/check-calendar.py'
readonly property string listCalendarsScript: Quickshell.shellDir + '/Scripts/calendar/list-calendars.py'
readonly property string calendarEventsScript: Quickshell.shellDir + '/Scripts/calendar/calendar-events.py'
// Cache file handling
FileView {
+1 -1
View File
@@ -540,7 +540,7 @@ Singleton {
_pauseDiscoveryFor(totalPauseMs);
// Prefer external dev script for pairing/connecting; executed detached
const scriptPath = Quickshell.shellDir + "/Bin/network/bluetooth-connect.sh";
const scriptPath = Quickshell.shellDir + "/Scripts/network/bluetooth-connect.sh";
// Use bash explicitly to avoid relying on executable bit in all environments
btExec(["bash", scriptPath, String(addr), String(pairWait), String(attempts), String(intervalSec)]);
}
+1 -1
View File
@@ -8,7 +8,7 @@ import qs.Services.UI
Singleton {
id: root
readonly property string colorsApplyScript: Quickshell.shellDir + '/Bin/colors-apply.sh'
readonly property string colorsApplyScript: Quickshell.shellDir + '/Scripts/theming/template-apply.sh'
Connections {
target: WallpaperService
+28 -38
View File
@@ -59,12 +59,8 @@ Singleton {
return;
const wp = wallpaperPath.replace(/'/g, "'\\''");
// Always use internal backend (colors.py)
let backend = "internal";
const script = buildGenerationScript(content, wp, mode);
const script = buildGenerationScript(content, wp, mode, backend);
generateProcess.generator = backend;
generateProcess.command = ["sh", "-lc", script];
generateProcess.running = true;
}
@@ -145,10 +141,10 @@ Singleton {
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
if (Settings.data.colorSchemes.useWallpaperColors) {
addWallpaperTemplates(lines, mode);
addWallpaperTheming(lines, mode);
}
addApplicationTemplates(lines, mode);
addApplicationTheming(lines, mode);
if (lines.length > 0) {
return ["[config]"].concat(lines).join("\n") + "\n";
@@ -156,18 +152,18 @@ Singleton {
return "";
}
function addWallpaperTemplates(lines, mode) {
function addWallpaperTheming(lines, mode) {
const homeDir = Quickshell.env("HOME");
// Noctalia colors JSON
lines.push("[templates.noctalia]");
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Theming/noctalia.json"');
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Templates/noctalia.json"');
lines.push('output_path = "' + Settings.configDir + 'colors.json"');
// Terminal templates
TemplateRegistry.terminals.forEach(terminal => {
if (isTemplateEnabled(terminal.id)) {
lines.push(`\n[templates.${terminal.id}]`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Theming/${terminal.templatePath}"`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${terminal.templatePath}"`);
const outputPath = terminal.outputPath.replace("~", homeDir);
lines.push(`output_path = "${outputPath}"`);
const postHook = terminal.postHook || `${TemplateRegistry.colorsApplyScript} ${terminal.id}`;
@@ -177,7 +173,7 @@ Singleton {
});
}
function addApplicationTemplates(lines, mode) {
function addApplicationTheming(lines, mode) {
const homeDir = Quickshell.env("HOME");
TemplateRegistry.applications.forEach(app => {
if (app.id === "discord") {
@@ -187,7 +183,7 @@ Singleton {
// 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/Theming/${app.input}"`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${app.input}"`);
const outputPath = client.path.replace("~", homeDir) + "/themes/noctalia.theme.css";
lines.push(`output_path = "${outputPath}"`);
}
@@ -200,7 +196,7 @@ Singleton {
// 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/Theming/${app.input}"`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${app.input}"`);
const expandedPath = client.path.replace("~", homeDir);
lines.push(`output_path = "${expandedPath}"`);
}
@@ -216,7 +212,7 @@ Singleton {
const doomDir = doomPath.substring(0, doomPath.lastIndexOf('/'));
lines.push(`\n[templates.emacs]`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Theming/${app.input}"`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${app.input}"`);
lines.push(`output_path = "${standardPath}"`);
// Move to doom if doom exists, then remove empty .emacs.d/themes and .emacs.d directories
// Check directories are empty before removing
@@ -230,7 +226,7 @@ Singleton {
app.outputs.forEach((output, idx) => {
lines.push(`\n[templates.${app.id}_${idx}]`);
const inputFile = output.input || app.input;
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Theming/${inputFile}"`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${inputFile}"`);
const outputPath = output.path.replace("~", homeDir);
lines.push(`output_path = "${outputPath}"`);
if (app.postProcess) {
@@ -263,7 +259,7 @@ Singleton {
return false;
}
function buildGenerationScript(content, wallpaper, mode, backend) {
function buildGenerationScript(content, wallpaper, mode) {
const delimiter = "THEME_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9);
const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''");
const wpDelimiter = "WALLPAPER_PATH_EOF_" + Math.random().toString(36).substr(2, 9);
@@ -272,13 +268,13 @@ Singleton {
let script = `cat > '${pathEsc}' << '${delimiter}'\n${content}\n${delimiter}\n`;
script += `NOCTALIA_WP_PATH=$(cat << '${wpDelimiter}'\n${wallpaper}\n${wpDelimiter}\n)\n`;
// Use colors.py (Python implementation)
const scriptPath = Quickshell.shellDir + "/Bin/theming/template-processor.py";
// Use template-processor.py (Python implementation)
const scriptPath = Quickshell.shellDir + "/Scripts/theming/template-processor.py";
const styleFlag = (Settings.data.colorSchemes.internalThemerMode === "normal") ? "--normal" : "--material";
// We pass --type for compatibility but it is ignored by internal logic unless needed
script += `python3 "${scriptPath}" "$NOCTALIA_WP_PATH" ${styleFlag} --config '${pathEsc}' --mode ${mode} `;
script += buildUserTemplateCommand("$NOCTALIA_WP_PATH", mode, backend);
script += buildUserTemplateCommand("$NOCTALIA_WP_PATH", mode);
return script + "\n";
}
@@ -294,7 +290,7 @@ Singleton {
if (!isDiscordClientEnabled(client.name))
return;
const templatePath = `${Quickshell.shellDir}/Assets/Theming/${discordApp.input}`;
const templatePath = `${Quickshell.shellDir}/Assets/Templates/${discordApp.input}`;
const outputPath = `${client.path}/themes/noctalia.theme.css`.replace("~", homeDir);
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
const baseConfigDir = outputDir.replace("/themes", "");
@@ -324,7 +320,7 @@ Singleton {
if (!isCodeClientEnabled(client.name))
return;
const templatePath = `${Quickshell.shellDir}/Assets/Theming/${codeApp.input}`;
const templatePath = `${Quickshell.shellDir}/Assets/Templates/${codeApp.input}`;
const outputPath = client.path.replace("~", homeDir);
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
@@ -369,7 +365,7 @@ Singleton {
const doomConfigDir = doomDir.substring(0, doomDir.lastIndexOf('/'));
const standardPath = app.outputs[1].path.replace("~", homeDir);
const standardDir = standardPath.substring(0, standardPath.lastIndexOf('/'));
const templatePath = `${Quickshell.shellDir}/Assets/Theming/${app.input}`;
const templatePath = `${Quickshell.shellDir}/Assets/Templates/${app.input}`;
let script = "";
script += `if [ -d "${doomConfigDir}" ]; then\n`;
@@ -389,13 +385,13 @@ Singleton {
});
} else {
app.outputs.forEach((output, idx) => {
const templatePath = `${Quickshell.shellDir}/Assets/Theming/${app.input}`;
const templatePath = `${Quickshell.shellDir}/Assets/Templates/${app.input}`;
const outputPath = output.path.replace("~", homeDir);
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
let script = "";
script += `mkdir -p ${outputDir}\n`;
const templateFile = output.input ? `${Quickshell.shellDir}/Assets/Theming/${output.input}` : templatePath;
const templateFile = output.input ? `${Quickshell.shellDir}/Assets/Templates/${output.input}` : templatePath;
script += `cp '${templateFile}' '${outputPath}'\n`;
script += replaceColorsInFile(outputPath, palette);
if (hasDualModePatterns && darkPalette && lightPalette) {
@@ -555,8 +551,8 @@ Singleton {
// ================================================================================
// USER TEMPLATES, advanced usage
// ================================================================================
function buildUserTemplateCommand(input, mode, backend) {
if (!Settings.data.templates.enableUserTemplates)
function buildUserTemplateCommand(input, mode) {
if (!Settings.data.templates.enableUserTheming)
return "";
const userConfigPath = getUserConfigPath();
@@ -566,22 +562,16 @@ Singleton {
// Otherwise, use single quotes for safety with file paths
const inputQuoted = input.startsWith("$") ? `"${input}"` : `'${input.replace(/'/g, "'\\''")}'`;
if (backend === "internal") {
const scriptPath = Quickshell.shellDir + "/Bin/theming/template-processor.py";
const styleFlag = (Settings.data.colorSchemes.internalThemerMode === "normal") ? "--normal" : "--material";
script += ` python3 "${scriptPath}" ${inputQuoted} ${styleFlag} --config '${userConfigPath}' --mode ${mode}\n`;
} else {
// Fallback to internal if backend param is weird
const scriptPath = Quickshell.shellDir + "/Bin/theming/template-processor.py";
script += ` python3 "${scriptPath}" ${inputQuoted} --material --config '${userConfigPath}' --mode ${mode}\n`;
}
const scriptPath = Quickshell.shellDir + "/Scripts/theming/template-processor.py";
const styleFlag = (Settings.data.colorSchemes.internalThemerMode === "normal") ? "--normal" : "--material";
script += ` python3 "${scriptPath}" ${inputQuoted} ${styleFlag} --config '${userConfigPath}' --mode ${mode}\n`;
script += "fi";
return script;
}
function buildUserTemplateCommandForPredefined(schemeData, mode) {
if (!Settings.data.templates.enableUserTemplates)
if (!Settings.data.templates.enableUserTheming)
return "";
const userConfigPath = getUserConfigPath();
@@ -599,8 +589,8 @@ Singleton {
}, null, 2) + "\n";
script += "EOF\n";
const scriptPath = Quickshell.shellDir + "/Bin/theming/template-processor.py";
// Call colors.py with JSON file as first arg (it will detect extension)
const scriptPath = Quickshell.shellDir + "/Scripts/theming/template-processor.py";
// Call template-processor.py with JSON file as first arg (it will detect extension)
script += ` python3 "${scriptPath}" '${tempJsonPathEsc}' --config '${userConfigPath}' --mode ${mode}\n`;
script += "fi";
+2 -2
View File
@@ -8,7 +8,7 @@ import qs.Commons
Singleton {
id: root
readonly property string colorsApplyScript: Quickshell.shellDir + '/Bin/theming/template-apply.sh'
readonly property string colorsApplyScript: Quickshell.shellDir + '/Scripts/theming/template-apply.sh'
// Terminal configurations (for wallpaper-based templates)
readonly property var terminals: [
@@ -430,7 +430,7 @@ Singleton {
lines.push("");
lines.push("# Remove this section and add your own templates");
lines.push("#[templates.placeholder]");
lines.push("#input_path = \"" + Quickshell.shellDir + "/Assets/Theming/noctalia.json\"");
lines.push("#input_path = \"" + Quickshell.shellDir + "/Assets/Templates/noctalia.json\"");
lines.push("#output_path = \"" + Settings.cacheDir + "placeholder.json\"");
lines.push("");
+1 -1
View File
@@ -1,4 +1,4 @@
pre-commit:
jobs:
- name: format qml
run: cd "$(git rev-parse --show-toplevel)" && ./Bin/dev/qmlfmt.sh && git update-index --again
run: cd "$(git rev-parse --show-toplevel)" && ./Scripts/dev/qmlfmt.sh && git update-index --again
+1 -1
View File
@@ -33,7 +33,7 @@ let
/.github
/.gitignore
/Assets/Screenshots
/Bin/dev
/Scripts/dev
/nix
/LICENSE
/README.md