mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
5cf22040b3
Add built-in Starship template that dynamically updates the prompt color palette when switching Noctalia color schemes. Includes both wallpaper-based and predefined template variants.
629 lines
18 KiB
QML
629 lines
18 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
Component.onCompleted: {
|
|
if (Settings.data.templates.enableUserTheming)
|
|
writeUserTemplatesToml();
|
|
}
|
|
|
|
readonly property string templateApplyScript: Quickshell.shellDir + '/Scripts/bash/template-apply.sh'
|
|
readonly property string gtkRefreshScript: Quickshell.shellDir + '/Scripts/python/src/theming/gtk-refresh.py'
|
|
readonly property string kdeApplyScript: Quickshell.shellDir + '/Scripts/python/src/theming/kde-apply-scheme.py'
|
|
readonly property string vscodeHelperScript: Quickshell.shellDir + '/Scripts/python/src/theming/vscode-helper.py'
|
|
|
|
// Dynamically resolved VSCode extension theme paths (all matching noctalia extensions)
|
|
property var resolvedCodePaths: []
|
|
property var resolvedCodiumPaths: []
|
|
|
|
// Terminal configurations (for wallpaper-based templates)
|
|
// Each terminal must define a postHook that sets up config includes and triggers reload
|
|
readonly property var terminals: [
|
|
{
|
|
"id": "foot",
|
|
"name": "Foot",
|
|
"templatePath": "terminal/foot",
|
|
"predefinedTemplatePath": "terminal/foot-predefined",
|
|
"outputPath": "~/.config/foot/themes/noctalia",
|
|
"postHook": `${templateApplyScript} foot`
|
|
},
|
|
{
|
|
"id": "ghostty",
|
|
"name": "Ghostty",
|
|
"templatePath": "terminal/ghostty",
|
|
"predefinedTemplatePath": "terminal/ghostty-predefined",
|
|
"outputPath": "~/.config/ghostty/themes/noctalia",
|
|
"postHook": `${templateApplyScript} ghostty`
|
|
},
|
|
{
|
|
"id": "kitty",
|
|
"name": "Kitty",
|
|
"templatePath": "terminal/kitty.conf",
|
|
"predefinedTemplatePath": "terminal/kitty-predefined.conf",
|
|
"outputPath": "~/.config/kitty/themes/noctalia.conf",
|
|
"postHook": `${templateApplyScript} kitty`
|
|
},
|
|
{
|
|
"id": "alacritty",
|
|
"name": "Alacritty",
|
|
"templatePath": "terminal/alacritty.toml",
|
|
"predefinedTemplatePath": "terminal/alacritty-predefined.toml",
|
|
"outputPath": "~/.config/alacritty/themes/noctalia.toml",
|
|
"postHook": `${templateApplyScript} alacritty`
|
|
},
|
|
{
|
|
"id": "wezterm",
|
|
"name": "Wezterm",
|
|
"templatePath": "terminal/wezterm.toml",
|
|
"predefinedTemplatePath": "terminal/wezterm-predefined.toml",
|
|
"outputPath": "~/.config/wezterm/colors/Noctalia.toml",
|
|
"postHook": `${templateApplyScript} wezterm`
|
|
},
|
|
{
|
|
"id": "starship",
|
|
"name": "Starship",
|
|
"templatePath": "terminal/starship.toml",
|
|
"predefinedTemplatePath": "terminal/starship-predefined.toml",
|
|
"outputPath": "~/.cache/noctalia/starship-palette.toml",
|
|
"postHook": `${templateApplyScript} starship`
|
|
}
|
|
]
|
|
|
|
// Application configurations - consolidated from Theming + AppThemeService
|
|
readonly property var applications: [
|
|
{
|
|
"id": "gtk",
|
|
"name": "GTK",
|
|
"category": "system",
|
|
"input": "gtk4.css",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/gtk-3.0/noctalia.css",
|
|
"input": "gtk3.css"
|
|
},
|
|
{
|
|
"path": "~/.config/gtk-4.0/noctalia.css",
|
|
"input": "gtk4.css"
|
|
}
|
|
],
|
|
"postProcess": mode => `python3 ${gtkRefreshScript} ${mode}`
|
|
},
|
|
{
|
|
"id": "qt",
|
|
"name": "Qt",
|
|
"category": "system",
|
|
"input": "qtct.conf",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/qt5ct/colors/noctalia.conf"
|
|
},
|
|
{
|
|
"path": "~/.config/qt6ct/colors/noctalia.conf"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "kcolorscheme",
|
|
"name": "KColorScheme",
|
|
"category": "system",
|
|
"input": "kcolorscheme.colors",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.local/share/color-schemes/noctalia.colors"
|
|
}
|
|
],
|
|
"postProcess": () => `${kdeApplyScript} noctalia`
|
|
},
|
|
{
|
|
"id": "fuzzel",
|
|
"name": "Fuzzel",
|
|
"category": "launcher",
|
|
"input": "fuzzel.conf",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/fuzzel/themes/noctalia"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} fuzzel`
|
|
},
|
|
{
|
|
"id": "vicinae",
|
|
"name": "Vicinae",
|
|
"category": "launcher",
|
|
"input": "vicinae.toml",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.local/share/vicinae/themes/noctalia.toml"
|
|
}
|
|
],
|
|
"postProcess": () => `cp --update=none ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${templateApplyScript} vicinae`
|
|
},
|
|
{
|
|
"id": "walker",
|
|
"name": "Walker",
|
|
"category": "launcher",
|
|
"input": "walker.css",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/walker/themes/noctalia/style.css"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} walker`,
|
|
"strict": true // Use strict mode for palette generation (preserves custom surface/outline values)
|
|
},
|
|
{
|
|
"id": "pywalfox",
|
|
"name": "Pywalfox",
|
|
"category": "browser",
|
|
"input": "pywalfox.json",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.cache/wal/colors.json"
|
|
}
|
|
],
|
|
"postProcess": mode => `${templateApplyScript} pywalfox ${mode}`
|
|
} // CONSOLIDATED DISCORD CLIENTS
|
|
,
|
|
{
|
|
"id": "discord",
|
|
"name": "Discord",
|
|
"category": "misc",
|
|
"input": ["discord-midnight.css", "discord-material.css"],
|
|
"clients": [
|
|
{
|
|
"name": "vesktop",
|
|
"path": "~/.config/vesktop"
|
|
},
|
|
{
|
|
"name": "webcord",
|
|
"path": "~/.config/webcord"
|
|
},
|
|
{
|
|
"name": "armcord",
|
|
"path": "~/.config/armcord"
|
|
},
|
|
{
|
|
"name": "equibop",
|
|
"path": "~/.config/equibop"
|
|
},
|
|
{
|
|
"name": "equicord",
|
|
"path": "~/.config/Equicord"
|
|
},
|
|
{
|
|
"name": "lightcord",
|
|
"path": "~/.config/lightcord"
|
|
},
|
|
{
|
|
"name": "dorion",
|
|
"path": "~/.config/dorion"
|
|
},
|
|
{
|
|
"name": "vencord",
|
|
"path": "~/.config/Vencord"
|
|
},
|
|
{
|
|
"name": "vencord-flatpak",
|
|
"path": "~/.var/app/com.discordapp.Discord/config/Vencord"
|
|
},
|
|
{
|
|
"name": "betterdiscord",
|
|
"path": "~/.config/BetterDiscord"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "code",
|
|
"name": "VSCode",
|
|
"category": "editor",
|
|
"input": "code.json",
|
|
"clients": [
|
|
{
|
|
"name": "code",
|
|
"path": "~/.vscode/extensions/noctalia.noctaliatheme-0.0.5/themes/NoctaliaTheme-color-theme.json"
|
|
},
|
|
{
|
|
"name": "codium",
|
|
"path": "~/.vscode-oss/extensions/noctalia.noctaliatheme-0.0.5-universal/themes/NoctaliaTheme-color-theme.json"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "zed",
|
|
"name": "Zed",
|
|
"category": "editor",
|
|
"input": "zed.json",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/zed/themes/noctalia.json"
|
|
}
|
|
],
|
|
"dualMode": true // Template contains both dark and light theme patterns
|
|
},
|
|
{
|
|
"id": "helix",
|
|
"name": "Helix",
|
|
"category": "editor",
|
|
"input": "helix.toml",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/helix/themes/noctalia.toml"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "spicetify",
|
|
"name": "Spicetify",
|
|
"category": "audio",
|
|
"input": "spicetify.ini",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/spicetify/Themes/Comfy/color.ini"
|
|
}
|
|
],
|
|
"postProcess": () => `spicetify -q apply --no-restart`
|
|
},
|
|
{
|
|
"id": "telegram",
|
|
"name": "Telegram",
|
|
"category": "misc",
|
|
"input": "telegram.tdesktop-theme",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/telegram-desktop/themes/noctalia.tdesktop-theme"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "zenBrowser",
|
|
"name": "Zen Browser",
|
|
"category": "browser",
|
|
"input": "zen-browser/zen-userChrome.css",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.cache/noctalia/zen-browser/zen-userChrome.css"
|
|
},
|
|
{
|
|
"path": "~/.cache/noctalia/zen-browser/zen-userContent.css",
|
|
"input": "zen-browser/zen-userContent.css"
|
|
}
|
|
],
|
|
"postProcess": ()
|
|
=> "sh -c 'CSS_CHROME=\"$HOME/.cache/noctalia/zen-browser/zen-userChrome.css\"; CSS_CONTENT=\"$HOME/.cache/noctalia/zen-browser/zen-userContent.css\"; LINE_CHROME=\"@import \\\"$CSS_CHROME\\\";\"; LINE_CONTENT=\"@import \\\"$CSS_CONTENT\\\";\"; find \"$HOME/.config/zen\" \"$HOME/.zen\" -mindepth 2 -maxdepth 2 -type d -name chrome -print0 2>/dev/null | while IFS= read -r -d \"\" dir; do USER_CHROME=\"$dir/userChrome.css\"; USER_CONTENT=\"$dir/userContent.css\"; mkdir -p \"$dir\"; touch \"$USER_CHROME\" \"$USER_CONTENT\"; sed -i \"/zen-browser\\/zen-userChrome\\.css/d\" \"$USER_CHROME\"; sed -i \"/zen-browser\\/zen-userContent\\.css/d\" \"$USER_CONTENT\"; if ! grep -Fq \"$LINE_CHROME\" \"$USER_CHROME\"; then printf \"%s\\n\" \"$LINE_CHROME\" >> \"$USER_CHROME\"; fi; if ! grep -Fq \"$LINE_CONTENT\" \"$USER_CONTENT\"; then printf \"%s\\n\" \"$LINE_CONTENT\" >> \"$USER_CONTENT\"; fi; done'"
|
|
},
|
|
{
|
|
"id": "cava",
|
|
"name": "Cava",
|
|
"category": "audio",
|
|
"input": "cava.ini",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/cava/themes/noctalia"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} cava`
|
|
},
|
|
{
|
|
"id": "yazi",
|
|
"name": "Yazi",
|
|
"category": "misc",
|
|
"input": "yazi.toml",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/yazi/flavors/noctalia.yazi/flavor.toml"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} yazi`
|
|
},
|
|
{
|
|
"id": "emacs",
|
|
"name": "Emacs",
|
|
"category": "editor",
|
|
"input": "emacs.el",
|
|
"postProcess": () => `emacsclient -e "(load-theme 'noctalia t)"`
|
|
},
|
|
{
|
|
"id": "labwc",
|
|
"name": "Labwc",
|
|
"category": "compositor",
|
|
"input": "labwc.conf",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/labwc/themerc-override"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} labwc`
|
|
},
|
|
{
|
|
"id": "niri",
|
|
"name": "Niri",
|
|
"category": "compositor",
|
|
"input": "niri.kdl",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/niri/noctalia.kdl"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} niri`
|
|
},
|
|
{
|
|
"id": "sway",
|
|
"name": "Sway",
|
|
"category": "compositor",
|
|
"input": "sway",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/sway/noctalia"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} sway`
|
|
},
|
|
{
|
|
"id": "scroll",
|
|
"name": "Scroll",
|
|
"category": "compositor",
|
|
"input": "scroll",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/scroll/noctalia"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} scroll`
|
|
},
|
|
{
|
|
"id": "hyprland",
|
|
"name": "Hyprland",
|
|
"category": "compositor",
|
|
"input": "hyprland.conf",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/hypr/noctalia/noctalia-colors.conf"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} hyprland`
|
|
},
|
|
{
|
|
"id": "hyprtoolkit",
|
|
"name": "Hyprtoolkit",
|
|
"category": "system",
|
|
"input": "hyprtoolkit.conf",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/hypr/hyprtoolkit.conf"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "mango",
|
|
"name": "Mango",
|
|
"category": "compositor",
|
|
"input": "mango.conf",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/mango/noctalia.conf"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} mango`
|
|
},
|
|
{
|
|
"id": "btop",
|
|
"name": "btop",
|
|
"category": "misc",
|
|
"input": "btop.theme",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/btop/themes/noctalia.theme"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} btop`
|
|
},
|
|
{
|
|
"id": "zathura",
|
|
"name": "Zathura",
|
|
"category": "misc",
|
|
"input": "zathurarc",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.config/zathura/noctaliarc"
|
|
}
|
|
],
|
|
"postProcess": () => `${templateApplyScript} zathura`
|
|
},
|
|
{
|
|
"id": "steam",
|
|
"name": "Steam",
|
|
"category": "misc",
|
|
"input": "steam.css",
|
|
"outputs": [
|
|
{
|
|
"path": "~/.steam/steam/steamui/skins/Material-Theme/css/main/colors/matugen.css"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
// Extract Discord clients for ProgramCheckerService compatibility
|
|
readonly property var discordClients: {
|
|
var clients = [];
|
|
var discordApp = applications.find(app => app.id === "discord");
|
|
if (discordApp && discordApp.clients) {
|
|
discordApp.clients.forEach(client => {
|
|
clients.push({
|
|
"name": client.name,
|
|
"configPath": client.path,
|
|
"themePath": `${client.path}/themes/noctalia.theme.css`
|
|
});
|
|
});
|
|
}
|
|
return clients;
|
|
}
|
|
|
|
// Get resolved theme paths for a code client (returns array of all matching paths)
|
|
function resolvedCodeClientPaths(clientName) {
|
|
if (clientName === "code")
|
|
return resolvedCodePaths;
|
|
if (clientName === "codium")
|
|
return resolvedCodiumPaths;
|
|
return [];
|
|
}
|
|
|
|
// Extract Code clients for ProgramCheckerService compatibility
|
|
readonly property var codeClients: {
|
|
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 = "";
|
|
if (client.name === "code") {
|
|
// For VSCode: ~/.vscode/extensions/... -> ~/.vscode
|
|
baseConfigDir = "~/.vscode";
|
|
} else if (client.name === "codium") {
|
|
// For VSCodium: ~/.vscode-oss/extensions/... -> ~/.vscode-oss
|
|
baseConfigDir = "~/.vscode-oss";
|
|
}
|
|
clients.push({
|
|
"name": client.name,
|
|
"configPath": baseConfigDir,
|
|
"themePath": "" // resolved dynamically via resolvedCodeClientPaths()
|
|
});
|
|
});
|
|
}
|
|
return clients;
|
|
}
|
|
|
|
// Resolve VSCode extension paths dynamically
|
|
Process {
|
|
id: codeResolverProcess
|
|
command: ["python3", vscodeHelperScript, "~/.vscode/extensions"]
|
|
running: true
|
|
property var paths: []
|
|
stdout: SplitParser {
|
|
onRead: data => {
|
|
var line = data.trim();
|
|
if (line)
|
|
codeResolverProcess.paths.push(line);
|
|
}
|
|
}
|
|
onExited: {
|
|
root.resolvedCodePaths = paths;
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: codiumResolverProcess
|
|
command: ["python3", vscodeHelperScript, "~/.vscode-oss/extensions"]
|
|
running: true
|
|
property var paths: []
|
|
stdout: SplitParser {
|
|
onRead: data => {
|
|
var line = data.trim();
|
|
if (line)
|
|
codiumResolverProcess.paths.push(line);
|
|
}
|
|
}
|
|
onExited: {
|
|
root.resolvedCodiumPaths = paths;
|
|
}
|
|
}
|
|
// 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/Templates/noctalia.json\"");
|
|
lines.push("#output_path = \"" + Settings.cacheDir + "placeholder.json\"");
|
|
lines.push("");
|
|
|
|
return lines.join("\n") + "\n";
|
|
}
|
|
|
|
// Write user templates TOML file (moved from Theming)
|
|
function writeUserTemplatesToml() {
|
|
var userConfigPath = Settings.configDir + "user-templates.toml";
|
|
|
|
// Check if file already exists
|
|
fileCheckProcess.command = ["test", "-s", userConfigPath];
|
|
fileCheckProcess.running = true;
|
|
}
|
|
|
|
function doWriteUserTemplatesToml() {
|
|
var userConfigPath = Settings.configDir + "user-templates.toml";
|
|
var configContent = buildUserTemplatesToml();
|
|
var userConfigPathEsc = userConfigPath.replace(/'/g, "'\\''");
|
|
var configDirEsc = Settings.configDir.replace(/'/g, "'\\''");
|
|
|
|
// Combine mkdir and write in a single script to avoid race condition
|
|
var script = `mkdir -p '${configDirEsc}' && cat > '${userConfigPathEsc}' << 'EOF'\n`;
|
|
script += configContent;
|
|
script += "EOF\n";
|
|
fileWriteProcess.command = ["sh", "-c", script];
|
|
fileWriteProcess.running = true;
|
|
}
|
|
|
|
// Extract Emacs clients for ProgramCheckerService compatibility
|
|
readonly property var emacsClients: [
|
|
{
|
|
"name": "doom",
|
|
"path": "~/.config/doom"
|
|
},
|
|
{
|
|
"name": "modern",
|
|
"path": "~/.config/emacs"
|
|
},
|
|
{
|
|
"name": "traditional",
|
|
"path": "~/.emacs.d"
|
|
}
|
|
]
|
|
|
|
// Process for checking if user templates file exists and is non-empty
|
|
Process {
|
|
id: fileCheckProcess
|
|
running: false
|
|
|
|
onExited: function (exitCode) {
|
|
if (exitCode === 0) {
|
|
// File exists and is non-empty, skip creation
|
|
Logger.d("TemplateRegistry", "User templates config already exists, skipping creation");
|
|
} else {
|
|
// File doesn't exist or is empty, create it
|
|
doWriteUserTemplatesToml();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process for writing user templates file with error reporting
|
|
Process {
|
|
id: fileWriteProcess
|
|
running: false
|
|
|
|
onExited: function (exitCode) {
|
|
if (exitCode === 0) {
|
|
Logger.d("TemplateRegistry", "User templates config written to:", Settings.configDir + "user-templates.toml");
|
|
} else {
|
|
Logger.e("TemplateRegistry", "Failed to write user templates config (exit code:", exitCode + ")");
|
|
}
|
|
}
|
|
}
|
|
}
|