@sebastienrousseau/dotfiles 0.2.511 → 0.2.513
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +2 -2
- package/docs/AI.md +2 -1
- package/docs/COPYRIGHT +1 -1
- package/docs/architecture/AI_COST_OPTIMIZATION.md +1 -0
- package/docs/archive/LEGACY_ROADMAP.md +4 -158
- package/docs/manual/00-introduction.md +1 -1
- package/docs/manual/01-concepts/02-trust-model.md +2 -1
- package/docs/manual/02-tutorials/02-add-wallpaper.md +6 -6
- package/docs/manual/02-tutorials/03-create-profile.md +1 -1
- package/docs/manual/02-tutorials/05-deploy-fleet.md +1 -1
- package/docs/manual/03-reference/02-config-files.md +1 -1
- package/docs/manual/03-reference/03-environment.md +1 -1
- package/docs/manual/04-cookbook/03-faq.md +1 -1
- package/docs/manual/05-appendices/B-security-checklist.md +2 -1
- package/docs/manual/command-index.md +1 -0
- package/docs/operations/ARCHITECTURE_ROADMAP.md +5 -143
- package/docs/operations/COVERAGE.md +30 -8
- package/docs/operations/REGISTRY.md +20 -19
- package/docs/operations/ROADMAP.md +3 -159
- package/docs/operations/ROADMAP_2026.md +5 -663
- package/docs/operations/ROADMAP_V0_2_503.md +5 -129
- package/docs/reference/POWERSHELL_PARITY.md +28 -27
- package/docs/reference/THEMES.md +1 -1
- package/docs/reference/TOOLS.md +1 -0
- package/docs/reference/UTILS.md +1 -0
- package/docs/schema/dot-registry-v1.json +33 -0
- package/docs/security/SCORECARD.md +1 -1
- package/install.sh +20 -11
- package/package.json +1 -1
- package/scripts/diagnostics/a2a-conformance.sh +0 -0
- package/scripts/diagnostics/aliases-manifest.sh +33 -6
- package/scripts/diagnostics/benchmark.sh +27 -1
- package/scripts/diagnostics/doctor.sh +18 -11
- package/scripts/diagnostics/health.sh +12 -4
- package/scripts/diagnostics/mcp-doctor.sh +3 -0
- package/scripts/diagnostics/secret-governance.sh +7 -1
- package/scripts/diagnostics/security-score.sh +14 -5
- package/scripts/diagnostics/verify_state.sh +9 -1
- package/scripts/diagnostics/workstation-attestation.sh +0 -0
- package/scripts/dot/commands/ai.sh +40 -31
- package/scripts/dot/commands/aliases.sh +28 -4
- package/scripts/dot/commands/appearance.sh +16 -0
- package/scripts/dot/commands/core.sh +17 -0
- package/scripts/dot/commands/diagnostics.sh +19 -0
- package/scripts/dot/commands/fleet.sh +1 -1
- package/scripts/dot/commands/manual.sh +4 -4
- package/scripts/dot/commands/meta.sh +87 -12
- package/scripts/dot/commands/registry.sh +164 -15
- package/scripts/dot/commands/secrets.sh +17 -0
- package/scripts/dot/commands/security.sh +17 -0
- package/scripts/dot/commands/tools.sh +19 -0
- package/scripts/dot/powershell/Dot.psm1 +146 -26
- package/scripts/fonts/install-nerd-fonts.sh +16 -10
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/ops/ai-setup.sh +13 -6
- package/scripts/ops/bundle.sh +31 -5
- package/scripts/ops/chezmoi-apply.sh +5 -0
- package/scripts/ops/heal-tools.sh +39 -111
- package/scripts/ops/post-apply-repair.sh +0 -0
- package/scripts/ops/release.sh +14 -5
- package/scripts/qa/docs-coverage.sh +1 -1
- package/scripts/qa/reliability-audit.sh +4 -4
- package/scripts/qa/scorecard-snapshot.sh +3 -3
- package/scripts/qa/validate-examples.sh +0 -0
- package/scripts/qa/wsl-contract.sh +0 -0
- package/scripts/secrets/age-init.sh +15 -4
- package/scripts/theme/apply-gnome-theme.sh +6 -2
- package/scripts/theme/extract-theme.py +97 -33
- package/scripts/theme/rebuild-themes.sh +240 -35
- package/scripts/theme/switch.sh +26 -16
- package/scripts/theme/wallpaper-sync.sh +95 -7
- package/scripts/tools/detect-collisions.py +0 -0
- package/scripts/version-sync.sh +108 -44
|
@@ -124,51 +124,99 @@ discover_linux_system() {
|
|
|
124
124
|
/usr/share/wallpapers
|
|
125
125
|
)
|
|
126
126
|
|
|
127
|
-
local dir file name
|
|
127
|
+
local dir file name variant
|
|
128
128
|
for dir in "${dirs[@]}"; do
|
|
129
129
|
[[ -d "$dir" ]] || continue
|
|
130
130
|
while IFS= read -r file; do
|
|
131
131
|
name="$(basename "$file")"
|
|
132
132
|
name="${name%.*}"
|
|
133
|
-
name="$(echo "$name" | tr '[:upper:]' '[:lower:]' | tr ' ' '
|
|
133
|
+
name="$(echo "$name" | tr '[:upper:]' '[:lower:]' | tr ' _' '--')"
|
|
134
134
|
name="${name//[^a-z0-9-]/}"
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
while [[ "$name" == *--* ]]; do name="${name//--/-}"; done
|
|
136
|
+
name="${name#-}"
|
|
137
|
+
name="${name%-}"
|
|
138
|
+
[[ -n "$name" ]] || continue
|
|
139
|
+
# Static system images carry no mode; theme them in both modes so
|
|
140
|
+
# they show as a pair (unless the file is already a -dark/-light).
|
|
141
|
+
if [[ "$name" == *-dark || "$name" == *-light ]]; then
|
|
142
|
+
[[ -z "${WALLPAPERS[$name]+x}" ]] && {
|
|
143
|
+
WALLPAPERS["$name"]="$file"
|
|
144
|
+
WP_SOURCE["$name"]="system"
|
|
145
|
+
}
|
|
146
|
+
continue
|
|
139
147
|
fi
|
|
140
|
-
|
|
148
|
+
local key
|
|
149
|
+
for variant in dark light; do
|
|
150
|
+
key="${name}-${variant}"
|
|
151
|
+
if [[ -z "${WALLPAPERS[$key]+x}" ]]; then
|
|
152
|
+
WALLPAPERS["$key"]="$file"
|
|
153
|
+
WP_SOURCE["$key"]="system"
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
done < <(find "$dir" -maxdepth 3 -type f \
|
|
157
|
+
\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \
|
|
158
|
+
-o -iname '*.heic' -o -iname '*.webp' -o -iname '*.tiff' \) 2>/dev/null | sort)
|
|
141
159
|
done
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
discover_custom() {
|
|
145
163
|
[[ -d "$CUSTOM_DIR" ]] || return 0
|
|
146
164
|
|
|
147
|
-
local file name frame_count
|
|
148
|
-
|
|
165
|
+
local file base name ext frame_count
|
|
166
|
+
# Case-insensitive, wide extension match. The old fixed-glob loop
|
|
167
|
+
# (`*.heic *.jpg *.png`) silently dropped `.jpeg`, `.webp`, `.tiff`
|
|
168
|
+
# and every uppercase variant (`.JPG`, `.HEIC`, …), so those
|
|
169
|
+
# wallpapers never made it into the theme table.
|
|
170
|
+
while IFS= read -r file; do
|
|
149
171
|
[[ -f "$file" ]] || continue
|
|
150
|
-
|
|
151
|
-
|
|
172
|
+
base="$(basename "$file")"
|
|
173
|
+
base="${base%.*}"
|
|
174
|
+
ext="$(echo "${file##*.}" | tr '[:upper:]' '[:lower:]')"
|
|
175
|
+
|
|
176
|
+
# Normalize to the [a-z0-9-] namespace that themes.toml and the
|
|
177
|
+
# `dot theme list` picker regex both require — a raw name with
|
|
178
|
+
# spaces/uppercase would generate a section the picker can't match.
|
|
179
|
+
name="$(echo "$base" | tr '[:upper:]' '[:lower:]' | tr ' _' '--')"
|
|
180
|
+
name="${name//[^a-z0-9-]/}"
|
|
181
|
+
while [[ "$name" == *--* ]]; do name="${name//--/-}"; done
|
|
182
|
+
name="${name#-}"
|
|
183
|
+
name="${name%-}"
|
|
184
|
+
[[ -n "$name" ]] || continue
|
|
152
185
|
|
|
153
|
-
#
|
|
154
|
-
if [[ "$
|
|
186
|
+
# Dynamic HEIC (multi-frame = light+dark packed in one file).
|
|
187
|
+
if [[ "$ext" == "heic" ]]; then
|
|
155
188
|
frame_count="$(magick identify "$file" 2>/dev/null | wc -l | tr -d ' ')"
|
|
156
189
|
if [[ "$frame_count" -ge 2 && "$name" != *-dark && "$name" != *-light ]]; then
|
|
157
|
-
# Dynamic HEIC: register as both dark and light
|
|
158
190
|
WALLPAPERS["${name}-light"]="${file}[0]"
|
|
159
191
|
WP_SOURCE["${name}-light"]="custom"
|
|
160
192
|
WALLPAPERS["${name}-dark"]="${file}[1]"
|
|
161
193
|
WP_SOURCE["${name}-dark"]="custom"
|
|
162
|
-
# Store the original file path for wallpaper-sync
|
|
194
|
+
# Store the original file path for wallpaper-sync.
|
|
163
195
|
WALLPAPERS["${name}"]="$file"
|
|
164
196
|
WP_SOURCE["${name}"]="custom-dynamic"
|
|
165
197
|
continue
|
|
166
198
|
fi
|
|
167
199
|
fi
|
|
168
200
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
201
|
+
# Explicitly-named single-mode variant (foo-dark.jpg / foo-light.jpg):
|
|
202
|
+
# honour the author's mode, register the one variant as-is.
|
|
203
|
+
if [[ "$name" == *-dark || "$name" == *-light ]]; then
|
|
204
|
+
WALLPAPERS["$name"]="$file"
|
|
205
|
+
WP_SOURCE["$name"]="custom"
|
|
206
|
+
continue
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
# Static single image: theme it in BOTH modes (extract-theme forces
|
|
210
|
+
# the mode from the -dark/-light suffix) so it appears as a light/dark
|
|
211
|
+
# pair in `dot theme list`. Previously a static produced only one
|
|
212
|
+
# variant and paired_families() hid it entirely.
|
|
213
|
+
WALLPAPERS["${name}-dark"]="$file"
|
|
214
|
+
WP_SOURCE["${name}-dark"]="custom"
|
|
215
|
+
WALLPAPERS["${name}-light"]="$file"
|
|
216
|
+
WP_SOURCE["${name}-light"]="custom"
|
|
217
|
+
done < <(find "$CUSTOM_DIR" -maxdepth 1 -type f \
|
|
218
|
+
\( -iname '*.heic' -o -iname '*.jpg' -o -iname '*.jpeg' \
|
|
219
|
+
-o -iname '*.png' -o -iname '*.webp' -o -iname '*.tiff' \) 2>/dev/null | sort)
|
|
172
220
|
}
|
|
173
221
|
|
|
174
222
|
# Remove dynamic base entries (keep only the -dark/-light variants for theme gen)
|
|
@@ -181,11 +229,15 @@ cleanup_dynamic_entries() {
|
|
|
181
229
|
done
|
|
182
230
|
}
|
|
183
231
|
|
|
184
|
-
# Discover in order: system first, custom overrides
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
232
|
+
# Discover in order: system first, custom overrides. System (OS-shipped)
|
|
233
|
+
# wallpapers are opt-in — most users only want themes from their own
|
|
234
|
+
# wallpapers. Enable the ~100 built-in ones with DOTFILES_THEME_SYSTEM=1.
|
|
235
|
+
if [[ "${DOTFILES_THEME_SYSTEM:-0}" == "1" ]]; then
|
|
236
|
+
case "$(uname -s)" in
|
|
237
|
+
Darwin) discover_macos_system ;;
|
|
238
|
+
Linux) discover_linux_system ;;
|
|
239
|
+
esac
|
|
240
|
+
fi
|
|
189
241
|
discover_custom
|
|
190
242
|
cleanup_dynamic_entries
|
|
191
243
|
|
|
@@ -238,16 +290,29 @@ done
|
|
|
238
290
|
# Generate themes
|
|
239
291
|
# ---------------------------------------------------------------------------
|
|
240
292
|
|
|
241
|
-
|
|
242
|
-
|
|
293
|
+
# Report distinct WALLPAPERS (families), not theme sections. Every
|
|
294
|
+
# wallpaper — a dynamic HEIC (light+dark frames) or a static image —
|
|
295
|
+
# yields a `-light` and a `-dark` theme, so a raw section count is ~2x
|
|
296
|
+
# the file count and reads as wrong ("74 files → 148 custom"). Collapse
|
|
297
|
+
# -dark/-light back to the family so the numbers match what's on disk
|
|
298
|
+
# and what `dot theme list` shows, and report the theme total separately.
|
|
299
|
+
# Explicit empty init: a `declare -A x` that never gets a key still trips
|
|
300
|
+
# `set -u` on ${#x[@]} (even in bash 5) — happens now that system wallpapers
|
|
301
|
+
# are opt-in and sys_fam can stay empty.
|
|
302
|
+
declare -A sys_fam=() cust_fam=()
|
|
243
303
|
for name in "${!WP_SOURCE[@]}"; do
|
|
304
|
+
family="${name%-dark}"
|
|
305
|
+
family="${family%-light}"
|
|
244
306
|
case "${WP_SOURCE[$name]}" in
|
|
245
|
-
system)
|
|
246
|
-
custom)
|
|
307
|
+
system) sys_fam["$family"]=1 ;;
|
|
308
|
+
custom) cust_fam["$family"]=1 ;;
|
|
247
309
|
esac
|
|
248
310
|
done
|
|
311
|
+
sys_count=${#sys_fam[@]}
|
|
312
|
+
cust_count=${#cust_fam[@]}
|
|
249
313
|
echo "Discovering wallpapers..."
|
|
250
|
-
echo " Found: $sys_count system, $cust_count custom
|
|
314
|
+
echo " Found: $sys_count system, $cust_count custom wallpapers" \
|
|
315
|
+
"($((sys_count + cust_count)) total → ${#WALLPAPERS[@]} light/dark themes)"
|
|
251
316
|
echo ""
|
|
252
317
|
|
|
253
318
|
GENERATED=0
|
|
@@ -297,11 +362,20 @@ if [[ $TOTAL_WORK -gt 0 ]]; then
|
|
|
297
362
|
wait
|
|
298
363
|
fi
|
|
299
364
|
|
|
300
|
-
# Count results
|
|
301
|
-
|
|
302
|
-
FAILED
|
|
365
|
+
# Count results — how many of THIS run's work items produced a cache file.
|
|
366
|
+
# (The old `find -newer "$0"` counted every cache file newer than the script,
|
|
367
|
+
# so when everything was cached FAILED went negative.)
|
|
368
|
+
GENERATED=0
|
|
369
|
+
FAILED=0
|
|
370
|
+
for name in "${WORK[@]}"; do
|
|
371
|
+
if [[ -f "$CACHE_DIR/${name}.toml" ]]; then
|
|
372
|
+
GENERATED=$((GENERATED + 1))
|
|
373
|
+
else
|
|
374
|
+
FAILED=$((FAILED + 1))
|
|
375
|
+
fi
|
|
376
|
+
done
|
|
303
377
|
echo ""
|
|
304
|
-
echo "Results: $
|
|
378
|
+
echo "Results: $GENERATED generated, $CACHED cached, $FAILED failed"
|
|
305
379
|
|
|
306
380
|
# ---------------------------------------------------------------------------
|
|
307
381
|
# Assemble themes.toml
|
|
@@ -326,14 +400,145 @@ echo "Assembling themes.toml..."
|
|
|
326
400
|
|
|
327
401
|
HEADER
|
|
328
402
|
|
|
329
|
-
|
|
403
|
+
# Assemble ONLY the wallpapers discovered this run, not every file left in
|
|
404
|
+
# the cache. This drops themes for wallpapers no longer present (e.g. system
|
|
405
|
+
# wallpapers once DOTFILES_THEME_SYSTEM is turned off) instead of letting
|
|
406
|
+
# stale cache entries pile up in themes.toml.
|
|
407
|
+
for name in $(printf '%s\n' "${!WALLPAPERS[@]}" | sort); do
|
|
408
|
+
cache_file="$CACHE_DIR/${name}.toml"
|
|
330
409
|
[[ -f "$cache_file" ]] || continue
|
|
331
410
|
echo ""
|
|
332
411
|
cat "$cache_file"
|
|
333
412
|
done
|
|
413
|
+
|
|
414
|
+
# Synthetic fallback themes — always emitted, never wallpaper-derived.
|
|
415
|
+
# Every theme template degrades to `fallback-dark` when `.theme` is unset or
|
|
416
|
+
# names a theme absent from this file. Because these are re-emitted on every
|
|
417
|
+
# rebuild, a regeneration that drops any wallpaper theme can never break the
|
|
418
|
+
# fallback (unlike hardcoding a wallpaper theme like the old big-sur-dark).
|
|
419
|
+
# switch.sh hides the `fallback` family from user-facing theme lists.
|
|
420
|
+
cat <<'FALLBACK'
|
|
421
|
+
|
|
422
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
423
|
+
# Synthetic fallback themes (NOT wallpaper-derived, NEVER user-selectable).
|
|
424
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
[themes.fallback-dark]
|
|
427
|
+
mode = "dark"
|
|
428
|
+
family = "fallback"
|
|
429
|
+
macos_accent = 4
|
|
430
|
+
wallpaper = ""
|
|
431
|
+
source = "custom"
|
|
432
|
+
|
|
433
|
+
[themes.fallback-dark.term]
|
|
434
|
+
bg = "#1e1e2e"
|
|
435
|
+
fg = "#d4d4d4"
|
|
436
|
+
cursor = "#455c67"
|
|
437
|
+
cursor_text = "#1e1e2e"
|
|
438
|
+
sel_bg = "#394145"
|
|
439
|
+
sel_fg = "#d4d4d4"
|
|
440
|
+
c0 = "#3c3c4d"
|
|
441
|
+
c1 = "#997d7a"
|
|
442
|
+
c2 = "#479174"
|
|
443
|
+
c3 = "#8a836f"
|
|
444
|
+
c4 = "#5f86b7"
|
|
445
|
+
c5 = "#917e8e"
|
|
446
|
+
c6 = "#5c8d82"
|
|
447
|
+
c7 = "#b9b8bb"
|
|
448
|
+
c8 = "#605f72"
|
|
449
|
+
c9 = "#bf9b97"
|
|
450
|
+
c10 = "#67b293"
|
|
451
|
+
c11 = "#aba389"
|
|
452
|
+
c12 = "#7fa6d9"
|
|
453
|
+
c13 = "#b49cb0"
|
|
454
|
+
c14 = "#7bada1"
|
|
455
|
+
c15 = "#e2e2e3"
|
|
456
|
+
|
|
457
|
+
[themes.fallback-dark.ui]
|
|
458
|
+
accent = "#455c67"
|
|
459
|
+
accent_text = "#ffffff"
|
|
460
|
+
error = "#997d7a"
|
|
461
|
+
warning = "#8a836f"
|
|
462
|
+
success = "#479174"
|
|
463
|
+
info = "#5f86b7"
|
|
464
|
+
panel = "#242435"
|
|
465
|
+
border = "#302f38"
|
|
466
|
+
|
|
467
|
+
[themes.fallback-dark.app]
|
|
468
|
+
nvim = "tokyonight"
|
|
469
|
+
nvim_style = "night"
|
|
470
|
+
lualine = "tokyonight"
|
|
471
|
+
gtk_theme = "Adwaita-dark"
|
|
472
|
+
gtk_icon = "Papirus-Dark"
|
|
473
|
+
gnome_shell = ""
|
|
474
|
+
gnome_gtk = "Adwaita-dark"
|
|
475
|
+
vscode = "Tokyonight Mocha"
|
|
476
|
+
vscode_dark = "Tokyonight Mocha"
|
|
477
|
+
vscode_light = "Tokyonight Latte"
|
|
478
|
+
cat_wallpaper = ""
|
|
479
|
+
starship_palette = "catppuccin_mocha"
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
[themes.fallback-light]
|
|
483
|
+
mode = "light"
|
|
484
|
+
family = "fallback"
|
|
485
|
+
macos_accent = 4
|
|
486
|
+
wallpaper = ""
|
|
487
|
+
source = "custom"
|
|
488
|
+
|
|
489
|
+
[themes.fallback-light.term]
|
|
490
|
+
bg = "#fbf1c7"
|
|
491
|
+
fg = "#3c3836"
|
|
492
|
+
cursor = "#2c5989"
|
|
493
|
+
cursor_text = "#fbf1c7"
|
|
494
|
+
sel_bg = "#c6cfe0"
|
|
495
|
+
sel_fg = "#3c3836"
|
|
496
|
+
c0 = "#2e2c26"
|
|
497
|
+
c1 = "#7d3d39"
|
|
498
|
+
c2 = "#205a48"
|
|
499
|
+
c3 = "#5b501d"
|
|
500
|
+
c4 = "#2b527d"
|
|
501
|
+
c5 = "#6e4069"
|
|
502
|
+
c6 = "#25554c"
|
|
503
|
+
c7 = "#6f6d67"
|
|
504
|
+
c8 = "#54524b"
|
|
505
|
+
c9 = "#853733"
|
|
506
|
+
c10 = "#025c46"
|
|
507
|
+
c11 = "#5b500b"
|
|
508
|
+
c12 = "#0d5388"
|
|
509
|
+
c13 = "#743b6f"
|
|
510
|
+
c14 = "#15564b"
|
|
511
|
+
c15 = "#979693"
|
|
512
|
+
|
|
513
|
+
[themes.fallback-light.ui]
|
|
514
|
+
accent = "#2c5989"
|
|
515
|
+
accent_text = "#ffffff"
|
|
516
|
+
error = "#7d3d39"
|
|
517
|
+
warning = "#5b501d"
|
|
518
|
+
success = "#205a48"
|
|
519
|
+
info = "#2b527d"
|
|
520
|
+
panel = "#f2e8bf"
|
|
521
|
+
border = "#e3e0d3"
|
|
522
|
+
|
|
523
|
+
[themes.fallback-light.app]
|
|
524
|
+
nvim = "catppuccin"
|
|
525
|
+
nvim_style = "latte"
|
|
526
|
+
lualine = "catppuccin"
|
|
527
|
+
gtk_theme = "Adwaita"
|
|
528
|
+
gtk_icon = "Papirus-Light"
|
|
529
|
+
gnome_shell = ""
|
|
530
|
+
gnome_gtk = "Adwaita"
|
|
531
|
+
vscode = "Catppuccin Latte"
|
|
532
|
+
vscode_dark = "Catppuccin Mocha"
|
|
533
|
+
vscode_light = "Catppuccin Latte"
|
|
534
|
+
cat_wallpaper = ""
|
|
535
|
+
starship_palette = "catppuccin_latte"
|
|
536
|
+
FALLBACK
|
|
334
537
|
} >"$THEMES_FILE"
|
|
335
538
|
|
|
336
|
-
|
|
337
|
-
|
|
539
|
+
# Count top-level [themes.NAME] blocks only — not the .term/.ui/.app
|
|
540
|
+
# subsections (which inflated the tally ~4x).
|
|
541
|
+
theme_count=$(grep -cE '^\[themes\.[a-z0-9-]+\]$' "$THEMES_FILE")
|
|
542
|
+
echo " Written: $THEMES_FILE ($theme_count themes)"
|
|
338
543
|
echo ""
|
|
339
544
|
echo "Done. Run 'dot theme list' to see available themes."
|
package/scripts/theme/switch.sh
CHANGED
|
@@ -78,15 +78,33 @@ fi
|
|
|
78
78
|
# Theme Database
|
|
79
79
|
# =============================================================================
|
|
80
80
|
|
|
81
|
-
# Default preferences
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
# Default preferences (must be present keys in themes.toml — the theme
|
|
82
|
+
# regeneration removed the old macos-monterey-* names; bloom is the current
|
|
83
|
+
# default family and always ships a dark+light pair).
|
|
84
|
+
DEFAULT_DARK="bloom-dark"
|
|
85
|
+
DEFAULT_LIGHT="bloom-light"
|
|
86
|
+
|
|
87
|
+
# Machine-local chezmoi override (chezmoi.toml [data] theme) takes precedence
|
|
88
|
+
# over .chezmoidata.toml when rendering, so it is the authoritative "current"
|
|
89
|
+
# value. Kept in sync by dot-theme-sync's write_theme().
|
|
90
|
+
CHEZMOI_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/chezmoi/chezmoi.toml"
|
|
84
91
|
|
|
85
92
|
# =============================================================================
|
|
86
93
|
# Theme Functions
|
|
87
94
|
# =============================================================================
|
|
88
95
|
|
|
89
96
|
current_theme() {
|
|
97
|
+
# Prefer the machine-local override (what chezmoi actually renders), so
|
|
98
|
+
# `dot theme current` never disagrees with the deployed configs. Fall back
|
|
99
|
+
# to the repo default in .chezmoidata.toml when no override is set.
|
|
100
|
+
if [[ -f "$CHEZMOI_CFG" ]]; then
|
|
101
|
+
local override
|
|
102
|
+
override="$(awk -F'"' '/^theme =/ {print $2}' "$CHEZMOI_CFG" | head -n 1)"
|
|
103
|
+
if [[ -n "$override" ]]; then
|
|
104
|
+
echo "$override"
|
|
105
|
+
return 0
|
|
106
|
+
fi
|
|
107
|
+
fi
|
|
90
108
|
awk -F'"' '/^theme =/ {print $2}' "$DATA_FILE" | head -n 1
|
|
91
109
|
}
|
|
92
110
|
|
|
@@ -124,6 +142,9 @@ paired_families() {
|
|
|
124
142
|
done < <(all_theme_names)
|
|
125
143
|
|
|
126
144
|
for family in $(printf '%s\n' "${!has_dark[@]}" | sort); do
|
|
145
|
+
# `fallback` is a synthetic safety theme (see themes.toml) that templates
|
|
146
|
+
# degrade to when .theme is unset/invalid — never a user-selectable one.
|
|
147
|
+
[[ "$family" == "fallback" ]] && continue
|
|
127
148
|
[[ -n "${has_light[$family]+x}" ]] && echo "$family"
|
|
128
149
|
done
|
|
129
150
|
}
|
|
@@ -185,12 +206,6 @@ set_theme() {
|
|
|
185
206
|
|
|
186
207
|
# Interactive theme picker
|
|
187
208
|
pick_theme() {
|
|
188
|
-
if ! command -v fzf &>/dev/null; then
|
|
189
|
-
ui_err "fzf" "required for interactive picker"
|
|
190
|
-
ui_info "Usage" "dot theme set <name>"
|
|
191
|
-
exit 1
|
|
192
|
-
fi
|
|
193
|
-
|
|
194
209
|
local current
|
|
195
210
|
current="$(current_theme)"
|
|
196
211
|
|
|
@@ -220,14 +235,9 @@ pick_theme() {
|
|
|
220
235
|
done < <(paired_families)
|
|
221
236
|
|
|
222
237
|
local selected_family
|
|
223
|
-
selected_family="$(
|
|
238
|
+
selected_family="$(printf '%s' "$theme_list" | ui_pick \
|
|
224
239
|
--header "Select wallpaper theme (current: $current_family [$current_mode])" \
|
|
225
|
-
--prompt "Theme >
|
|
226
|
-
--height 30 \
|
|
227
|
-
--reverse \
|
|
228
|
-
--no-sort \
|
|
229
|
-
--no-preview \
|
|
230
|
-
--ansi |
|
|
240
|
+
--prompt "Theme >" |
|
|
231
241
|
awk '$1 !~ /^#/ && NF >= 2 {print $2}')" || return 0
|
|
232
242
|
|
|
233
243
|
if [[ -n "$selected_family" ]]; then
|
|
@@ -43,6 +43,16 @@ detect_mode() {
|
|
|
43
43
|
esac
|
|
44
44
|
fi
|
|
45
45
|
|
|
46
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
47
|
+
# AppleInterfaceStyle is "Dark" in dark mode and unset in light mode.
|
|
48
|
+
if [[ "$(defaults read -g AppleInterfaceStyle 2>/dev/null || true)" == "Dark" ]]; then
|
|
49
|
+
echo "dark"
|
|
50
|
+
else
|
|
51
|
+
echo "light"
|
|
52
|
+
fi
|
|
53
|
+
return 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
46
56
|
if command -v gsettings &>/dev/null; then
|
|
47
57
|
local scheme
|
|
48
58
|
scheme="$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null || echo "")"
|
|
@@ -125,10 +135,16 @@ wallpaper_for_theme() {
|
|
|
125
135
|
found && /^wallpaper/ { sub(/.*= *"/, ""); sub(/".*/, ""); print; exit }
|
|
126
136
|
' "$themes_file")"
|
|
127
137
|
if [[ -n "$stored_wp" ]]; then
|
|
128
|
-
#
|
|
138
|
+
# Home-relative form (current generator): ~/Pictures/... → $HOME/Pictures/...
|
|
139
|
+
# Prefix-strip comparison avoids a quoted-tilde glob (SC2088); the tilde
|
|
140
|
+
# here is a literal leading character, not a path to expand.
|
|
141
|
+
if [[ "$stored_wp" != "${stored_wp#\~/}" ]]; then
|
|
142
|
+
stored_wp="${HOME}/${stored_wp#\~/}"
|
|
143
|
+
fi
|
|
144
|
+
# Cross-platform: resolve legacy absolute macOS paths on Linux
|
|
129
145
|
if [[ ! -f "$stored_wp" ]]; then
|
|
130
146
|
if [[ "$stored_wp" == /Users/* ]]; then
|
|
131
|
-
# /Users/<user>/Pictures/... → $HOME/Pictures/...
|
|
147
|
+
# /Users/<user>/Pictures/... → $HOME/Pictures/... (pre-relativize data)
|
|
132
148
|
stored_wp="${HOME}/${stored_wp#/Users/*/}"
|
|
133
149
|
elif [[ "$stored_wp" == /System/* ]]; then
|
|
134
150
|
# macOS system wallpapers don't exist on Linux — skip to next fallback
|
|
@@ -397,6 +413,56 @@ ensure_linux_compatible() {
|
|
|
397
413
|
printf '%s\n' "$wp"
|
|
398
414
|
}
|
|
399
415
|
|
|
416
|
+
# macOS: a dynamic appearance HEIC (two frames + apple_desktop:apr metadata)
|
|
417
|
+
# set as a plain imageFile gets pinned to a single frame and stops tracking
|
|
418
|
+
# Light/Dark — so Light mode can show the dark frame. Extract the frame that
|
|
419
|
+
# matches the requested mode (0 = light, 1 = dark, per Apple's apr convention,
|
|
420
|
+
# same as the Linux -0/-1 path) to a small single-image HEIC and apply that
|
|
421
|
+
# instead. Single-image HEICs and non-HEICs are returned unchanged.
|
|
422
|
+
macos_appearance_frame() {
|
|
423
|
+
local wp="$1" mode="$2"
|
|
424
|
+
[[ "${wp##*.}" == "heic" ]] || {
|
|
425
|
+
printf '%s\n' "$wp"
|
|
426
|
+
return
|
|
427
|
+
}
|
|
428
|
+
command -v magick &>/dev/null || {
|
|
429
|
+
printf '%s\n' "$wp"
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
local frames
|
|
433
|
+
frames="$(magick identify "$wp" 2>/dev/null | wc -l | tr -d ' ')"
|
|
434
|
+
[[ "${frames:-0}" -ge 2 ]] || {
|
|
435
|
+
printf '%s\n' "$wp"
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
local idx=0
|
|
440
|
+
[[ "$mode" == "dark" ]] && idx=1
|
|
441
|
+
local cache_dir="$WALLPAPER_DIR/.dot-frames"
|
|
442
|
+
mkdir -p "$cache_dir" 2>/dev/null || {
|
|
443
|
+
printf '%s\n' "$wp"
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
local frame
|
|
447
|
+
frame="$cache_dir/$(basename "${wp%.heic}")-${mode}.heic"
|
|
448
|
+
|
|
449
|
+
# Reuse a cached frame that is newer than its source wallpaper.
|
|
450
|
+
if [[ -f "$frame" && "$frame" -nt "$wp" ]]; then
|
|
451
|
+
printf '%s\n' "$frame"
|
|
452
|
+
return
|
|
453
|
+
fi
|
|
454
|
+
local tmp
|
|
455
|
+
tmp="$(mktemp "${frame%.heic}.XXXXXX.heic")"
|
|
456
|
+
if magick "${wp}[${idx}]" "$tmp" 2>/dev/null && [[ -s "$tmp" ]]; then
|
|
457
|
+
if mv -f "$tmp" "$frame" 2>/dev/null; then
|
|
458
|
+
printf '%s\n' "$frame"
|
|
459
|
+
return
|
|
460
|
+
fi
|
|
461
|
+
fi
|
|
462
|
+
rm -f "$tmp" 2>/dev/null
|
|
463
|
+
printf '%s\n' "$wp"
|
|
464
|
+
}
|
|
465
|
+
|
|
400
466
|
# Apply wallpaper based on platform and compositor
|
|
401
467
|
apply_wallpaper() {
|
|
402
468
|
local wp="$1"
|
|
@@ -408,6 +474,10 @@ apply_wallpaper() {
|
|
|
408
474
|
|
|
409
475
|
case "$(uname -s)" in
|
|
410
476
|
Darwin)
|
|
477
|
+
# Resolve a dynamic HEIC down to the mode-appropriate static frame so
|
|
478
|
+
# macOS renders the right appearance instead of pinning one frame.
|
|
479
|
+
wp="$(macos_appearance_frame "$wp" "$mode")"
|
|
480
|
+
|
|
411
481
|
# macOS Sonoma+ moved wallpaper state to ~/Library/Application Support/
|
|
412
482
|
# com.apple.wallpaper/Store/Index.plist, owned by WallpaperAgent. Each
|
|
413
483
|
# Space and Display has its own entry, and AppleScript's `every desktop`
|
|
@@ -494,12 +564,29 @@ if updated:
|
|
|
494
564
|
print(updated)
|
|
495
565
|
PYEOF
|
|
496
566
|
|
|
497
|
-
# Restart WallpaperAgent so it re-reads the store
|
|
498
|
-
|
|
567
|
+
# Restart WallpaperAgent so it re-reads the store and repaints EVERY
|
|
568
|
+
# Space (the store rewrite above covers all Spaces; the agent only
|
|
569
|
+
# applies it on respawn). Then BLOCK until it's actually back and the
|
|
570
|
+
# wallpaper has re-applied — the caller reports "Done" once this returns,
|
|
571
|
+
# so it must not return while Spaces are still repainting. Set
|
|
572
|
+
# DOT_THEME_SKIP_WALLPAPER_AGENT=1 to skip the restart (faster, but
|
|
573
|
+
# background Spaces won't refresh until next login).
|
|
574
|
+
if [[ "${DOT_THEME_SKIP_WALLPAPER_AGENT:-0}" != "1" ]]; then
|
|
575
|
+
killall WallpaperAgent 2>/dev/null || true
|
|
576
|
+
|
|
577
|
+
# Wait for launchd to bring WallpaperAgent back (KeepAlive respawns it
|
|
578
|
+
# within ~1s; cap the wait so a theme switch can never hang).
|
|
579
|
+
local _waited=0
|
|
580
|
+
while ! pgrep -x WallpaperAgent >/dev/null 2>&1; do
|
|
581
|
+
sleep 0.1
|
|
582
|
+
_waited=$((_waited + 1))
|
|
583
|
+
[[ "$_waited" -ge 60 ]] && break
|
|
584
|
+
done
|
|
585
|
+
fi
|
|
499
586
|
|
|
500
|
-
#
|
|
501
|
-
#
|
|
502
|
-
#
|
|
587
|
+
# Re-assert the wallpaper through the (now running) agent — covers the
|
|
588
|
+
# active Space's first paint and any screen the store rewrite missed —
|
|
589
|
+
# then give that paint a moment to finish before returning.
|
|
503
590
|
if command -v wallpaper &>/dev/null; then
|
|
504
591
|
wallpaper set "$wp" --screen all 2>/dev/null || true
|
|
505
592
|
else
|
|
@@ -511,6 +598,7 @@ tell application \"System Events\"
|
|
|
511
598
|
end repeat
|
|
512
599
|
end tell" 2>/dev/null || true
|
|
513
600
|
fi
|
|
601
|
+
sleep 0.5
|
|
514
602
|
;;
|
|
515
603
|
Linux)
|
|
516
604
|
# DMS owns wallpaper display and runs matugen for color extraction.
|
|
File without changes
|