feat: bundle Terebi Sunset as a selectable local theme
This commit is contained in:
@@ -11,7 +11,7 @@ DARK_NOTIFY_LOG := $(HOME)/Library/Logs/dark-notify.log
|
||||
CONFIG_MARKER := $(CONFIG_DIR)/.tint-installed-config
|
||||
STATE_MARKER := $(CONFIG_DIR)/.tint-installed-state
|
||||
|
||||
.PHONY: install uninstall
|
||||
.PHONY: install install-schemes uninstall
|
||||
install: default-state.toml config.toml
|
||||
@command -v tinty >/dev/null || { echo "tinty is required" >&2; exit 1; }
|
||||
@command -v dark-notify >/dev/null || { echo "dark-notify is required" >&2; exit 1; }
|
||||
@@ -31,6 +31,7 @@ install: default-state.toml config.toml
|
||||
touch "$(HERDR_CONFIG)" "$(HERDR_MARKER)"; \
|
||||
fi
|
||||
XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty sync
|
||||
$(MAKE) install-schemes
|
||||
@dark_notify="$$(command -v dark-notify)"; \
|
||||
tinty_dir="$$(dirname "$$(command -v tinty)")"; \
|
||||
printf '%s\n' \
|
||||
@@ -69,9 +70,19 @@ install: default-state.toml config.toml
|
||||
@launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true
|
||||
launchctl bootstrap "gui/$$(id -u)" "$(LAUNCH_AGENT)"
|
||||
|
||||
install-schemes: schemes/base16/terebi-sunset.yaml
|
||||
@set -e; \
|
||||
data_dir="$$(XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty config --data-dir-path)"; \
|
||||
mkdir -p "$$data_dir/custom-schemes/base16"; \
|
||||
cp schemes/base16/terebi-sunset.yaml "$$data_dir/custom-schemes/base16/terebi-sunset.yaml"
|
||||
|
||||
uninstall:
|
||||
@launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true
|
||||
rm -f "$(BIN_DIR)/tint" "$(LAUNCH_AGENT)" "$(DARK_NOTIFY_LOG)"
|
||||
@if command -v tinty >/dev/null; then \
|
||||
data_dir="$$(XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty config --data-dir-path)" && \
|
||||
rm -f "$$data_dir/custom-schemes/base16/terebi-sunset.yaml"; \
|
||||
fi
|
||||
@if [ -e "$(STATE_MARKER)" ]; then \
|
||||
rm -f "$(CONFIG_DIR)/state.toml" "$(STATE_MARKER)"; \
|
||||
fi
|
||||
|
||||
@@ -38,6 +38,7 @@ The installer:
|
||||
- Copies the versioned `default-state.toml` to the installed `state.toml` only when no state file already exists.
|
||||
- Creates `~/.config/herdr/config.toml` if needed.
|
||||
- Runs `tinty sync` to download the configured theme repositories.
|
||||
- Installs bundled custom schemes into Tinty's data directory.
|
||||
- Creates and loads a `dark-notify` LaunchAgent for automatic macOS appearance switching.
|
||||
|
||||
Add the binary directory to your shell path:
|
||||
@@ -172,6 +173,23 @@ TINTY_PAIR_APPEARANCE=light tint swamp
|
||||
TINTY_PAIR_APPEARANCE=dark tint swamp
|
||||
```
|
||||
|
||||
### Terebi Sunset
|
||||
|
||||
The bundled [`base16-terebi-sunset`](schemes/base16/terebi-sunset.yaml) palette comes from the sibling Terebi project. It preserves the player's brown, cream, sand, orange and ember colors and adds the remaining Base16 syntax hues. It is dark-only; keep your existing light theme.
|
||||
|
||||
`make install` installs it automatically. To install just the palette and updated wrapper without syncing repositories or reloading the appearance agent:
|
||||
|
||||
```sh
|
||||
make install-schemes
|
||||
go build -o "$HOME/.local/bin/tint" .
|
||||
tint favorite base16-terebi-sunset
|
||||
tint dark:base16-terebi-sunset
|
||||
```
|
||||
|
||||
Setting the dark side preserves the light side and applies whichever side matches macOS appearance. Use `tint dark` to apply the saved dark side immediately.
|
||||
|
||||
Tinty stores the YAML under `$(tinty config --data-dir-path)/custom-schemes/base16/terebi-sunset.yaml` and builds the installed app templates when applying it. Both theme validation and app palette lookup support [Tinty's native custom schemes](https://github.com/tinted-theming/tinty/blob/v0.34.0/USAGE.md#use-your-own-schemes), so Ghostty, Zed, Herdr, OMP and Conjure use the normal application flow. List local palettes with `tinty list --custom-schemes`. Reinstalling updates the bundled palette without changing the saved pair or favorites; uninstalling removes the bundled YAML.
|
||||
|
||||
## Update
|
||||
|
||||
After changing or updating the wrapper source:
|
||||
|
||||
@@ -51,7 +51,9 @@ func currentScheme() (tintyScheme, error) {
|
||||
if err != nil {
|
||||
return tintyScheme{}, err
|
||||
}
|
||||
output, err := exec.Command("tinty", "list", "--json").Output()
|
||||
id := strings.TrimSpace(string(current))
|
||||
for _, args := range [][]string{{"list", "--json"}, {"list", "--json", "--custom-schemes"}} {
|
||||
output, err := exec.Command("tinty", args...).Output()
|
||||
if err != nil {
|
||||
return tintyScheme{}, err
|
||||
}
|
||||
@@ -59,12 +61,12 @@ func currentScheme() (tintyScheme, error) {
|
||||
if err := json.Unmarshal(output, &schemes); err != nil {
|
||||
return tintyScheme{}, err
|
||||
}
|
||||
id := strings.TrimSpace(string(current))
|
||||
for _, scheme := range schemes {
|
||||
if scheme.ID == id {
|
||||
return scheme, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return tintyScheme{}, fmt.Errorf("scheme not found: %s", id)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCustomSchemeCanBeFavoritedAndAppliedToApps(t *testing.T) {
|
||||
tinty, err := exec.LookPath("tinty")
|
||||
if err != nil {
|
||||
t.Skip("requires tinty 0.34+")
|
||||
}
|
||||
home := t.TempDir()
|
||||
data := filepath.Join(home, "data")
|
||||
bin := filepath.Join(home, "bin")
|
||||
for _, dir := range []string{bin, filepath.Join(data, "repos", "schemes", "base16"), filepath.Join(data, "custom-schemes", "base16"), filepath.Join(home, ".config", "herdr")} {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
palette, err := os.ReadFile("schemes/base16/terebi-sunset.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
files := map[string][]byte{
|
||||
filepath.Join(data, "custom-schemes", "base16", "terebi-sunset.yaml"): palette,
|
||||
filepath.Join(home, "config.toml"): []byte("items = []\n"),
|
||||
filepath.Join(home, ".config", "herdr", "config.toml"): []byte("[server]\nport = 8080\n"),
|
||||
filepath.Join(home, "zed.json"): []byte(`{"name":"Terebi Sunset","themes":[{"name":"Terebi Sunset","appearance":"dark"}]}`),
|
||||
}
|
||||
for path, contents := range files {
|
||||
if err := os.WriteFile(path, contents, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
writeExecutable(t, filepath.Join(bin, "tinty"), `#!/bin/sh
|
||||
exec "$TINTY_TEST_BINARY" --data-dir "$TINTY_TEST_DATA" --config "$TINTY_TEST_CONFIG" "$@"
|
||||
`)
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
|
||||
t.Setenv("PATH", bin+":/usr/bin:/bin")
|
||||
t.Setenv("TINTY_TEST_BINARY", tinty)
|
||||
t.Setenv("TINTY_TEST_DATA", data)
|
||||
t.Setenv("TINTY_TEST_CONFIG", filepath.Join(home, "config.toml"))
|
||||
statePath := filepath.Join(home, "state.toml")
|
||||
t.Setenv("TINT_STATE_FILE", statePath)
|
||||
initial := state{Current: currentPair{Light: "personal-light", Dark: "personal-dark"}}
|
||||
if err := saveState(statePath, initial); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
if err := runCLI([]string{"favorite", "base16-terebi-sunset"}, &stdout, &stderr); err != nil {
|
||||
t.Fatalf("custom scheme could not be favorited: %v: %s", err, stderr.String())
|
||||
}
|
||||
value, err := loadState(statePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if value.Current != initial.Current || len(value.Catalog.FavoriteThemes) != 1 || value.Catalog.FavoriteThemes[0] != "base16-terebi-sunset" {
|
||||
t.Fatalf("favorite changed the current pair or omitted the scheme: %#v", value)
|
||||
}
|
||||
before := readFile(t, statePath)
|
||||
if err := runCLI([]string{"favorite", "base16-missing"}, &stdout, &stderr); err == nil {
|
||||
t.Fatal("accepted a nonexistent custom scheme")
|
||||
}
|
||||
if readFile(t, statePath) != before {
|
||||
t.Fatal("invalid scheme changed saved state")
|
||||
}
|
||||
if output, err := exec.Command("tinty", "apply", "base16-terebi-sunset").CombinedOutput(); err != nil {
|
||||
t.Fatalf("Tinty could not apply the custom scheme: %v\n%s", err, output)
|
||||
}
|
||||
if err := applyApps(filepath.Join(home, "zed.json")); err != nil {
|
||||
t.Fatalf("custom scheme could not be applied: %v", err)
|
||||
}
|
||||
var omp struct {
|
||||
Colors map[string]string `json:"colors"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(home, ".omp", "agent", "themes", "tinty-current.json"))), &omp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if omp.Colors["text"] != "#ffe3b5" || omp.Colors["syntaxNumber"] != "#f2943b" {
|
||||
t.Fatalf("OMP did not receive the custom scheme: %#v", omp.Colors)
|
||||
}
|
||||
var conjure struct {
|
||||
Variant string `json:"variant"`
|
||||
Colors map[string]string `json:"colors"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(home, ".config", "conjure", "theme.json"))), &conjure); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if conjure.Variant != "dark" || conjure.Colors["background"] != "#1f1714" || conjure.Colors["secondaryText"] != "#d9c7a6" {
|
||||
t.Fatalf("Conjure did not receive the custom scheme: %#v", conjure)
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ themes = [
|
||||
"base16-selenized-light",
|
||||
"base16-swamp-dark",
|
||||
"base16-swamp-light",
|
||||
"base16-terebi-sunset",
|
||||
"base24-ic-orange-ppl",
|
||||
"base24-later-this-evening",
|
||||
"tinted8-gruvbox-dark",
|
||||
|
||||
@@ -34,8 +34,12 @@ func TestInstallFromFreshCheckout(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := os.CopyFS(filepath.Join(source, "schemes"), os.DirFS("schemes")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeExecutable(t, filepath.Join(bin, "tinty"), `#!/bin/sh
|
||||
case "$1" in
|
||||
config) printf '%s/tinted-theming/tinty\n' "$XDG_DATA_HOME" ;;
|
||||
sync|info) exit 0 ;;
|
||||
apply) printf '%s\n' "$2" >> "$TINTY_LOG" ;;
|
||||
*) exit 2 ;;
|
||||
@@ -54,6 +58,7 @@ esac
|
||||
t.Setenv("GOMODCACHE", strings.TrimSpace(string(cache)))
|
||||
t.Setenv("PATH", bin+":"+os.Getenv("PATH"))
|
||||
t.Setenv("TINTY_LOG", log)
|
||||
t.Setenv("XDG_DATA_HOME", filepath.Join(tmp, "data"))
|
||||
install := func() {
|
||||
t.Helper()
|
||||
cmd := exec.Command("make", "install", "HOME="+home, "XDG_CONFIG_HOME="+config)
|
||||
|
||||
@@ -192,6 +192,11 @@ func validatePair(pair currentPair, stderr io.Writer) error {
|
||||
func validateTheme(theme string, stderr io.Writer) error {
|
||||
cmd := exec.Command("tinty", "info", theme)
|
||||
cmd.Stdout = io.Discard
|
||||
if err := cmd.Run(); err == nil {
|
||||
return nil
|
||||
}
|
||||
cmd = exec.Command("tinty", "info", theme, "--custom-schemes")
|
||||
cmd.Stdout = io.Discard
|
||||
cmd.Stderr = stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Palette source: terebi/Themes/terebi-sunset.yaml (sibling project).
|
||||
# App colors are rounded to 8-bit sRGB; the translucent backdrop becomes opaque.
|
||||
# Styling roles: https://github.com/tinted-theming/home/blob/main/styling.md
|
||||
system: "base16"
|
||||
name: "Terebi Sunset"
|
||||
author: "Terebi"
|
||||
variant: "dark"
|
||||
palette:
|
||||
base00: "#1f1714" # Background — app backdrop, without alpha
|
||||
base01: "#2c221c" # Raised background / status bars
|
||||
base02: "#45352b" # Selection
|
||||
base03: "#9b8775" # Comments / muted text
|
||||
base04: "#d9c7a6" # Secondary foreground — app secondaryText
|
||||
base05: "#ffe3b5" # Foreground — app text
|
||||
base06: "#ffedcf" # Light foreground
|
||||
base07: "#fff6e6" # Lightest foreground
|
||||
base08: "#e07864" # Red — variables / errors / deletions
|
||||
base09: "#f2943b" # Orange — numbers / constants; app accent
|
||||
base0A: "#e6bc65" # Yellow — types / search
|
||||
base0B: "#a9b665" # Green — strings / additions
|
||||
base0C: "#8bb9aa" # Cyan — escapes / support
|
||||
base0D: "#7da6b8" # Blue — functions / headings
|
||||
base0E: "#b294bb" # Purple — keywords
|
||||
base0F: "#c2452e" # Brown/red — deprecated; app ember (3.51:1 on base00)
|
||||
Reference in New Issue
Block a user