Skip to content

Commit a5c16e3

Browse files
committed
Seed defaults from tokyo-night on omarchy installs
New ThemeState pulls palette and 0- wallpaper from the installed tokyo-night theme when available, falling back to the existing Catppuccin palette in standalone mode. Frontend calls GetInitialState on mount so stores start with the seeded values instead of their hardcoded defaults.
1 parent b1dbde8 commit a5c16e3

7 files changed

Lines changed: 187 additions & 2 deletions

File tree

app.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ func (a *App) GetThemeColors() map[string]string {
6868
return a.themeWatcher.CurrentColors()
6969
}
7070

71+
// GetInitialState returns the backend-seeded theme state so the
72+
// frontend can show the right defaults on first paint (e.g. tokyo-night
73+
// palette + wallpaper on omarchy installs).
74+
func (a *App) GetInitialState() theme.StateSnapshot {
75+
return a.state.Snapshot()
76+
}
77+
7178
// NewApp creates a new App instance.
7279
func NewApp() *App {
7380
return &App{

frontend/src/App.svelte

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,20 @@
111111
if (tab) setActiveTab(tab as any);
112112
} catch {}
113113
114+
// Seed stores from backend defaults (tokyo-night on omarchy, blank
115+
// otherwise). Stores init to DEFAULT_PALETTE at module load, so
116+
// this overwrites those before any user interaction.
117+
try {
118+
const {GetInitialState} = await import('../wailsjs/go/main/App');
119+
const s = await GetInitialState();
120+
if (s?.palette?.length >= 16) {
121+
setPalette(s.palette, true);
122+
}
123+
if (s?.wallpaperPath) {
124+
setWallpaperPath(s.wallpaperPath);
125+
}
126+
} catch {}
127+
114128
initKeyboardShortcuts();
115129
116130
registerShortcut('ctrl+z', undoAction);

frontend/wailsjs/go/main/App.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export function GetFavorites(): Promise<Array<favorites.Favorite>>;
6464

6565
export function GetFocusTab(): Promise<string>;
6666

67+
export function GetInitialState(): Promise<theme.StateSnapshot>;
68+
6769
export function GetMediaURL(arg1: string): Promise<string>;
6870

6971
export function GetPreview(arg1: string): Promise<string>;

frontend/wailsjs/go/main/App.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ export function GetFocusTab() {
8282
return window['go']['main']['App']['GetFocusTab']();
8383
}
8484

85+
export function GetInitialState() {
86+
return window['go']['main']['App']['GetInitialState']();
87+
}
88+
8589
export function GetMediaURL(arg1) {
8690
return window['go']['main']['App']['GetMediaURL'](arg1);
8791
}

frontend/wailsjs/go/models.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,63 @@ export namespace omarchy {
324324
}
325325
}
326326

327+
export namespace template {
328+
export class ColorRoles {
329+
background: string;
330+
foreground: string;
331+
black: string;
332+
red: string;
333+
green: string;
334+
yellow: string;
335+
blue: string;
336+
magenta: string;
337+
cyan: string;
338+
white: string;
339+
bright_black: string;
340+
bright_red: string;
341+
bright_green: string;
342+
bright_yellow: string;
343+
bright_blue: string;
344+
bright_magenta: string;
345+
bright_cyan: string;
346+
bright_white: string;
347+
accent: string;
348+
cursor: string;
349+
selection_foreground: string;
350+
selection_background: string;
351+
352+
static createFrom(source: any = {}) {
353+
return new ColorRoles(source);
354+
}
355+
356+
constructor(source: any = {}) {
357+
if ('string' === typeof source) source = JSON.parse(source);
358+
this.background = source['background'];
359+
this.foreground = source['foreground'];
360+
this.black = source['black'];
361+
this.red = source['red'];
362+
this.green = source['green'];
363+
this.yellow = source['yellow'];
364+
this.blue = source['blue'];
365+
this.magenta = source['magenta'];
366+
this.cyan = source['cyan'];
367+
this.white = source['white'];
368+
this.bright_black = source['bright_black'];
369+
this.bright_red = source['bright_red'];
370+
this.bright_green = source['bright_green'];
371+
this.bright_yellow = source['bright_yellow'];
372+
this.bright_blue = source['bright_blue'];
373+
this.bright_magenta = source['bright_magenta'];
374+
this.bright_cyan = source['bright_cyan'];
375+
this.bright_white = source['bright_white'];
376+
this.accent = source['accent'];
377+
this.cursor = source['cursor'];
378+
this.selection_foreground = source['selection_foreground'];
379+
this.selection_background = source['selection_background'];
380+
}
381+
}
382+
}
383+
327384
export namespace theme {
328385
export class ApplyResult {
329386
success: boolean;
@@ -365,6 +422,57 @@ export namespace theme {
365422
this.videoCpuMode = source['videoCpuMode'];
366423
}
367424
}
425+
export class StateSnapshot {
426+
palette: string[];
427+
wallpaperPath: string;
428+
lightMode: boolean;
429+
lockedColors: Record<number, boolean>;
430+
colorRoles: template.ColorRoles;
431+
extendedColors: Record<string, string>;
432+
extractionMode: string;
433+
additionalImages: string[];
434+
appOverrides: Record<string, any>;
435+
436+
static createFrom(source: any = {}) {
437+
return new StateSnapshot(source);
438+
}
439+
440+
constructor(source: any = {}) {
441+
if ('string' === typeof source) source = JSON.parse(source);
442+
this.palette = source['palette'];
443+
this.wallpaperPath = source['wallpaperPath'];
444+
this.lightMode = source['lightMode'];
445+
this.lockedColors = source['lockedColors'];
446+
this.colorRoles = this.convertValues(
447+
source['colorRoles'],
448+
template.ColorRoles
449+
);
450+
this.extendedColors = source['extendedColors'];
451+
this.extractionMode = source['extractionMode'];
452+
this.additionalImages = source['additionalImages'];
453+
this.appOverrides = source['appOverrides'];
454+
}
455+
456+
convertValues(a: any, classs: any, asMap: boolean = false): any {
457+
if (!a) {
458+
return a;
459+
}
460+
if (a.slice && a.map) {
461+
return (a as any[]).map(elem =>
462+
this.convertValues(elem, classs)
463+
);
464+
} else if ('object' === typeof a) {
465+
if (asMap) {
466+
for (const key of Object.keys(a)) {
467+
a[key] = new classs(a[key]);
468+
}
469+
return a;
470+
}
471+
return new classs(a);
472+
}
473+
return a;
474+
}
475+
}
368476
}
369477

370478
export namespace wallhaven {

internal/omarchy/themes.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,42 @@ func LoadAllThemes() ([]Theme, error) {
105105

106106
return themes, nil
107107
}
108+
109+
// TokyoNightDefaults loads the tokyo-night palette and its first
110+
// wallpaper from a local omarchy install, for use as Aether's
111+
// out-of-the-box defaults. Returns ok=false when the theme isn't
112+
// present (e.g. standalone installs without omarchy).
113+
func TokyoNightDefaults() (palette [16]string, bg, fg, wallpaper string, ok bool) {
114+
home, err := os.UserHomeDir()
115+
if err != nil {
116+
return
117+
}
118+
candidates := []string{
119+
filepath.Join(home, ".config", "omarchy", "themes", "tokyo-night"),
120+
filepath.Join(home, ".local", "share", "omarchy", "themes", "tokyo-night"),
121+
}
122+
123+
for _, themeDir := range candidates {
124+
data, err := os.ReadFile(filepath.Join(themeDir, "colors.toml"))
125+
if err != nil {
126+
continue
127+
}
128+
palette, bg, fg = ParseColorsToml(string(data))
129+
130+
// os.ReadDir returns entries sorted by filename, so the first
131+
// image is tokyo-night's "0-*" wallpaper by convention.
132+
if entries, err := os.ReadDir(filepath.Join(themeDir, "backgrounds")); err == nil {
133+
for _, e := range entries {
134+
ext := strings.ToLower(filepath.Ext(e.Name()))
135+
if ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".webp" {
136+
wallpaper = filepath.Join(themeDir, "backgrounds", e.Name())
137+
break
138+
}
139+
}
140+
}
141+
142+
ok = true
143+
return
144+
}
145+
return
146+
}

internal/theme/state.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package theme
22

33
import (
44
"aether/internal/color"
5+
"aether/internal/omarchy"
56
"aether/internal/template"
67
)
78

@@ -41,10 +42,20 @@ var DefaultPalette = [16]string{
4142
}
4243

4344
// NewThemeState returns a ThemeState initialised with default values.
45+
// On omarchy systems it seeds the palette and wallpaper from the
46+
// installed tokyo-night theme; standalone installs keep DefaultPalette.
4447
func NewThemeState() *ThemeState {
48+
palette := DefaultPalette
49+
var wallpaper string
50+
if p, _, _, w, ok := omarchy.TokyoNightDefaults(); ok {
51+
palette = p
52+
wallpaper = w
53+
}
54+
4555
s := &ThemeState{
46-
Palette: DefaultPalette,
47-
BasePalette: DefaultPalette,
56+
Palette: palette,
57+
BasePalette: palette,
58+
WallpaperPath: wallpaper,
4859
LockedColors: make(map[int]bool),
4960
Adjustments: color.DefaultAdjustments(),
5061
ExtendedColors: make(map[string]string),

0 commit comments

Comments
 (0)