This commit is contained in:
2026-11-04 20:35:46 +01:00
parent 5f4de5e60c
commit 86abc097f4
348 changed files with 10300 additions and 0 deletions
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "37e681728b2fec11796989fc48cc3d31"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.antigravity]
name = "Antigravity"
category = "ai"
[templates.antigravity]
input_path = "antigravity.json"
output_path = "~/.gemini/config/theme.json"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "b2c456fb15853159cfcb213f38b91b97"
},
"version": 1
}
@@ -0,0 +1,9 @@
[catalog.archetype-pokemmo]
name = "Archetype (PokeMMO)"
category = "gaming"
[templates.archetype-pokemmo]
input_path_modes = { dark = "archetype-colors-dark.json", light = "archetype-colors-light.json" }
output_path = "$XDG_CACHE_HOME/noctalia/archetype-pokemmo/colors.json"
post_hook = "python3 '{{ config_dir }}/apply.py'"
hook_async = false
@@ -0,0 +1,9 @@
{
"files": {
"README.md": "c971168a3f8d5a0c570c8a7813760655",
"apply.sh": "72fdf03821d232593cf85ea926253472",
"bat.tmTheme": "48ce50cbdc345e4a61d8a3dc95b4fce8",
"template.toml": "6ee484a55483e12300a00641ec38aecc"
},
"version": 1
}
@@ -0,0 +1,9 @@
# bat
Adds a Noctalia syntax-highlighting theme for bat.
Enable **bat** in **Noctalia Settings -> Templates**. The template writes `$XDG_CONFIG_HOME/bat/themes/noctalia.tmTheme` (`~/.config/bat/themes/noctalia.tmTheme` by default).
The Bash post-hook creates `$XDG_CONFIG_HOME/bat/config` if necessary and selects `--theme=noctalia`. Unless that exact line is already present, it removes existing lines beginning with `--theme=` before appending the new selection. Back up your config first; no backup is created.
Install `bat` or `batcat`: the hook runs the available executable with `cache --build` after generation. If neither exists, it prints a warning; run `bat cache --build` (or `batcat cache --build`) yourself after installation.
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail
bat_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/bat"
themes_dir="$bat_config_dir/themes"
config_file="$bat_config_dir/config"
theme_file="$themes_dir/noctalia.tmTheme"
theme_line="--theme=noctalia"
mkdir -p "$themes_dir"
touch "$config_file" "$theme_file"
write_if_changed() {
local target="$1" tmp="$2"
if ! cmp -s "$target" "$tmp"; then
cat "$tmp" >"$target"
fi
rm -f "$tmp"
}
if ! grep -Fxq -- "$theme_line" "$config_file"; then
tmp_file="$(mktemp "${config_file}.tmp.XXXXXX")"
sed '/^--theme=/d' "$config_file" >"$tmp_file"
[ -s "$tmp_file" ] && [ -n "$(tail -c1 "$tmp_file")" ] && echo >>"$tmp_file"
printf '%s\n' "$theme_line" >>"$tmp_file"
write_if_changed "$config_file" "$tmp_file"
fi
if command -v bat >/dev/null 2>&1; then
bat cache --build
elif command -v batcat >/dev/null 2>&1; then
batcat cache --build
else
echo "Warning: 'bat' executable not found. Please run 'bat cache --build' manually once it is installed."
fi
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
[catalog.bat]
name = "bat"
category = "terminal"
[templates.bat]
input_path = "bat.tmTheme"
output_path = "$XDG_CONFIG_HOME/bat/themes/noctalia.tmTheme"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,9 @@
{
"files": {
"README.md": "af0b5fd57df6929d6d1f629518541b08",
"blender_theme.py": "1a0cd158ac744d4887a832631d1c58b3",
"screenshot.png": "35fe418801156063765900cb85780f4e",
"template.toml": "2268722156753442c3797250df7f4222"
},
"version": 1
}
@@ -0,0 +1,41 @@
# Blender
Blender's theme system isn't a CSS file, it's roughly 570 individual color properties on
`bpy.context.preferences.themes[0]`, saved into a binary `userpref.blend`, not a plain-text
config. So this template doesn't just render a file, `input_path` is a Python script (with
`{{colors.role.default.hex}}` placeholders substituted the normal way), and `post_hook` runs it
through Blender itself in headless mode, which applies the colors via the real `bpy` API and
saves preferences directly. No GUI window opens, `--background` mode is fully invisible.
## What gets themed, and what deliberately doesn't
Only structural chrome: panel backgrounds, headers, tab strips, widget fills/outlines/text,
editor borders, list backgrounds, metadata overlay boxes. Around 345 of the ~570 properties.
The rest (about 225) are left at Blender's own defaults on purpose: mesh-editing selection
states (vertex/edge/face colors), node category colors, sequencer strip-type colors, syntax
highlighting, keyframe types, axis/gizmo colors. These are workflow conventions users rely on
for actual 3D work, not decoration, recoloring them to an arbitrary palette would hurt
usability rather than improve consistency. Same reasoning already applied to terminal ANSI
colors and Blender's own axis-color convention (X=red, Y=green, Z=blue) elsewhere.
## Native install
Runs `blender --background --python ...` directly. No setup beyond having `blender` on `PATH`.
## Flatpak install (`org.blender.Blender`)
Blender's Flatpak has `filesystems=host` (full host filesystem access), broader than most
sandboxed apps here. `$XDG_CONFIG_HOME` inside the sandbox is still remapped to
`~/.var/app/org.blender.Blender/config` regardless, same as every other Flatpak app, but that
doesn't matter for this template: the rendered script lives at a plain host path
(`~/.cache/noctalia/blender_theme_rendered.py`), and Blender's `host` grant means it can read
that path directly by its real location, no `flatpak override` or `.var/app`-local copy needed
at all.
## Testing
Tested against Blender 5.2.0 LTS (Flatpak). Verified via a full audit: captured Blender's true
factory-default theme values (`--factory-startup`) and diffed against the post-apply state
property by property, confirming exactly which of the ~570 were intentionally changed versus
intentionally left alone, not just spot-checked visually.
@@ -0,0 +1,192 @@
"""Applies Noctalia's palette to Blender's UI chrome, run headless via `blender --background --python`.
Only structural chrome is themed: panel backgrounds, headers, widget fills/outlines/text.
Workflow-meaningful colors (mesh selection states, node categories, sequencer strip types,
syntax highlighting) are deliberately left at Blender's own defaults, since users rely on those
as functional conventions, not decoration.
"""
import bpy
COLORS = {
"surface": "{{colors.surface.default.hex}}",
"on_surface": "{{colors.on_surface.default.hex}}",
"surface_variant": "{{colors.surface_variant.default.hex}}",
"on_surface_variant": "{{colors.on_surface_variant.default.hex}}",
"surface_container": "{{colors.surface_container.default.hex}}",
"surface_container_low": "{{colors.surface_container_low.default.hex}}",
"surface_container_high": "{{colors.surface_container_high.default.hex}}",
"outline": "{{colors.outline.default.hex}}",
"outline_variant": "{{colors.outline_variant.default.hex}}",
"primary": "{{colors.primary.default.hex}}",
"on_primary": "{{colors.on_primary.default.hex}}",
"primary_container": "{{colors.primary_container.default.hex}}",
"on_primary_container": "{{colors.on_primary_container.default.hex}}",
"secondary": "{{colors.secondary.default.hex}}",
"secondary_container": "{{colors.secondary_container.default.hex}}",
"on_secondary_container": "{{colors.on_secondary_container.default.hex}}",
"tertiary": "{{colors.tertiary.default.hex}}",
"tertiary_container": "{{colors.tertiary_container.default.hex}}",
"on_tertiary_container": "{{colors.on_tertiary_container.default.hex}}",
"error": "{{colors.error.default.hex}}",
"on_error": "{{colors.on_error.default.hex}}",
"error_container": "{{colors.error_container.default.hex}}",
}
def hex_to_rgb(hex_str):
hex_str = hex_str.lstrip("#")
return tuple(int(hex_str[i : i + 2], 16) / 255.0 for i in (0, 2, 4))
RGB = {name: hex_to_rgb(value) for name, value in COLORS.items()}
def classify(path):
if path.endswith(".space.back"):
return "surface"
if path.endswith(".space.header"):
return "surface_container"
if path.endswith(".space.header_text"):
return "on_surface"
if path.endswith(".space.header_text_hi"):
return "primary"
if path.endswith(".space.text"):
return "on_surface"
if path.endswith(".space.text_hi"):
return "primary"
if path.endswith(".space.title"):
return "on_surface_variant"
if path.endswith(".space.tab_active"):
return "surface_container_high"
if path.endswith(".space.tab_back"):
return "surface_container_low"
if path.endswith(".space.tab_outline"):
return "outline"
if path.endswith(".space.gradients.gradient"):
return "surface"
if path.endswith(".space.gradients.high_gradient"):
return "surface_container"
if path.endswith("row_alternate"):
return "surface_container_low"
if ".wcol_" in path:
suffix = path.rsplit(".", 1)[-1]
wcol_map = {
"inner": "surface_container",
"inner_sel": "primary_container",
"inner_key": "tertiary_container",
"inner_key_sel": "tertiary_container",
"inner_anim": "secondary_container",
"inner_anim_sel": "secondary_container",
"inner_driven": "tertiary",
"inner_driven_sel": "tertiary",
"inner_changed": "primary",
"inner_changed_sel": "primary",
"inner_overridden": "error_container",
"inner_overridden_sel": "error_container",
"outline": "outline",
"outline_sel": "primary",
"item": "on_surface_variant",
"text": "on_surface",
"text_sel": "on_primary",
"error": "error",
"warning": "tertiary",
"success": "secondary",
"info": "primary",
}
if suffix in wcol_map:
return wcol_map[suffix]
if path.startswith("regions."):
if path.endswith(".back") or path.endswith(".header_back"):
return "surface_container"
if path.endswith(".tab_back"):
return "surface_container_low"
if path.endswith(".text"):
return "on_surface"
if path.endswith(".text_selected"):
return "primary"
direct = {
"text_editor.line_numbers": "on_surface_variant",
"text_editor.line_numbers_background": "surface_container_low",
"text_editor.cursor": "primary",
"text_editor.selected_text": "primary_container",
"image_editor.metadatabg": "surface_container",
"image_editor.metadatatext": "on_surface",
"sequence_editor.metadatabg": "surface_container",
"sequence_editor.metadatatext": "on_surface",
"sequence_editor.preview_back": "surface",
"clip_editor.metadatabg": "surface_container",
"clip_editor.metadatatext": "on_surface",
"node_editor.node_outline": "outline",
"user_interface.widget_text_cursor": "primary",
"user_interface.editor_border": "outline",
"user_interface.editor_outline": "outline_variant",
"user_interface.editor_outline_active": "primary",
"user_interface.panel_header": "surface_container",
"user_interface.panel_back": "surface_container_low",
"user_interface.panel_outline": "outline",
"user_interface.panel_title": "on_surface",
"user_interface.panel_text": "on_surface",
"user_interface.panel_active": "primary",
"outliner.selected_highlight": "primary_container",
"outliner.active": "primary",
"outliner.selected_object": "on_primary_container",
"outliner.active_object": "on_primary",
"outliner.match": "tertiary_container",
"info.info_error_text": "error",
"info.info_warning_text": "tertiary",
"info.info_info_text": "on_surface",
"info.info_property_text": "secondary",
"info.info_operator_text": "primary",
"info.info_debug_text": "outline",
"info.info_selected": "primary_container",
"info.info_selected_text": "on_primary_container",
"console.line_output": "on_surface",
"console.line_input": "primary",
"console.line_info": "secondary",
"console.line_error": "error",
"console.select": "primary_container",
"console.cursor": "primary",
"file_browser.selected_file": "primary_container",
"preferences.match": "tertiary_container",
"properties.match": "tertiary_container",
}
if path in direct:
return direct[path]
return None
def walk_and_apply(obj, path="", depth=0):
if depth > 4:
return 0
applied = 0
for prop in obj.bl_rna.properties:
if prop.identifier == "rna_type":
continue
try:
val = getattr(obj, prop.identifier)
except Exception:
continue
full = f"{path}.{prop.identifier}" if path else prop.identifier
if prop.type == "POINTER" and val is not None:
applied += walk_and_apply(val, full, depth + 1)
elif prop.type == "FLOAT" and getattr(prop, "array_length", 0) in (3, 4):
role = classify(full)
if role is None:
continue
rgb = RGB[role]
current = list(val)
current[0], current[1], current[2] = rgb
setattr(obj, prop.identifier, current)
applied += 1
return applied
theme = bpy.context.preferences.themes[0]
count = walk_and_apply(theme)
print(f"noctalia-theme: applied {count} properties")
bpy.ops.wm.save_userpref()
Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

@@ -0,0 +1,15 @@
[catalog.blender]
name = "Blender"
category = "editor"
[templates.blender]
input_path = "blender_theme.py"
output_path = "~/.cache/noctalia/blender_theme_rendered.py"
post_hook = "blender --background --python ~/.cache/noctalia/blender_theme_rendered.py"
requires_path = "~/.config/blender"
[templates.blender-flatpak]
input_path = "blender_theme.py"
output_path = "~/.cache/noctalia/blender_theme_rendered.py"
post_hook = "flatpak run org.blender.Blender --background --python ~/.cache/noctalia/blender_theme_rendered.py"
requires_path = "~/.var/app/org.blender.Blender"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "b00582c206b5456ae8fd82882d0c8084"
},
"version": 1
}
@@ -0,0 +1,9 @@
[catalog.brave-origin]
name = "Brave Origin"
category = "browser"
[templates.brave-origin]
input_path = "manifest.json"
output_path = "$XDG_CACHE_HOME/noctalia/brave-origin-theme/manifest.json"
post_hook = "bash '{{ config_dir }}/apply.sh' {{ mode }}"
requires_path = "~/.config/BraveSoftware/Brave-Origin"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "5824b129b9a884cf0a1d6ce1ac0c2c7a"
},
"version": 1
}
@@ -0,0 +1,9 @@
[catalog.brave]
name = "Brave"
category = "browser"
[templates.brave]
input_path = "manifest.json"
output_path = "$XDG_CACHE_HOME/noctalia/brave-theme/manifest.json"
post_hook = "bash '{{ config_dir }}/apply.sh' {{ mode }}"
requires_path = "~/.config/BraveSoftware/Brave-Browser"
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "630b15c67a7b75a02d88151b21b09a06"
},
"version": 1
}
@@ -0,0 +1,9 @@
[catalog.chromium-based]
name = "Chromium based"
category = "browser"
[templates.chromium-based]
input_path = "color.json"
output_path_dynamic = "bash '{{ config_dir }}/apply.sh' output"
post_hook = "bash '{{ config_dir }}/apply.sh' refresh"
undo_hook = "bash '{{ config_dir }}/apply.sh' undo"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "47a5727698275d6e0a4dd457a0a93bc9"
},
"version": 1
}
@@ -0,0 +1,7 @@
[catalog.claude-code]
name = "Claude Code"
category = "ai"
[templates.claude-code]
input_path = "claude-code.json"
output_path = "~/.claude/themes/noctalia.json"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "d3540eaa368262a0691d7f21f1c537f3"
},
"version": 1
}
@@ -0,0 +1,7 @@
[catalog.codex]
name = "Codex"
category = "ai"
[templates.codex]
input_path = "codex.tmTheme"
output_path = "~/.codex/themes/noctalia.tmTheme"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "695af0569a2f5d7067a8079466115416"
},
"version": 1
}
@@ -0,0 +1,11 @@
[catalog.cosmic]
name = "COSMIC"
category = "system"
[templates.cosmic]
input_path = "cosmic-colors"
output_path = "$XDG_CACHE_HOME/noctalia/cosmic-palette"
post_hook = "python3 '{{ config_dir }}/apply.py'"
undo_hook = "bash '{{ config_dir }}/undo.sh'"
hook_async = false
requires_path = "~/.config/cosmic"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "20dd9a1cf1df1d87e3674062ed53df2b"
},
"version": 1
}
@@ -0,0 +1,7 @@
[catalog.cressida]
name = "Cressida"
category = "chat"
[templates.cressida]
input_path = "cressida.toml"
output_path = "$XDG_CONFIG_HOME/cressida/themes/noctalia.toml"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "6759a4e4a2ffde8fd624f67a73cefdeb"
},
"version": 1
}
@@ -0,0 +1,13 @@
[catalog.darktable]
name = "darktable"
category = "editor"
[templates.darktable]
input_path = "noctalia.css"
output_path = "~/.config/darktable/themes/noctalia.css"
requires_path = "~/.config/darktable"
[templates.darktable-flatpak]
input_path = "noctalia-flatpak.css"
output_path = "~/.var/app/org.darktable.Darktable/config/darktable/themes/noctalia.css"
requires_path = "~/.var/app/org.darktable.Darktable"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "b7fcfda472b57734eb8259fa80bf2fd2"
},
"version": 1
}
@@ -0,0 +1,135 @@
[catalog.discord]
name = "Discord"
category = "chat"
[templates.discord_midnight_vesktop]
input_path = "discord-midnight.css"
output_path = "$XDG_CONFIG_HOME/vesktop/themes/noctalia.theme.css"
[templates.discord_material_vesktop]
input_path = "discord-material.css"
output_path = "$XDG_CONFIG_HOME/vesktop/themes/noctalia-material.theme.css"
[templates.discord_system24_vesktop]
input_path = "discord-system24.css"
output_path = "$XDG_CONFIG_HOME/vesktop/themes/discord-system24.css"
[templates.discord_midnight_vesktop_flatpak]
input_path = "discord-midnight.css"
output_path = "~/.var/app/dev.vencord.Vesktop/config/vesktop/themes/noctalia.theme.css"
[templates.discord_material_vesktop_flatpak]
input_path = "discord-material.css"
output_path = "~/.var/app/dev.vencord.Vesktop/config/vesktop/themes/noctalia-material.theme.css"
[templates.discord_system24_vesktop_flatpak]
input_path = "discord-system24.css"
output_path = "~/.var/app/dev.vencord.Vesktop/config/vesktop/themes/discord-system24.css"
[templates.discord_midnight_webcord]
input_path = "discord-midnight.css"
output_path = "$XDG_CONFIG_HOME/webcord/themes/noctalia.theme.css"
[templates.discord_material_webcord]
input_path = "discord-material.css"
output_path = "$XDG_CONFIG_HOME/webcord/themes/noctalia-material.theme.css"
[templates.discord_system24_webcord]
input_path = "discord-system24.css"
output_path = "$XDG_CONFIG_HOME/webcord/themes/discord-system24.css"
[templates.discord_midnight_legcord]
input_path = "discord-midnight.css"
output_path = "$XDG_CONFIG_HOME/legcord/themes/noctalia.theme.css"
[templates.discord_material_legcord]
input_path = "discord-material.css"
output_path = "$XDG_CONFIG_HOME/legcord/themes/noctalia-material.theme.css"
[templates.discord_system24_legcord]
input_path = "discord-system24.css"
output_path = "$XDG_CONFIG_HOME/legcord/themes/discord-system24.css"
[templates.discord_midnight_equibop]
input_path = "discord-midnight.css"
output_path = "$XDG_CONFIG_HOME/equibop/themes/noctalia.theme.css"
[templates.discord_material_equibop]
input_path = "discord-material.css"
output_path = "$XDG_CONFIG_HOME/equibop/themes/noctalia-material.theme.css"
[templates.discord_system24_equibop]
input_path = "discord-system24.css"
output_path = "$XDG_CONFIG_HOME/equibop/themes/discord-system24.css"
[templates.discord_midnight_equicord]
input_path = "discord-midnight.css"
output_path = "$XDG_CONFIG_HOME/Equicord/themes/noctalia.theme.css"
[templates.discord_material_equicord]
input_path = "discord-material.css"
output_path = "$XDG_CONFIG_HOME/Equicord/themes/noctalia-material.theme.css"
[templates.discord_system24_equicord]
input_path = "discord-system24.css"
output_path = "$XDG_CONFIG_HOME/Equicord/themes/discord-system24.css"
[templates.discord_midnight_lightcord]
input_path = "discord-midnight.css"
output_path = "$XDG_CONFIG_HOME/lightcord/themes/noctalia.theme.css"
[templates.discord_material_lightcord]
input_path = "discord-material.css"
output_path = "$XDG_CONFIG_HOME/lightcord/themes/noctalia-material.theme.css"
[templates.discord_system24_lightcord]
input_path = "discord-system24.css"
output_path = "$XDG_CONFIG_HOME/lightcord/themes/discord-system24.css"
[templates.discord_midnight_dorion]
input_path = "discord-midnight.css"
output_path = "$XDG_CONFIG_HOME/dorion/themes/noctalia.theme.css"
[templates.discord_material_dorion]
input_path = "discord-material.css"
output_path = "$XDG_CONFIG_HOME/dorion/themes/noctalia-material.theme.css"
[templates.discord_system24_dorion]
input_path = "discord-system24.css"
output_path = "$XDG_CONFIG_HOME/dorion/themes/discord-system24.css"
[templates.discord_midnight_vencord]
input_path = "discord-midnight.css"
output_path = "$XDG_CONFIG_HOME/Vencord/themes/noctalia.theme.css"
[templates.discord_material_vencord]
input_path = "discord-material.css"
output_path = "$XDG_CONFIG_HOME/Vencord/themes/noctalia-material.theme.css"
[templates.discord_system24_vencord]
input_path = "discord-system24.css"
output_path = "$XDG_CONFIG_HOME/Vencord/themes/discord-system24.css"
[templates.discord_midnight_vencord_flatpak]
input_path = "discord-midnight.css"
output_path = "~/.var/app/com.discordapp.Discord/config/Vencord/themes/noctalia.theme.css"
[templates.discord_material_vencord_flatpak]
input_path = "discord-material.css"
output_path = "~/.var/app/com.discordapp.Discord/config/Vencord/themes/noctalia-material.theme.css"
[templates.discord_system24_vencord_flatpak]
input_path = "discord-system24.css"
output_path = "~/.var/app/com.discordapp.Discord/config/Vencord/themes/discord-system24.css"
[templates.discord_midnight_betterdiscord]
input_path = "discord-midnight.css"
output_path = "$XDG_CONFIG_HOME/BetterDiscord/themes/noctalia.theme.css"
[templates.discord_material_betterdiscord]
input_path = "discord-material.css"
output_path = "$XDG_CONFIG_HOME/BetterDiscord/themes/noctalia-material.theme.css"
[templates.discord_system24_betterdiscord]
input_path = "discord-system24.css"
output_path = "$XDG_CONFIG_HOME/BetterDiscord/themes/discord-system24.css"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "2be6fd472586a5be0c40b4e2de4bdf4c"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.driftwm]
name = "DriftWM"
category = "compositor"
[templates.driftwm]
input_path = "driftwm-colors"
output_path = "$XDG_CACHE_HOME/noctalia-driftwm-colors"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,10 @@
{
"files": {
"README.md": "eaa53d6ac50931e057bf030c678fd3d7",
"apply.sh": "c605011961d37be31b8c710c83303360",
"fastfetch-colors.jsonc": "3de977e47b4cc00e76866e680974700a",
"screenshot.png": "e5be2fe0b3041fd4342ccfce6877a169",
"template.toml": "f05501b07584db28af6d779612bb58cf"
},
"version": 1
}
@@ -0,0 +1,45 @@
# fastfetch
Themes [fastfetch](https://github.com/fastfetch-cli/fastfetch) — sets `display.color.keys`,
`display.color.title`, `display.percent.color.{green,yellow,red}` and `logo.color.{1,2}`.
fastfetch's config (`~/.config/fastfetch/config.jsonc`) has no include/merge directive, so
`apply.sh` merges the rendered color object into the user's real config.jsonc directly (`jq -s
'.[0] * .[1]'`), preserving everything else in it (modules list, custom module config, etc.).
**Known limitation: strict JSON only, not full JSONC.** `jq` (used for the merge) only parses
strict JSON — `//`/`/* */` comments and trailing commas, both valid JSONC, will make `apply.sh`
fail. It fails loudly with a clear message in that case rather than silently corrupting the file
or applying nothing with no explanation. If your `config.jsonc` uses comments, remove them to use
this template (fastfetch's own `--gen-config`/`--gen-config-force` output has none by default).
Also fails loudly (rather than auto-creating one) if `config.jsonc` doesn't exist at all yet —
run fastfetch once first to generate a default config.
**Idempotency note**: the merge is guarded by comparing only the `logo`/`display` sub-objects
semantically (`jq -S`), not the whole file's raw bytes — a naive whole-file `cmp` after a `jq`
merge will never actually match hand-formatted JSON (`jq` re-serializes with its own canonical
formatting), which silently defeats the guard and rewrites the file on every apply regardless of
whether colors changed. Verified idempotent: running `apply.sh` twice with unchanged colors
leaves the file's mtime and content completely untouched.
**Known upstream bug (worked around here): `logo.color` silently drops pastel/light colors.**
Confirmed live against fastfetch 2.66.0: a `logo.color.N` value is dropped with no error — the
logo falls back to its default palette — whenever its truecolor SGR string (`38;2;R;G;B`) is
exactly 16 characters, i.e. every one of R/G/B is a 3-digit number (`>=100`). That covers a large
share of real accent colors, including most pastel/light theme colors. `display.color.keys`/
`title`/`percent` are unaffected — only `logo.color.N` goes through the broken code path.
Isolated by testing the exact byte-length trigger directly (both via `--logo-color-N` and via this
same JSON key) with fully saturated colors of the same hue (worked) vs. desaturated/light colors
producing a 16-char string (failed), including a control test with a non-pastel color that still
had all three channels `>=100` (also failed) — confirming it's the string length, not the hue or
lightness itself.
`apply.sh` routes around it: whenever a `logo.color` value would trip the bug, the single
*smallest* RGB channel is nudged down to 99 — the minimal change that drops the SGR string below
16 characters, and since it's already the channel closest to the 100 boundary, it's the smallest
possible perceptual shift (e.g. `#fff59b` → `#fff563`). An earlier version of this fix quantized
to the nearest ANSI-256 cube color instead, but the 216-color cube only has 6 steps per channel,
so a near-white pastel like `#fff59b` snapped to `#ffffaf` — visually just white on a small ascii
logo. The channel-nudge keeps full 24-bit truecolor and stays close to the real color.
Tested against fastfetch 2.66.0.
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
set -euo pipefail
config_dir_xdg="${XDG_CONFIG_HOME:-$HOME/.config}/fastfetch"
config_file="$config_dir_xdg/config.jsonc"
theme_file="$config_dir_xdg/themes/noctalia.jsonc"
if [ ! -f "$config_file" ]; then
echo "Error: fastfetch config not found at $config_file -- run fastfetch once to generate a default config first." >&2
exit 1
fi
if [ ! -f "$theme_file" ]; then
echo "Warning: fastfetch theme file not found: $theme_file" >&2
exit 0
fi
# jq only accepts strict JSON. config.jsonc is JSONC (comments/trailing commas allowed), so
# fail loudly with a clear message rather than let a bare jq parse error explain nothing, or
# silently skip and leave the user without any indication why colors never apply.
if ! jq empty "$config_file" 2>/dev/null; then
echo "Error: $config_file could not be parsed as strict JSON. Comments and trailing commas (valid JSONC, not valid JSON) are not currently supported by this hook -- remove them to use this template." >&2
exit 1
fi
# fastfetch 2.66.0 has a confirmed real bug in its logo renderer: a
# logo.color value is dropped silently -- the logo falls back to its
# default palette with no error -- whenever its truecolor SGR string
# ("38;2;R;G;B") is exactly 16 characters, i.e. every one of R/G/B is a
# 3-digit number (>=100). That covers a large share of real accent colors
# (most pastel/light theme colors included). display.color.keys/title/
# percent are unaffected -- only logo.color.N goes through the broken path.
# Verified live against 2.66.0 by isolating the exact byte-length trigger:
# any hex where all three channels are >=100 reproduces it, regardless of
# hue; anything with at least one channel <100 (a shorter SGR string)
# renders correctly.
#
# Route around it by nudging the single *smallest* channel down to 99
# whenever all three are >=100 -- that's the minimal change that drops the
# SGR string below 16 chars, and it's always the channel closest to the
# 100 boundary already, so it's the smallest possible perceptual shift.
# (An earlier version of this fix quantized to the nearest ANSI-256 cube
# color instead, but the 216-color cube only has 6 steps per channel, so a
# near-white pastel like #fff59b snapped to #ffffaf -- visually just
# "white" on a small ascii logo. This keeps full 24-bit truecolor.)
nudge_channel() {
local hex="${1#\#}"
local r=$((16#${hex:0:2}))
local g=$((16#${hex:2:2}))
local b=$((16#${hex:4:2}))
if [ "$r" -ge 100 ] && [ "$g" -ge 100 ] && [ "$b" -ge 100 ]; then
if [ "$r" -le "$g" ] && [ "$r" -le "$b" ]; then
r=99
elif [ "$g" -le "$r" ] && [ "$g" -le "$b" ]; then
g=99
else
b=99
fi
fi
printf '#%02x%02x%02x' "$r" "$g" "$b"
}
tmp_theme="$(mktemp)"
tmp_file=""
trap 'rm -f "$tmp_theme" "${tmp_file:-}"' EXIT
logo_1="$(jq -r '.logo.color."1" // empty' "$theme_file")"
logo_2="$(jq -r '.logo.color."2" // empty' "$theme_file")"
jq \
--arg c1 "$([ -n "$logo_1" ] && nudge_channel "$logo_1" || echo "")" \
--arg c2 "$([ -n "$logo_2" ] && nudge_channel "$logo_2" || echo "")" \
'(if $c1 != "" then .logo.color."1" = $c1 else . end)
| (if $c2 != "" then .logo.color."2" = $c2 else . end)' \
"$theme_file" >"$tmp_theme"
# Compare only the "logo"/"display" sub-objects semantically (jq -S, sorted+canonical),
# not the whole file's raw bytes -- jq's own re-serialization would never byte-match
# hand-formatted JSON, which made a naive cmp-the-whole-file guard fire on every single
# apply regardless of whether colors actually changed.
current_logo="$(jq -S '.logo // {}' "$config_file")"
current_display="$(jq -S '.display // {}' "$config_file")"
new_logo="$(jq -S '.logo // {}' "$tmp_theme")"
new_display="$(jq -S '.display // {}' "$tmp_theme")"
if [ "$current_logo" != "$new_logo" ] || [ "$current_display" != "$new_display" ]; then
tmp_file="$(mktemp)"
jq -s '.[0] * .[1]' "$config_file" "$tmp_theme" >"$tmp_file"
cat "$tmp_file" >"$config_file"
fi
@@ -0,0 +1,21 @@
{
"logo": {
"color": {
"1": "{{colors.primary.default.hex}}",
"2": "{{colors.secondary.default.hex}}"
}
},
"display": {
"color": {
"keys": "{{colors.primary.default.hex}}",
"title": "{{colors.secondary.default.hex}}"
},
"percent": {
"color": {
"green": "{{colors.tertiary.default.hex}}",
"yellow": "{{colors.primary_fixed_dim.default.hex}}",
"red": "{{colors.error.default.hex}}"
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

@@ -0,0 +1,8 @@
[catalog.fastfetch]
name = "Fastfetch"
category = "misc"
[templates.fastfetch]
input_path = "fastfetch-colors.jsonc"
output_path = "$XDG_CONFIG_HOME/fastfetch/themes/noctalia.jsonc"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "56a010cefb7570319d53289c27a99513"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.fcitx5]
name = "Fcitx5"
category = "system"
[templates.fcitx5]
input_path = "theme.conf"
output_path = "$XDG_DATA_HOME/fcitx5/themes/noctalia/theme.conf"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "5b838e0791ee65415c48ad1524f17435"
},
"version": 1
}
@@ -0,0 +1,7 @@
[catalog.feishin]
name = "Feishin"
category = "audio"
[templates.feishin]
input_path = "custom.css"
output_path = "$XDG_CONFIG_HOME/feishin/custom.css"
@@ -0,0 +1,9 @@
{
"files": {
"README.md": "5273001c9d22560e14e9ab07c580c3e9",
"apply.sh": "2cdc7ebbd5d3798fa896b1191214468c",
"fuzzel.conf": "ada6c69528e9fd1f32f27a82ba24c9d3",
"template.toml": "07c2e5d778a043872eadddea0a2edfa4"
},
"version": 1
}
@@ -0,0 +1,9 @@
# Fuzzel
Generates Noctalia colors for the Fuzzel launcher, including translucent background and selection colors.
Enable **Fuzzel** in **Noctalia Settings -> Templates**. The theme is written to `$XDG_CONFIG_HOME/fuzzel/themes/noctalia` (`~/.config/fuzzel/themes/noctalia` by default).
The Bash post-hook creates or updates `$XDG_CONFIG_HOME/fuzzel/fuzzel.ini` to include the generated theme using an absolute `include=.../fuzzel/themes/noctalia` line. If the exact include is absent, existing lines beginning with `include=` and containing `themes` are replaced; otherwise the include is appended. Other settings are retained.
Install Fuzzel and back up `fuzzel.ini` first: the hook changes it without creating a backup. No launcher reload command is included.
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}"
config_file="$config_dir/fuzzel/fuzzel.ini"
include_line="include=$config_dir/fuzzel/themes/noctalia"
mkdir -p "$(dirname "$config_file")"
write_if_changed() {
local target="$1" tmp="$2"
if [ ! -e "$target" ] && [ ! -L "$target" ]; then
mv "$tmp" "$target"
return
fi
if ! cmp -s "$target" "$tmp"; then
cat "$tmp" >"$target"
fi
rm -f "$tmp"
}
if [ ! -f "$config_file" ]; then
echo "$include_line" >"$config_file"
elif grep -q "^$include_line$" "$config_file"; then
:
elif grep -q '^include=.*themes' "$config_file"; then
tmp_file="$(mktemp "${config_file}.tmp.XXXXXX")"
sed 's|^include=.*themes.*|'"$include_line"'|' "$config_file" >"$tmp_file"
write_if_changed "$config_file" "$tmp_file"
else
[ -s "$config_file" ] && [ -n "$(tail -c1 "$config_file")" ] && echo >>"$config_file"
echo "$include_line" >>"$config_file"
fi
@@ -0,0 +1,15 @@
# Fuzzel Colors
# Generated by Noctalia's Template Processor
[colors]
background={{colors.background.default.hex_stripped}}CC
text={{colors.on_surface.default.hex_stripped}}ff
prompt={{colors.secondary.default.hex_stripped}}ff
placeholder={{colors.tertiary.default.hex_stripped}}ff
input={{colors.primary.default.hex_stripped}}ff
match={{colors.tertiary.default.hex_stripped}}ff
selection={{colors.primary.default.hex_stripped}}80
selection-text={{colors.on_surface.default.hex_stripped}}ff
selection-match={{colors.on_primary.default.hex_stripped}}ff
counter={{colors.secondary.default.hex_stripped}}ff
border={{colors.primary.default.hex_stripped}}ff
@@ -0,0 +1,8 @@
[catalog.fuzzel]
name = "Fuzzel"
category = "launcher"
[templates.fuzzel]
input_path = "fuzzel.conf"
output_path = "$XDG_CONFIG_HOME/fuzzel/themes/noctalia"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "49ef166bf4868c326404dcd1f35120ea"
},
"version": 1
}
@@ -0,0 +1,11 @@
[catalog.fzf]
name = "fzf"
category = "terminal"
[templates.fzf_bash]
input_path = "fzf.sh"
output_path = "$XDG_CONFIG_HOME/fzf/themes/noctalia.sh"
[templates.fzf_fish]
input_path = "fzf.fish"
output_path = "$XDG_CONFIG_HOME/fzf/themes/noctalia.fish"
@@ -0,0 +1,10 @@
{
"files": {
"README.md": "2776c8398f20a6a106c3cddb9b7e1171",
"gimp.css": "782d352483659abf7382546573c9d167",
"screenshot-preferences.png": "d946d563f7bbebc75c6c202982c790dc",
"screenshot.png": "ea41bc5097843dac92792cbd902a3c3b",
"template.toml": "e612bf68941874992ce2120e4f0a6455"
},
"version": 1
}
@@ -0,0 +1,54 @@
# GIMP
GIMP has its own personal-override CSS hook, separate from the auto-generated `theme.css` that
ships with the app (which is regenerated on every theme change and explicitly says not to edit
it by hand). This template writes to that personal file directly: `gimp.css`, imported
automatically if present, no extra setup needed once the template is enabled.
## What gets themed, and the two things that needed extra care
GIMP's own theme only defines 17 named colors (`fg-color`, `bg-color`, `selected-color`, and so
on), reused across `common.css`/`common-dark.css`. Most of the UI follows from setting those.
Two things needed more than a plain 1:1 color swap:
- **Context menus and other `GtkPopoverMenu` content had no background rule at all** in GIMP's
own theme (only narrow button/list-row styling), so they fell through to GTK's stock light
default. Fixed with explicit `popover`/`popover contents` rules.
- **`selected-color`/`extreme-selected-color` are always paired with white text** in GIMP's own
CSS, with no dark-text variant the way Noctalia's other templates use `on_primary` on top of a
`primary`-colored background. Mapping them straight to Noctalia's yellow accent made text
unreadable everywhere that yellow shows up (menu hovers, selected list rows, the Preferences
sidebar). Fixed by keeping the yellow and adding an explicit dark-text override for every
selector GIMP's `common.css` actually uses those two variables as a background for (checked via
grep, not guessed).
- A few widgets, confirmed just the Preferences dialog's own page title bar, don't use GIMP's
custom variables at all. GIMP's own source comment on that exact element admits it "doesn't
have any more specific way to style it," meaning it falls back to standard GTK/Adwaita
variable names (`@theme_bg_color`, `@theme_selected_bg_color`, etc.) instead. Those are set
too, as a safety net, since the system `gtk.css` those would normally come from never loads
inside a Flatpak sandbox.
## Requires GIMP's own "Default" base theme
GIMP has two selectable base themes (Preferences > Interface > Theme): `Default` and `System`.
This template is built against `Default` (`themes/Default/gimp-dark.css`), the one this personal
override's own 17 variables and extra selectors are checked against. `System` imports a
different, much smaller CSS file with no guarantee it uses the same variable names, not tested
or supported here. If colors look wrong after enabling this template, check Preferences first.
## Native install
`gimp.css` lives at `~/.config/GIMP/3.2/gimp.css`.
## Flatpak install (`org.gimp.GIMP`)
Same file, inside the sandbox's own isolated config: `~/.var/app/org.gimp.GIMP/config/GIMP/3.2/
gimp.css`. No `flatpak override` needed, GIMP reads its own config from inside its sandbox
regardless of any host `~/.config` access.
## Testing
Tested against GIMP 3.2.4 (Flatpak). Verified visually across three passes: main window and
toolbox (`screenshot.png`), right-click context menus, and the Preferences dialog specifically
(`screenshot-preferences.png`), the hardest case since it touches nearly every one of GIMP's 17
variables plus the GTK fallback ones.
@@ -0,0 +1,119 @@
@define-color fg-color {{colors.on_surface.default.hex}};
@define-color bg-color {{colors.surface.default.hex}};
@define-color border-color {{colors.outline.default.hex}};
@define-color dimmed-fg-color {{colors.on_surface_variant.default.hex}};
@define-color disabled-fg-color {{colors.outline_variant.default.hex}};
@define-color disabled-button-color {{colors.surface_container_low.default.hex}};
@define-color hover-color {{colors.surface_container_high.default.hex}};
@define-color widget-bg-color {{colors.surface_container.default.hex}};
@define-color selected-color {{colors.primary.default.hex}};
@define-color extreme-bg-color {{colors.surface_container_low.default.hex}};
@define-color extreme-selected-color {{colors.primary.default.hex}};
@define-color strong-border-color {{colors.outline.default.hex}};
@define-color stronger-border-color {{colors.outline.default.hex}};
@define-color edge-border-color {{colors.outline_variant.default.hex}};
@define-color scrollbar-slider-color {{colors.outline.default.hex}};
@define-color scrollbar-trough-color {{colors.surface_container.default.hex}};
@define-color ruler-color {{colors.outline_variant.default.rgba | set_alpha: 0.3}};
/* Standard GTK/Adwaita variable names, not GIMP's own. Some widgets
(confirmed: the Preferences page title bar, per GIMP's own source
comment admitting it "doesn't have any more specific way to style it")
fall back to these instead of GIMP's custom palette above. Without
this they'd stay unthemed since the system gtk.css never loads inside
a Flatpak sandbox. */
@define-color theme_bg_color {{colors.surface.default.hex}};
@define-color theme_fg_color {{colors.on_surface.default.hex}};
@define-color theme_base_color {{colors.surface_container.default.hex}};
@define-color theme_text_color {{colors.on_surface.default.hex}};
@define-color theme_selected_bg_color {{colors.primary.default.hex}};
@define-color theme_selected_fg_color {{colors.on_primary.default.hex}};
@define-color borders {{colors.outline.default.hex}};
/* Most of GIMP's UI reuses selected-color/extreme-selected-color as a full
background fill for selection/hover/checked states. Left as bright
Noctalia yellow, that fill makes GIMP's own default (light) icon
rendering invisible on several widgets, confirmed live and not fixable
by recoloring the icon directly (tried extensively: direct color
targeting, wildcard inheritance, -gtk-icon-shadow; `filter` isn't even
a valid GTK3 CSS property at all, confirmed via a real parse error).
Real prior art settles this cleanly: community GIMP dark+accent themes
(e.g. github.com/bunnywaffle/GIMP-3-Themes, "Black Green Glow") never
use a bright accent as a full selection fill either, only as a thin
border stripe on top of a still-dark background. Applying that same
pattern here: every selector below keeps a dark, subtle background
(hover-color) and gets the yellow as a left border accent instead. No
per-widget icon or text color override is needed this way, since GIMP's
own default text/icon rendering already works fine against a dark
background, that's the entire point.
Uses box-shadow, not a real border: GIMP's own common.css already
renders a bottom accent bar this way in several places (e.g. `menubar >
menuitem:hover { box-shadow: inset 0 -3px @stronger-border-color; }`),
and stronger-border-color is mapped to Noctalia's outline role (a
blue-purple tone) elsewhere in this file, which is exactly the stray
blue bar seen live alongside an earlier version of this fix that used a
real `border` property instead: two separate indicators stacking, not
one. Matching GIMP's own mechanism here replaces that blue bar outright
instead of adding a second one next to it.
One deliberate exception, kept as a full yellow fill instead (below):
toolbox tool selection. Confirmed working with a full fill plus a dark
icon override, and that's the preferred look there specifically. The
Preferences category sidebar is a GtkTreeView (confirmed by its expander
chevrons), matched by `.view:selected` below, not a GtkListBox `list
row`, so it stays with this box-shadow treatment; pulling it out into a
full-fill override was tried and also recolored the page's title-bar
header the same way (both fall back to the same GTK theme variables),
which looked wrong, so left as-is. */
.view:selected,
.view row:selected,
menuitem *:hover,
popover list row:selected,
combobox button:hover,
notebook header button:hover,
notebook header tabs tab:checked,
GimpDeviceStatus widget:selected,
GimpDeviceStatus widget:selected box,
button:checked,
button.titlebutton:hover,
spinbutton button:hover {
background-color: {{colors.surface_container_high.default.hex}};
background-image: none;
color: {{colors.on_surface.default.hex}};
border-width: 0;
box-shadow: inset 0 -3px {{colors.primary.default.hex}};
}
/* Full yellow fill for the toolbox, matching selected-color's real value
(unchanged from GIMP's own base rule, no override needed for the
background itself). Text/icon color need the explicit dark override
GIMP's own CSS never provides. The small corner triangle marking a tool
group (e.g. on the Paintbrush/Pencil/Airbrush group) stays whatever
color GIMP draws it: confirmed no dedicated icon asset exists for it
anywhere in GIMP's own icon set, so it's a fixed-color overlay drawn
directly in GIMP's C code, not a themeable CSS node. */
toolbutton button.flat:hover,
toolbutton button.flat:checked,
toolbutton button.flat:checked:hover {
color: {{colors.on_primary.default.hex}};
}
toolbutton button.flat:checked image,
toolbutton button.flat:checked icon {
color: {{colors.on_primary.default.hex}};
}
/* GIMP's own theme has no general rule for GtkPopoverMenu backgrounds
(only narrow button/list-row styling), so context menus fall through
to GTK's stock light default without this. */
popover, popover contents, popover.menu, popover.background {
background-color: @bg-color;
color: @fg-color;
}
popover modelbutton, popover menuitem {
color: @fg-color;
}
popover modelbutton:hover, popover menuitem:hover {
background-color: @extreme-bg-color;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

@@ -0,0 +1,13 @@
[catalog.gimp]
name = "GIMP"
category = "editor"
[templates.gimp]
input_path = "gimp.css"
output_path = "~/.config/GIMP/3.2/gimp.css"
requires_path = "~/.config/GIMP"
[templates.gimp-flatpak]
input_path = "gimp.css"
output_path = "~/.var/app/org.gimp.GIMP/config/GIMP/3.2/gimp.css"
requires_path = "~/.var/app/org.gimp.GIMP"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "03732f9df248ab5075647e694192479d"
},
"version": 1
}
@@ -0,0 +1,7 @@
[catalog.glow]
name = "Glow"
category = "terminal"
[templates.glow]
input_path = "glow.json"
output_path = "$XDG_CONFIG_HOME/glow/noctalia.json"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "bffcacd872da4661aba3a4be3759027d"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.gruvbox-plus-icons]
name = "Gruvbox Plus Icon Theme"
category = "system"
[templates.gruvbox_plus_icons]
input_path = "colors"
output_path = "colors-final"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "4f13dc5fc08a089dad1426a692b45bb4"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.halloy]
name = "halloy"
category = "chat"
[templates.halloy]
input_path = "halloy.toml"
output_path = "$XDG_CONFIG_HOME/halloy/themes/noctalia.toml"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "fcdf331f3b03ce4d352930fbf6539e91"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.herdr]
name = "herdr"
category = "terminal"
[templates.herdr]
input_path = "herdr-colors.toml"
output_path = "$XDG_CONFIG_HOME/herdr/noctalia-colors.toml"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "0af766d5cbf7b20ef059d8f26c04afd4"
},
"version": 1
}
@@ -0,0 +1,13 @@
[catalog.heroiclauncher]
name = "Heroic Launcher"
category = "gaming"
[templates.heroiclauncher]
input_path = 'heroic.css'
output_path = '$XDG_CONFIG_HOME/heroic/themes/matugen.css'
requires_path = '~/.config/heroic'
[templates.heroiclauncher-flatpak]
input_path = 'heroic.css'
output_path = '~/.var/app/com.heroicgameslauncher.hgl/config/heroic/themes/matugen.css'
requires_path = '~/.var/app/com.heroicgameslauncher.hgl'
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "99b9324b0bdc834bfe71fb85febea677"
},
"version": 1
}
@@ -0,0 +1,7 @@
[catalog.hyprtoolkit]
name = "Hyprtoolkit"
category = "system"
[templates.hyprtoolkit]
input_path = "hyprtoolkit.conf"
output_path = "$XDG_CONFIG_HOME/hypr/hyprtoolkit.conf"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "4fe75090ad20b8fa348f9409deeb469e"
},
"version": 1
}
@@ -0,0 +1,13 @@
[catalog.inkscape]
name = "Inkscape"
category = "editor"
[templates.inkscape]
input_path = "user.css"
output_path = "~/.config/inkscape/ui/user.css"
requires_path = "~/.config/inkscape"
[templates.inkscape-flatpak]
input_path = "user.css"
output_path = "~/.var/app/org.inkscape.Inkscape/config/inkscape/ui/user.css"
requires_path = "~/.var/app/org.inkscape.Inkscape"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "e99b61279760be238ac83f299c5d5382"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.jay]
name = "Jay"
category = "compositor"
[templates.jay]
input_path = "jay.toml"
output_path = "$XDG_CACHE_HOME/noctalia/templates/jay-theme.toml"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "e2327271c97a1ab46a8ef7c16a0ef979"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.karere]
name = "Karere Custom CSS"
category = "chat"
[templates.karere]
input_path = "karere.css"
output_path = "$XDG_CONFIG_HOME/karere/custom.css"
requires_path = "$XDG_CONFIG_HOME/karere"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "787e3f76d9e2675a05eb6e1bd0bc4496"
},
"version": 1
}
@@ -0,0 +1,9 @@
[catalog.konsole]
name = "Konsole"
category = "terminal"
[templates.konsole]
input_path = "noctalia.colorscheme"
output_path = "$XDG_DATA_HOME/konsole/noctalia.colorscheme"
pre_hook = "bash '{{ config_dir }}/pre-apply.sh'"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "0497bb389ed096b6d40238f68e8d9162"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.lazygit]
name = "lazygit"
category = "terminal"
[templates.lazygit]
input_path = "lazygit.yml"
output_path = "$XDG_CONFIG_HOME/lazygit/themes/noctalia.yml"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "1f5ca395e7fe79bedb37b30bb74babac"
},
"version": 1
}
@@ -0,0 +1,9 @@
[catalog.lazyvim]
name = "LazyVim"
category = "editor"
[templates.lazyvim]
input_path = "noctalia.lua"
output_path = "$XDG_CONFIG_HOME/nvim/colors/noctalia.lua"
post_hook = "bash '{{ config_dir }}/apply.sh'"
requires_path = "$XDG_CONFIG_HOME/nvim/lazyvim.json"
@@ -0,0 +1,14 @@
{
"files": {
"Paths.xcu": "f0249a769fed68b4329baf57ac5c1bac",
"README.md": "c36760d40cae238078e2c8ceae480730",
"Theme_Colors.xcu": "9e3918407865e787a1081059d19187fd",
"apply.sh": "79b637c398e457e22e1975169c1f7f2f",
"description.xml": "810920710dcde3c1bfe9fb1ea671316c",
"light-screenshot.png": "ad688f5dc4feda44ed3da38827165497",
"pkg-description.en": "50f53d956476e61f87005bf9399e36f4",
"screenshot.png": "8f29907e3b5fd8a6471914503a3c8b2b",
"template.toml": "e1da6de79923919b05291dddf6ff5fd0"
},
"version": 1
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="Paths" oor:package="org.openoffice.Office">
<node oor:name="Paths">
<node oor:name="Palette">
<node oor:name="InternalPaths">
<node oor:name="%origin%/palettes" oor:op="fuse"/>
</node>
</node>
</node>
</oor:component-data>
@@ -0,0 +1,104 @@
# LibreOffice
Tested against LibreOffice 26.2.4.2 (Flatpak, `org.libreoffice.LibreOffice`).
## Why this ships as an extension, not a rendered config file
LibreOffice's main window chrome (menus, toolbars, buttons, dialogs) is drawn by its own widget
framework, not read from `gtk.css`. Confirmed live: an unmistakable
`window { background-color: #ff0000; }` override in
`~/.var/app/org.libreoffice.LibreOffice/config/gtk-3.0/gtk.css` had zero effect, even with the
GTK3 VCL plugin explicitly forced (`SAL_USE_VCLPLUGIN=gtk3`). LibreOffice's real theming
mechanism is its own **ColorScheme** system (Tools > Options > LibreOffice > Application Colors),
distributed as an installable `.oxt` extension package, the same format real community themes
like [libre-nord](https://github.com/gomesluiz/libre-nord) use.
## The property list is confirmed against LibreOffice's own source, not guessed
Every `ColorScheme` property name and its real meaning is confirmed against
[`officecfg/registry/schema/org/openoffice/Office/UI.xcs`](https://github.com/LibreOffice/core/blob/master/officecfg/registry/schema/org/openoffice/Office/UI.xcs)
in the LibreOffice/core repository, not a partial third-party example. This mattered in practice:
`FieldColor`'s own schema description is "Allows to overwrite the system field color", it
controls input/dropdown widget backgrounds, not document-embedded field shading the way its name
first suggests. An earlier pass mapped it to a literal document-shading color by analogy with a
third-party reference theme, and dropdown boxes, the Options sidebar, and text fields stayed on
a stock dark color as a result, exactly the kind of "confirmed working" that only turns out to
be partially true once someone actually looks closely. Fixed by mapping `FieldColor` to a real
Noctalia surface role, then cross-checking the *entire* property list against the schema (several
other properties, `DisabledColor`, `ActiveTextColor`, `InactiveColor`, and their pairs, were
missing from the theme entirely and are now set too).
Colors below are hex placeholders in the template source; `Theme_Colors.xcu` uses this schema's
own decimal-integer format, not hex, so `apply.sh` converts every value at render time (plain
`int(hex, 16)`, confirmed against the Document Foundation's own blog post examples).
## Document canvas and BASIC editor colors stay literal, deliberately
Same reasoning as darktable's neutral photo-viewing background: `DocColor`, `FontColor`,
`DocBoundaries`, `CalcGrid`, and the BASIC IDE syntax-highlighting colors are kept as sensible
literal light/dark values regardless of how dark the surrounding chrome is. A document is still
meant to look like a normal page (or be printed), and code syntax highlighting needs
distinguishable, conventional colors (keywords, strings, comments), not four or five UI accent
roles stretched over a dozen token types. These are functional, not chrome.
## Real gotchas found building this, not theoretical
- **The scheme has to be selected manually, once.** Installing the extension registers "Noctalia"
as an available option in the Scheme dropdown, it does not auto-select it. Confirmed live: same
behavior class as darktable's own theme dropdown. Go to Tools > Options > LibreOffice >
Application Colors, pick "Noctalia" from the Scheme dropdown, click Apply/OK. After that one
manual step, every later theme change re-renders and reinstalls the extension automatically
through Noctalia's own template pipeline, no further manual selection needed.
- **A restart is needed to see updated colors**, same as Heroic's own documented limitation.
Confirmed live: switching Noctalia's theme mode while LibreOffice was already open did not
change the running window at all; closing and reopening it did. This is not a bug in the
template, extensions are loaded at LibreOffice startup.
- **`unopkg add --force` alone is not reliable for reinstalling the same extension identifier.**
Confirmed live, the hard way: after several reinstalls it left the extension in a broken state,
`unopkg list` reported nothing installed at all, while a stale binary cache index
(`uno_packages/cache/uno_packages.pmap`) still pointed at a deleted temp directory, causing
every subsequent install attempt to fail with "file opening ... NOT_EXISTING" until the whole
cache directory was cleared by hand. `apply.sh` now explicitly `unopkg remove`s the extension
identifier before every `add`, ignoring failure since it may not be installed yet, rather than
relying on `--force` to handle that atomically.
- **`unopkg add` while LibreOffice is running can corrupt the install the same way.** `apply.sh`
checks for a running `soffice.bin` process first and skips the install step entirely if found,
rather than risk it, the `.oxt` is still rebuilt with the current colors either way, so the next
render (once LibreOffice is closed) or the next manual re-apply installs them, and a restart is
already required regardless.
- **`unopkg` is not on the sandbox's `PATH`** inside the Flatpak. Confirmed live:
`flatpak run --command=unopkg ...` fails with "No such file or directory", the full path
`/app/libreoffice/program/unopkg` is required.
- **If LibreOffice was open at the exact moment the theme changed, the install-skip above has no
automatic retry.** The `.oxt` still gets rebuilt with the current colors every render, but
since the install step is skipped while LibreOffice is running, closing LibreOffice alone is
not enough, nothing re-triggers the install afterward on its own. Confirmed live: after
changing the palette while LibreOffice was open, colors stayed stale even after closing and
reopening it, until `apply.sh` was run again by hand with LibreOffice closed. In practice this
means a color change made while LibreOffice happens to be open may need the theme re-applied
once more (`noctalia msg templates-apply`) after closing it, not just a restart.
## Native install
Same `unopkg` mechanism, invoked directly. `apply.sh` resolves `unopkg` from `PATH` first, then
falls back to the official TDF install layout (`/opt/libreoffice*/program/unopkg`,
`/usr/lib/libreoffice/program/unopkg`, `/usr/lib64/libreoffice/program/unopkg`) so the official
`.deb`/`.rpm` packages, which do **not** put `unopkg` on `PATH`, are detected too. If no native
`unopkg` is found it falls back to the Flatpak `flatpak run --command=` form.
**Verified live** against LibreOffice 26.2.4.2 from the official TDF `.deb` packages on
Kali Linux (amd64), including a clean-PATH run where `command -v unopkg` returned nothing and
`/opt/libreoffice26.2/program/unopkg` was picked up by the fallback; the extension installed
and registered correctly.
## Flatpak install (`org.libreoffice.LibreOffice`)
Extension cache lives at
`~/.var/app/org.libreoffice.LibreOffice/config/libreoffice/4/user/uno_packages/`. No
`flatpak override` needed.
## Status
Verified live against LibreOffice 26.2.4.2 (Flatpak) through Noctalia's own real render pipeline,
`community_ids` + local `community-templates/libreoffice/`, both dark and light mode, confirmed
after a restart in each case. Native install path is implemented but not live-tested.
@@ -0,0 +1,64 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Colors below are hex placeholders, {{ config_dir }}/apply.sh converts them to the decimal
integers this schema actually requires (standard int(hex, 16) RRGGBB conversion, confirmed
against LibreOffice/core's own officecfg/registry/schema/org/openoffice/Office/UI.xcs, the
authoritative source for every ColorScheme property name and description used below,
including FieldColor, which really does control input-widget backgrounds despite its name
reading like a document-field-shading setting at first glance). Chrome roles map to Noctalia
colors. Document/BASIC-editor colors stay literal, legible regardless of chrome darkness,
same reasoning as darktable's neutral photo-viewing background: these are functional, not
chrome. -->
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" oor:name="UI" oor:package="org.openoffice.Office">
<node oor:name="ColorScheme">
<node oor:name="ColorSchemes">
<node oor:name="Noctalia" oor:op="fuse">
<node oor:name="AppBackground"><prop oor:name="Color" oor:op="fuse"><value>{{colors.surface.default.hex_stripped}}</value></prop></node>
<node oor:name="DocColor"><prop oor:name="Color" oor:op="fuse"><value>f7f7fa</value></prop></node>
<node oor:name="FontColor"><prop oor:name="Color" oor:op="fuse"><value>1a1a1a</value></prop></node>
<node oor:name="DocBoundaries"><prop oor:name="Color" oor:op="fuse"><value>cccccc</value></prop></node>
<node oor:name="WindowColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.surface.default.hex_stripped}}</value></prop></node>
<node oor:name="WindowTextColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.on_surface.default.hex_stripped}}</value></prop></node>
<node oor:name="BaseColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.surface.default.hex_stripped}}</value></prop></node>
<node oor:name="ButtonColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.surface_container_high.default.hex_stripped}}</value></prop></node>
<node oor:name="ButtonTextColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.on_surface.default.hex_stripped}}</value></prop></node>
<node oor:name="AccentColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.primary.default.hex_stripped}}</value></prop></node>
<node oor:name="DisabledColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.outline_variant.default.hex_stripped}}</value></prop></node>
<node oor:name="DisabledTextColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.on_surface_variant.default.hex_stripped}}</value></prop></node>
<node oor:name="ShadowColor"><prop oor:name="Color" oor:op="fuse"><value>808080</value></prop></node>
<node oor:name="Shadow"><prop oor:name="Color" oor:op="fuse"><value xsi:nil="false" /></prop><prop oor:name="IsVisible" oor:op="fuse"><value>false</value></prop></node>
<node oor:name="SeparatorColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.outline_variant.default.hex_stripped}}</value></prop></node>
<node oor:name="FaceColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.surface_container.default.hex_stripped}}</value></prop></node>
<node oor:name="ActiveColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.primary.default.hex_stripped}}</value></prop></node>
<node oor:name="ActiveTextColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.on_primary.default.hex_stripped}}</value></prop></node>
<node oor:name="ActiveBorderColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.primary.default.hex_stripped}}</value></prop></node>
<node oor:name="FieldColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.surface_container_high.default.hex_stripped}}</value></prop></node>
<node oor:name="MenuBarColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.surface_container.default.hex_stripped}}</value></prop></node>
<node oor:name="MenuBarTextColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.on_surface.default.hex_stripped}}</value></prop></node>
<node oor:name="MenuBarHighlightColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.primary.default.hex_stripped}}</value></prop></node>
<node oor:name="MenuBarHighlightTextColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.on_primary.default.hex_stripped}}</value></prop></node>
<node oor:name="MenuColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.surface_container.default.hex_stripped}}</value></prop></node>
<node oor:name="MenuTextColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.on_surface.default.hex_stripped}}</value></prop></node>
<node oor:name="MenuHighlightColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.primary.default.hex_stripped}}</value></prop></node>
<node oor:name="MenuHighlightTextColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.on_primary.default.hex_stripped}}</value></prop></node>
<node oor:name="MenuBorderColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.outline.default.hex_stripped}}</value></prop></node>
<node oor:name="InactiveColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.surface_container.default.hex_stripped}}</value></prop></node>
<node oor:name="InactiveTextColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.on_surface_variant.default.hex_stripped}}</value></prop></node>
<node oor:name="InactiveBorderColor"><prop oor:name="Color" oor:op="fuse"><value>{{colors.outline_variant.default.hex_stripped}}</value></prop></node>
<node oor:name="BASICEditor"><prop oor:name="Color" oor:op="fuse"><value>ffffff</value></prop></node>
<node oor:name="BASICError"><prop oor:name="Color" oor:op="fuse"><value>d32f2f</value></prop></node>
<node oor:name="BASICIdentifier"><prop oor:name="Color" oor:op="fuse"><value>1a1a1a</value></prop></node>
<node oor:name="BASICKeyword"><prop oor:name="Color" oor:op="fuse"><value>0b57d0</value></prop></node>
<node oor:name="BASICNumber"><prop oor:name="Color" oor:op="fuse"><value>d97706</value></prop></node>
<node oor:name="BASICOperator"><prop oor:name="Color" oor:op="fuse"><value>444444</value></prop></node>
<node oor:name="BASICString"><prop oor:name="Color" oor:op="fuse"><value>15803d</value></prop></node>
<node oor:name="BASICComment"><prop oor:name="Color" oor:op="fuse"><value>6b7280</value></prop></node>
<node oor:name="WriterIdxShadings"><prop oor:name="Color" oor:op="fuse"><value>e8e8e8</value></prop><prop oor:name="IsVisible" oor:op="fuse"><value>true</value></prop></node>
<node oor:name="WriterFieldShadings"><prop oor:name="Color" oor:op="fuse"><value>e8e8e8</value></prop><prop oor:name="IsVisible" oor:op="fuse"><value>true</value></prop></node>
<node oor:name="CalcGrid"><prop oor:name="Color" oor:op="fuse"><value>d9d9d9</value></prop></node>
</node>
</node>
</node>
</oor:component-data>
@@ -0,0 +1,130 @@
#!/usr/bin/env bash
# Runs on every theme change (post_hook). Converts the rendered Theme_Colors.xcu's
# hex color values to the decimal integers LibreOffice's ColorScheme schema
# actually requires, assembles a fresh .oxt from this directory's static files
# plus the converted colors, and installs it via unopkg.
set -euo pipefail
CONFIG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RENDERED="${XDG_STATE_HOME:-$HOME/.local/state}/noctalia/libreoffice-theme-staging/Theme_Colors.xcu"
BUILD_DIR="$CONFIG_DIR/build"
OXT_PATH="$BUILD_DIR/noctalia-theme.oxt"
if [ ! -f "$RENDERED" ]; then
echo "noctalia libreoffice: rendered Theme_Colors.xcu not found at $RENDERED" >&2
exit 1
fi
rm -rf "$BUILD_DIR/pkg"
mkdir -p "$BUILD_DIR/pkg/META-INF"
# Convert every 6-hex-digit <value>...</value> to its decimal equivalent.
# ColorScheme's schema stores colors as base-10 integers, not hex strings.
python3 - "$RENDERED" "$BUILD_DIR/pkg/Theme_Colors.xcu" << 'PYEOF'
import re
import sys
src, dst = sys.argv[1], sys.argv[2]
with open(src) as f:
content = f.read()
def hex_to_dec(match):
return f"<value>{int(match.group(1), 16)}</value>"
converted = re.sub(r"<value>([0-9a-fA-F]{6})</value>", hex_to_dec, content)
with open(dst, "w") as f:
f.write(converted)
PYEOF
cp "$CONFIG_DIR/Paths.xcu" "$BUILD_DIR/pkg/Paths.xcu"
cp "$CONFIG_DIR/description.xml" "$BUILD_DIR/pkg/description.xml"
cp "$CONFIG_DIR/pkg-description.en" "$BUILD_DIR/pkg/pkg-description.en"
# Write the package manifest inline rather than copying it from the template directory.
# The template distribution pipeline only ships top-level files, so the
# META-INF/manifest.xml that used to live here was never delivered to users and
# the previous `cp` aborted under set -euo pipefail before the .oxt was built.
# The manifest is static and small, so generating it here keeps the template
# self-contained and removes the only subdirectory dependency.
cat > "$BUILD_DIR/pkg/META-INF/manifest.xml" <<'MANIFEST'
<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
<manifest:file-entry manifest:full-path="Theme_Colors.xcu" manifest:media-type="application/vnd.sun.star.configuration-data"/>
<manifest:file-entry manifest:full-path="Paths.xcu" manifest:media-type="application/vnd.sun.star.configuration-data"/>
</manifest:manifest>
MANIFEST
rm -f "$OXT_PATH"
(cd "$BUILD_DIR/pkg" && zip -qr "$OXT_PATH" .)
# unopkg add --force while LibreOffice is running has been confirmed live to
# silently corrupt the install (it can remove the old registration before
# failing to add the new one, leaving nothing installed at all). Skip the
# install step entirely rather than risk that -- the .oxt is still rebuilt
# above so the next run (once LibreOffice is closed) installs the current
# colors, and a restart is already required to see any change regardless.
if pgrep -x soffice.bin >/dev/null 2>&1; then
# The .oxt above is already rebuilt with the current colors, only the
# install step is skipped here. A restart alone does NOT pick up anything
# new, the previously-installed extension is still what's registered.
# Close LibreOffice, then re-run the theme apply (or wait for the next
# theme change) to actually install this build.
echo "noctalia libreoffice: LibreOffice is running, skipping extension install this run (installing while it's open has been confirmed to corrupt the existing install). Close LibreOffice, then re-apply the theme to actually install these colors, a restart alone will not pick them up." >&2
exit 0
fi
EXT_ID="dev.noctalia.libreoffice.theme"
INSTALLED_ANY=0
# `add --force` alone has been confirmed live to leave the extension in a
# broken state (a stale cache index pointing at a deleted temp dir, `list`
# then reports nothing installed at all, and every later reinstall fails
# with "file opening ... NOT_EXISTING" until the cache is cleared by hand).
# Explicitly removing the old registration first, ignoring failure since it
# may not be installed yet, avoids relying on --force to do that atomically.
# Install to every LibreOffice found, not just whichever `unopkg` a plain
# PATH lookup happens to resolve first. A machine with both a native
# LibreOffice (providing `unopkg` on PATH) and the Flatpak (the tested
# target) installed would otherwise only get themed on whichever one won
# that lookup, silently leaving the other one unthemed.
#
# Native detection here also covers the official TDF `.deb`/`.rpm` layout,
# which installs into /opt/libreoffice<major>.<minor>/ and does NOT place
# `unopkg` on PATH. `command -v unopkg` alone misses it, so fall back to the
# known install dirs before concluding there is no native install.
UNOPKG="$(command -v unopkg || true)"
if [ -z "$UNOPKG" ]; then
for candidate in /opt/libreoffice*/program/unopkg \
/usr/lib/libreoffice/program/unopkg \
/usr/lib64/libreoffice/program/unopkg; do
if [ -x "$candidate" ]; then
UNOPKG="$candidate"
break
fi
done
fi
if [ -n "$UNOPKG" ]; then
"$UNOPKG" remove "$EXT_ID" >/dev/null 2>&1 || true
if "$UNOPKG" add --force "$OXT_PATH" 2>&1; then
INSTALLED_ANY=1
else
echo "noctalia libreoffice: native unopkg install failed ($UNOPKG)" >&2
fi
fi
if flatpak info org.libreoffice.LibreOffice >/dev/null 2>&1; then
flatpak run --command=/app/libreoffice/program/unopkg org.libreoffice.LibreOffice remove "$EXT_ID" >/dev/null 2>&1 || true
if flatpak run --command=/app/libreoffice/program/unopkg org.libreoffice.LibreOffice add --force "$OXT_PATH" 2>&1; then
INSTALLED_ANY=1
else
echo "noctalia libreoffice: Flatpak unopkg install failed" >&2
fi
fi
if [ "$INSTALLED_ANY" -eq 0 ]; then
echo "noctalia libreoffice: no LibreOffice installation found (neither native unopkg on PATH nor the org.libreoffice.LibreOffice Flatpak)" >&2
fi
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
<manifest:file-entry manifest:full-path="Theme_Colors.xcu" manifest:media-type="application/vnd.sun.star.configuration-data"/>
<manifest:file-entry manifest:full-path="Paths.xcu" manifest:media-type="application/vnd.sun.star.configuration-data"/>
</manifest:manifest>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="Paths" oor:package="org.openoffice.Office">
<node oor:name="Paths">
<node oor:name="Palette">
<node oor:name="InternalPaths">
<node oor:name="%origin%/palettes" oor:op="fuse"/>
</node>
</node>
</node>
</oor:component-data>
@@ -0,0 +1,64 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Colors below are hex placeholders, /home/yuri/.local/state/noctalia/community-templates/libreoffice/apply.sh converts them to the decimal
integers this schema actually requires (standard int(hex, 16) RRGGBB conversion, confirmed
against LibreOffice/core's own officecfg/registry/schema/org/openoffice/Office/UI.xcs, the
authoritative source for every ColorScheme property name and description used below,
including FieldColor, which really does control input-widget backgrounds despite its name
reading like a document-field-shading setting at first glance). Chrome roles map to Noctalia
colors. Document/BASIC-editor colors stay literal, legible regardless of chrome darkness,
same reasoning as darktable's neutral photo-viewing background: these are functional, not
chrome. -->
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" oor:name="UI" oor:package="org.openoffice.Office">
<node oor:name="ColorScheme">
<node oor:name="ColorSchemes">
<node oor:name="Noctalia" oor:op="fuse">
<node oor:name="AppBackground"><prop oor:name="Color" oor:op="fuse"><value>1250068</value></prop></node>
<node oor:name="DocColor"><prop oor:name="Color" oor:op="fuse"><value>16250874</value></prop></node>
<node oor:name="FontColor"><prop oor:name="Color" oor:op="fuse"><value>1710618</value></prop></node>
<node oor:name="DocBoundaries"><prop oor:name="Color" oor:op="fuse"><value>13421772</value></prop></node>
<node oor:name="WindowColor"><prop oor:name="Color" oor:op="fuse"><value>1250068</value></prop></node>
<node oor:name="WindowTextColor"><prop oor:name="Color" oor:op="fuse"><value>15000290</value></prop></node>
<node oor:name="BaseColor"><prop oor:name="Color" oor:op="fuse"><value>1250068</value></prop></node>
<node oor:name="ButtonColor"><prop oor:name="Color" oor:op="fuse"><value>2763306</value></prop></node>
<node oor:name="ButtonTextColor"><prop oor:name="Color" oor:op="fuse"><value>15000290</value></prop></node>
<node oor:name="AccentColor"><prop oor:name="Color" oor:op="fuse"><value>12568530</value></prop></node>
<node oor:name="DisabledColor"><prop oor:name="Color" oor:op="fuse"><value>4474699</value></prop></node>
<node oor:name="DisabledTextColor"><prop oor:name="Color" oor:op="fuse"><value>12961483</value></prop></node>
<node oor:name="ShadowColor"><prop oor:name="Color" oor:op="fuse"><value>8421504</value></prop></node>
<node oor:name="Shadow"><prop oor:name="Color" oor:op="fuse"><value xsi:nil="false" /></prop><prop oor:name="IsVisible" oor:op="fuse"><value>false</value></prop></node>
<node oor:name="SeparatorColor"><prop oor:name="Color" oor:op="fuse"><value>4474699</value></prop></node>
<node oor:name="FaceColor"><prop oor:name="Color" oor:op="fuse"><value>2039584</value></prop></node>
<node oor:name="ActiveColor"><prop oor:name="Color" oor:op="fuse"><value>12568530</value></prop></node>
<node oor:name="ActiveTextColor"><prop oor:name="Color" oor:op="fuse"><value>2699577</value></prop></node>
<node oor:name="ActiveBorderColor"><prop oor:name="Color" oor:op="fuse"><value>12568530</value></prop></node>
<node oor:name="FieldColor"><prop oor:name="Color" oor:op="fuse"><value>2763306</value></prop></node>
<node oor:name="MenuBarColor"><prop oor:name="Color" oor:op="fuse"><value>2039584</value></prop></node>
<node oor:name="MenuBarTextColor"><prop oor:name="Color" oor:op="fuse"><value>15000290</value></prop></node>
<node oor:name="MenuBarHighlightColor"><prop oor:name="Color" oor:op="fuse"><value>12568530</value></prop></node>
<node oor:name="MenuBarHighlightTextColor"><prop oor:name="Color" oor:op="fuse"><value>2699577</value></prop></node>
<node oor:name="MenuColor"><prop oor:name="Color" oor:op="fuse"><value>2039584</value></prop></node>
<node oor:name="MenuTextColor"><prop oor:name="Color" oor:op="fuse"><value>15000290</value></prop></node>
<node oor:name="MenuHighlightColor"><prop oor:name="Color" oor:op="fuse"><value>12568530</value></prop></node>
<node oor:name="MenuHighlightTextColor"><prop oor:name="Color" oor:op="fuse"><value>2699577</value></prop></node>
<node oor:name="MenuBorderColor"><prop oor:name="Color" oor:op="fuse"><value>9343381</value></prop></node>
<node oor:name="InactiveColor"><prop oor:name="Color" oor:op="fuse"><value>2039584</value></prop></node>
<node oor:name="InactiveTextColor"><prop oor:name="Color" oor:op="fuse"><value>12961483</value></prop></node>
<node oor:name="InactiveBorderColor"><prop oor:name="Color" oor:op="fuse"><value>4474699</value></prop></node>
<node oor:name="BASICEditor"><prop oor:name="Color" oor:op="fuse"><value>16777215</value></prop></node>
<node oor:name="BASICError"><prop oor:name="Color" oor:op="fuse"><value>13840175</value></prop></node>
<node oor:name="BASICIdentifier"><prop oor:name="Color" oor:op="fuse"><value>1710618</value></prop></node>
<node oor:name="BASICKeyword"><prop oor:name="Color" oor:op="fuse"><value>743376</value></prop></node>
<node oor:name="BASICNumber"><prop oor:name="Color" oor:op="fuse"><value>14251782</value></prop></node>
<node oor:name="BASICOperator"><prop oor:name="Color" oor:op="fuse"><value>4473924</value></prop></node>
<node oor:name="BASICString"><prop oor:name="Color" oor:op="fuse"><value>1409085</value></prop></node>
<node oor:name="BASICComment"><prop oor:name="Color" oor:op="fuse"><value>7041664</value></prop></node>
<node oor:name="WriterIdxShadings"><prop oor:name="Color" oor:op="fuse"><value>15263976</value></prop><prop oor:name="IsVisible" oor:op="fuse"><value>true</value></prop></node>
<node oor:name="WriterFieldShadings"><prop oor:name="Color" oor:op="fuse"><value>15263976</value></prop><prop oor:name="IsVisible" oor:op="fuse"><value>true</value></prop></node>
<node oor:name="CalcGrid"><prop oor:name="Color" oor:op="fuse"><value>14277081</value></prop></node>
</node>
</node>
</node>
</oor:component-data>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006"
xmlns:d="http://openoffice.org/extensions/description/2006"
xmlns:xlink="http://www.w3.org/1999/xlink">
<identifier value="dev.noctalia.libreoffice.theme" />
<version value="1.0.0" />
<publisher>
<name xlink:href="https://github.com/noctalia-dev/community-templates" lang="en">Noctalia</name>
</publisher>
<display-name>
<name lang="en">Noctalia Theme</name>
</display-name>
<extension-description>
<src xlink:href="pkg-description.en" lang="en" />
</extension-description>
</description>
@@ -0,0 +1,2 @@
Colors LibreOffice's application chrome (menus, toolbars, buttons, dialogs) to match the active
Noctalia palette. Installed and kept in sync automatically by Noctalia's own template pipeline.
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006"
xmlns:d="http://openoffice.org/extensions/description/2006"
xmlns:xlink="http://www.w3.org/1999/xlink">
<identifier value="dev.noctalia.libreoffice.theme" />
<version value="1.0.0" />
<publisher>
<name xlink:href="https://github.com/noctalia-dev/community-templates" lang="en">Noctalia</name>
</publisher>
<display-name>
<name lang="en">Noctalia Theme</name>
</display-name>
<extension-description>
<src xlink:href="pkg-description.en" lang="en" />
</extension-description>
</description>
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

@@ -0,0 +1,2 @@
Colors LibreOffice's application chrome (menus, toolbars, buttons, dialogs) to match the active
Noctalia palette. Installed and kept in sync automatically by Noctalia's own template pipeline.
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

@@ -0,0 +1,8 @@
[catalog.libreoffice]
name = "LibreOffice"
category = "editor"
[templates.libreoffice]
input_path = "Theme_Colors.xcu"
output_path = "$XDG_STATE_HOME/noctalia/libreoffice-theme-staging/Theme_Colors.xcu"
post_hook = "bash '{{ config_dir }}/apply.sh'"
@@ -0,0 +1,6 @@
{
"files": {
"template.toml": "73d384d321a6f561082a439d8ef474ab"
},
"version": 1
}
@@ -0,0 +1,8 @@
[catalog.lsd]
name = "lsd"
category = "terminal"
[templates.lsd]
input_path = "colors.yaml"
output_path = "$XDG_CONFIG_HOME/lsd/colors.yaml"
post_hook = "bash '{{ config_dir }}/apply.sh'"

Some files were not shown because too many files have changed in this diff Show More