@sebastienrousseau/dotfiles 0.2.520 → 0.2.522

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.
Files changed (323) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE-APACHE +190 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +43 -37
  5. package/install.sh +76 -10
  6. package/package.json +7 -7
  7. package/tools/README.md +49 -0
  8. package/tools/ci/install-chezmoi-verified.sh +68 -0
  9. package/docs/.vitepress/reports/localization-readability-audit.md +0 -73
  10. package/docs/AI.md +0 -179
  11. package/docs/ARCHITECTURE.md +0 -117
  12. package/docs/CNAME +0 -1
  13. package/docs/CONFIG_STRATEGY.md +0 -124
  14. package/docs/COPYRIGHT +0 -7
  15. package/docs/ECOSYSTEM.md +0 -220
  16. package/docs/GOLD-STANDARD-AUDIT.md +0 -352
  17. package/docs/GOVERNANCE.md +0 -98
  18. package/docs/MAINTAINERS.md +0 -41
  19. package/docs/MINIMUM-TOOLCHAIN.md +0 -100
  20. package/docs/NAMING_CONVENTIONS.md +0 -102
  21. package/docs/OPENCODE.md +0 -127
  22. package/docs/README.md +0 -84
  23. package/docs/STRUCTURE.md +0 -102
  24. package/docs/adr/ADR-001-ci-cd-pipeline.md +0 -118
  25. package/docs/adr/ADR-002-shell-performance.md +0 -130
  26. package/docs/adr/ADR-003-security-first.md +0 -158
  27. package/docs/adr/ADR-004-cli-architecture.md +0 -171
  28. package/docs/adr/ADR-005-chezmoi-choice.md +0 -99
  29. package/docs/adr/ADR-006-shell-selection.md +0 -124
  30. package/docs/adr/ADR-007-multi-shell-parity.md +0 -62
  31. package/docs/adr/ADR-008-alias-system-architecture.md +0 -95
  32. package/docs/adr/ADR-009-wallpaper-driven-theming.md +0 -131
  33. package/docs/adr/ADR-010-starship-transient-prompt.md +0 -144
  34. package/docs/adr/ADR-011-nushell-tier3-keep.md +0 -144
  35. package/docs/adr/ADR-012-ai-fleet-local-proxy.md +0 -79
  36. package/docs/adr/README.md +0 -40
  37. package/docs/architecture/AI_COST_OPTIMIZATION.md +0 -144
  38. package/docs/architecture/ARCHITECTURE.md +0 -20
  39. package/docs/architecture/INTEROP.md +0 -44
  40. package/docs/architecture/REPO_LAYOUT.md +0 -241
  41. package/docs/architecture/WALKTHROUGH.md +0 -86
  42. package/docs/architecture/fleet-deployment.md +0 -77
  43. package/docs/archive/EUXIS_2026_REVIEW.md +0 -127
  44. package/docs/archive/LEGACY_ROADMAP.md +0 -6
  45. package/docs/archive/MILESTONE_v0.2.493.md +0 -47
  46. package/docs/archive/PLAN.md +0 -199
  47. package/docs/archive/REPO_AUDIT.md +0 -31
  48. package/docs/articles/.pages +0 -6
  49. package/docs/articles/2026-07-05-custom-mkdocs-material-dark-theme.md +0 -216
  50. package/docs/articles/2026-07-05-fish-startup-abbr.md +0 -153
  51. package/docs/articles/2026-07-05-master-to-main-rename-runbook.md +0 -128
  52. package/docs/articles/index.md +0 -36
  53. package/docs/guides/INSTALL.md +0 -144
  54. package/docs/guides/MACOS_ICLOUD_SYMLINKS.md +0 -121
  55. package/docs/guides/NEOVIM_IDE_GUIDE.md +0 -61
  56. package/docs/guides/THEMING.md +0 -230
  57. package/docs/guides/TROUBLESHOOTING.md +0 -176
  58. package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +0 -792
  59. package/docs/index.md +0 -132
  60. package/docs/interop/A2A.md +0 -39
  61. package/docs/interop/POWERSHELL.md +0 -102
  62. package/docs/manual/00-introduction.md +0 -89
  63. package/docs/manual/01-concepts/01-architecture.md +0 -138
  64. package/docs/manual/01-concepts/02-trust-model.md +0 -183
  65. package/docs/manual/01-concepts/03-theme-engine.md +0 -186
  66. package/docs/manual/01-concepts/04-fleet.md +0 -148
  67. package/docs/manual/01-concepts/05-self-healing.md +0 -204
  68. package/docs/manual/02-tutorials/01-first-install.md +0 -197
  69. package/docs/manual/02-tutorials/02-add-wallpaper.md +0 -216
  70. package/docs/manual/02-tutorials/03-create-profile.md +0 -244
  71. package/docs/manual/02-tutorials/04-encrypt-secret.md +0 -281
  72. package/docs/manual/02-tutorials/05-deploy-fleet.md +0 -283
  73. package/docs/manual/03-reference/01-dot-cli.md +0 -475
  74. package/docs/manual/03-reference/02-config-files.md +0 -265
  75. package/docs/manual/03-reference/03-environment.md +0 -124
  76. package/docs/manual/03-reference/04-templates.md +0 -190
  77. package/docs/manual/03-reference/05-feature-flags.md +0 -187
  78. package/docs/manual/04-cookbook/01-recipes.md +0 -285
  79. package/docs/manual/04-cookbook/02-troubleshooting.md +0 -351
  80. package/docs/manual/04-cookbook/03-faq.md +0 -175
  81. package/docs/manual/05-appendices/A-platform-matrix.md +0 -101
  82. package/docs/manual/05-appendices/B-security-checklist.md +0 -85
  83. package/docs/manual/05-appendices/C-glossary.md +0 -40
  84. package/docs/manual/05-appendices/D-bibliography.md +0 -58
  85. package/docs/manual/05-appendices/E-license.md +0 -38
  86. package/docs/manual/_toc.yml +0 -58
  87. package/docs/manual/command-index.md +0 -175
  88. package/docs/manual/concept-index.md +0 -170
  89. package/docs/manual/index.md +0 -66
  90. package/docs/migration/README.md +0 -81
  91. package/docs/migration/from-bare-git-repo.md +0 -156
  92. package/docs/migration/from-gnu-stow.md +0 -165
  93. package/docs/migration/from-plain-chezmoi.md +0 -148
  94. package/docs/migration/from-yadm.md +0 -187
  95. package/docs/operations/ARCHITECTURE_ROADMAP.md +0 -7
  96. package/docs/operations/ATTESTATION.md +0 -44
  97. package/docs/operations/CI_CADENCE.md +0 -107
  98. package/docs/operations/CI_COMPOSITES.md +0 -156
  99. package/docs/operations/COMPLETIONS.md +0 -123
  100. package/docs/operations/COVERAGE.md +0 -204
  101. package/docs/operations/DRIFT.md +0 -107
  102. package/docs/operations/HARD_AUDIT_2026.md +0 -631
  103. package/docs/operations/MAINTENANCE.md +0 -63
  104. package/docs/operations/MANIFEST.md +0 -127
  105. package/docs/operations/MIGRATION.md +0 -109
  106. package/docs/operations/OPERATIONS.md +0 -188
  107. package/docs/operations/PERFORMANCE.md +0 -133
  108. package/docs/operations/PERFORMANCE_BUDGETS.md +0 -196
  109. package/docs/operations/REGISTRY.md +0 -90
  110. package/docs/operations/RELEASE_PIPELINE.md +0 -128
  111. package/docs/operations/RELIABILITY.md +0 -122
  112. package/docs/operations/RFC_v0_2_503_reorganization.md +0 -280
  113. package/docs/operations/ROADMAP.md +0 -10
  114. package/docs/operations/ROADMAP_2026.md +0 -7
  115. package/docs/operations/ROADMAP_V0_2_503.md +0 -10
  116. package/docs/operations/TESTING.md +0 -216
  117. package/docs/operations/TRACEABILITY.md +0 -44
  118. package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +0 -65
  119. package/docs/operations/VERSION_SYNC.md +0 -393
  120. package/docs/packaging.md +0 -222
  121. package/docs/reference/ALIASES.md +0 -131
  122. package/docs/reference/ALIASES_CHEATSHEET.md +0 -32
  123. package/docs/reference/ALIASES_DEPRECATIONS.md +0 -13
  124. package/docs/reference/FEATURE-MATRIX.md +0 -646
  125. package/docs/reference/FEATURES.md +0 -66
  126. package/docs/reference/FONTS.md +0 -112
  127. package/docs/reference/POWERSHELL_PARITY.md +0 -82
  128. package/docs/reference/PROFILES.md +0 -69
  129. package/docs/reference/SCREENSHOTS.md +0 -121
  130. package/docs/reference/SCRIPTS.md +0 -71
  131. package/docs/reference/SUPPORT_MATRIX.md +0 -80
  132. package/docs/reference/THEMES.md +0 -117
  133. package/docs/reference/TOOLS.md +0 -110
  134. package/docs/reference/UTILS.md +0 -243
  135. package/docs/registry.json +0 -6
  136. package/docs/schema/dot-env-v1.json +0 -110
  137. package/docs/schema/dot-registry-v1.json +0 -33
  138. package/docs/security/AI_ACT_COMPLIANCE.md +0 -94
  139. package/docs/security/AUDIT_BYPASS.md +0 -103
  140. package/docs/security/AUTOMATION_SECRETS.md +0 -26
  141. package/docs/security/CI_EGRESS_ALLOWLIST.md +0 -127
  142. package/docs/security/CI_PINNING.md +0 -129
  143. package/docs/security/COMMIT_SIGNING.md +0 -138
  144. package/docs/security/COMPLIANCE.md +0 -458
  145. package/docs/security/DEPS_DEV_EXCEPTIONS.md +0 -86
  146. package/docs/security/DISCLOSURE.md +0 -130
  147. package/docs/security/ENCRYPTION.md +0 -57
  148. package/docs/security/FMEA.md +0 -159
  149. package/docs/security/FUZZING.md +0 -209
  150. package/docs/security/HISTORY_FILTERING.md +0 -132
  151. package/docs/security/INCIDENT_RESPONSE.md +0 -579
  152. package/docs/security/INSTALL_VERIFICATION.md +0 -122
  153. package/docs/security/KEYS.md +0 -49
  154. package/docs/security/KEY_ROTATION.md +0 -303
  155. package/docs/security/MCP_POLICY.md +0 -78
  156. package/docs/security/POLICY_RELEASES.md +0 -37
  157. package/docs/security/README.md +0 -28
  158. package/docs/security/SCORECARD.md +0 -195
  159. package/docs/security/SECRETS.md +0 -158
  160. package/docs/security/SECURITY.md +0 -45
  161. package/docs/security/SECURITY_CHECKLIST.md +0 -55
  162. package/docs/security/SHELL_EXEMPTIONS.md +0 -145
  163. package/docs/security/SOUP_REGISTER.md +0 -36
  164. package/docs/security/THREAT_MODEL.md +0 -130
  165. package/docs/security/VERIFICATION_VALIDATION.md +0 -228
  166. package/docs/security/VERIFY_RELEASE.md +0 -201
  167. package/docs/security/security-pubkey.asc +0 -15
  168. package/docs/stylesheets/extra.css +0 -444
  169. package/docs/themes/README.md +0 -10
  170. package/docs/themes/VISUAL_INTEGRITY_REPORT.md +0 -30
  171. package/docs/themes/hero-shot.svg +0 -78
  172. package/scripts/README.md +0 -123
  173. package/scripts/ci/check-copyright-headers.sh +0 -8
  174. package/scripts/ci/check-shell-preamble.sh +0 -8
  175. package/scripts/ci/guard-gitleaks-checkout.sh +0 -8
  176. package/scripts/demo/record.sh +0 -43
  177. package/scripts/diagnostics/a2a-conformance.sh +0 -163
  178. package/scripts/diagnostics/alias-governance.sh +0 -165
  179. package/scripts/diagnostics/aliases-cheatsheet.sh +0 -74
  180. package/scripts/diagnostics/aliases-manifest.sh +0 -77
  181. package/scripts/diagnostics/attest-verify.sh +0 -147
  182. package/scripts/diagnostics/benchmark.sh +0 -408
  183. package/scripts/diagnostics/conflicts.sh +0 -73
  184. package/scripts/diagnostics/doctor-unified.sh +0 -43
  185. package/scripts/diagnostics/doctor.sh +0 -797
  186. package/scripts/diagnostics/drift-dashboard.sh +0 -203
  187. package/scripts/diagnostics/health.sh +0 -656
  188. package/scripts/diagnostics/history-analysis.sh +0 -86
  189. package/scripts/diagnostics/mcp-doctor.sh +0 -582
  190. package/scripts/diagnostics/perf.sh +0 -453
  191. package/scripts/diagnostics/scorecard.sh +0 -120
  192. package/scripts/diagnostics/secret-governance.sh +0 -65
  193. package/scripts/diagnostics/security-score.sh +0 -467
  194. package/scripts/diagnostics/smoke-test.sh +0 -88
  195. package/scripts/diagnostics/snapshot.sh +0 -90
  196. package/scripts/diagnostics/verify.sh +0 -108
  197. package/scripts/diagnostics/verify_state.sh +0 -73
  198. package/scripts/diagnostics/version-locks.sh +0 -94
  199. package/scripts/diagnostics/workstation-attestation.sh +0 -212
  200. package/scripts/dot/commands/agent.sh +0 -535
  201. package/scripts/dot/commands/agents.sh +0 -352
  202. package/scripts/dot/commands/ai.sh +0 -600
  203. package/scripts/dot/commands/aliases.sh +0 -277
  204. package/scripts/dot/commands/appearance.sh +0 -110
  205. package/scripts/dot/commands/completion.sh +0 -171
  206. package/scripts/dot/commands/core.sh +0 -217
  207. package/scripts/dot/commands/diagnostics.sh +0 -265
  208. package/scripts/dot/commands/env-emit.sh +0 -203
  209. package/scripts/dot/commands/fleet.sh +0 -711
  210. package/scripts/dot/commands/init.sh +0 -185
  211. package/scripts/dot/commands/lint.sh +0 -208
  212. package/scripts/dot/commands/manual.sh +0 -169
  213. package/scripts/dot/commands/meta.sh +0 -438
  214. package/scripts/dot/commands/patterns.sh +0 -55
  215. package/scripts/dot/commands/registry.sh +0 -455
  216. package/scripts/dot/commands/restore.sh +0 -232
  217. package/scripts/dot/commands/secrets.sh +0 -296
  218. package/scripts/dot/commands/security.sh +0 -102
  219. package/scripts/dot/commands/tools.sh +0 -570
  220. package/scripts/dot/data/alias-deprecations.tsv +0 -2
  221. package/scripts/dot/powershell/Dot.psm1 +0 -319
  222. package/scripts/fonts/install-nerd-fonts.sh +0 -75
  223. package/scripts/fonts/patch-fonts.sh +0 -36
  224. package/scripts/git-hooks/install.sh +0 -12
  225. package/scripts/git-hooks/pre-commit +0 -12
  226. package/scripts/git-hooks/pre-commit-audit.sh +0 -146
  227. package/scripts/git-hooks/pre-push +0 -105
  228. package/scripts/git-hooks/prepare-commit-msg +0 -29
  229. package/scripts/lib/secrets_provider.sh +0 -200
  230. package/scripts/nvim/headless-upgrade.lua +0 -81
  231. package/scripts/ops/ai-setup.sh +0 -71
  232. package/scripts/ops/bundle.sh +0 -104
  233. package/scripts/ops/chaos.sh +0 -50
  234. package/scripts/ops/chezmoi-apply.sh +0 -333
  235. package/scripts/ops/chezmoi-diff.sh +0 -16
  236. package/scripts/ops/chezmoi-remove.sh +0 -46
  237. package/scripts/ops/chezmoi-update.sh +0 -67
  238. package/scripts/ops/heal-chezmoi.sh +0 -87
  239. package/scripts/ops/heal-system.sh +0 -129
  240. package/scripts/ops/heal-tools.sh +0 -297
  241. package/scripts/ops/heal.sh +0 -223
  242. package/scripts/ops/post-apply-repair.sh +0 -107
  243. package/scripts/ops/prewarm.sh +0 -128
  244. package/scripts/ops/release.sh +0 -262
  245. package/scripts/ops/rollback.sh +0 -613
  246. package/scripts/ops/setup.sh +0 -138
  247. package/scripts/ops/teleport.sh +0 -34
  248. package/scripts/qa/check-feature-matrix.sh +0 -296
  249. package/scripts/qa/check-version-consistency.sh +0 -12
  250. package/scripts/qa/coverage-baseline.sh +0 -61
  251. package/scripts/qa/docs-coverage.sh +0 -118
  252. package/scripts/qa/examples-coverage.sh +0 -94
  253. package/scripts/qa/powershell-contract.ps1 +0 -95
  254. package/scripts/qa/reliability-audit.sh +0 -139
  255. package/scripts/qa/scorecard-snapshot.sh +0 -128
  256. package/scripts/qa/traceability-coverage.sh +0 -124
  257. package/scripts/qa/validate-examples.sh +0 -90
  258. package/scripts/qa/wsl-contract.sh +0 -12
  259. package/scripts/secrets/age-init.sh +0 -82
  260. package/scripts/secrets/create-secrets-file.sh +0 -46
  261. package/scripts/secrets/encrypt-ssh-key.sh +0 -44
  262. package/scripts/security/backup.sh +0 -58
  263. package/scripts/security/check-disclosure-key-expiry.sh +0 -111
  264. package/scripts/security/dns-doh.sh +0 -52
  265. package/scripts/security/encryption-check.sh +0 -55
  266. package/scripts/security/enforce-policies.sh +0 -552
  267. package/scripts/security/firewall.sh +0 -91
  268. package/scripts/security/lock-configs.sh +0 -67
  269. package/scripts/security/lock-screen.sh +0 -56
  270. package/scripts/security/manage-secrets.sh +0 -429
  271. package/scripts/security/ssh-cert.sh +0 -204
  272. package/scripts/security/telemetry-kill.sh +0 -51
  273. package/scripts/security/usb-safety.sh +0 -52
  274. package/scripts/theme/apply-gnome-theme.sh +0 -333
  275. package/scripts/theme/extract-heic-frames.sh +0 -115
  276. package/scripts/theme/extract-theme.py +0 -1020
  277. package/scripts/theme/install-boot-logo.sh +0 -63
  278. package/scripts/theme/install-catppuccin-themes.sh +0 -371
  279. package/scripts/theme/install-cursors.sh +0 -26
  280. package/scripts/theme/install-file-icons.sh +0 -27
  281. package/scripts/theme/install-grub-theme.sh +0 -62
  282. package/scripts/theme/install-lock-icon.sh +0 -31
  283. package/scripts/theme/merge-wallpaper.sh +0 -146
  284. package/scripts/theme/rebuild-themes.sh +0 -603
  285. package/scripts/theme/switch.sh +0 -476
  286. package/scripts/theme/wallpaper-rotate.sh +0 -137
  287. package/scripts/theme/wallpaper-sync.sh +0 -690
  288. package/scripts/tools/cmatrix.sh +0 -22
  289. package/scripts/tools/detect-collisions.py +0 -103
  290. package/scripts/tools/emoji-picker.sh +0 -49
  291. package/scripts/tools/figlet-banner.sh +0 -19
  292. package/scripts/tools/log-rotate.sh +0 -31
  293. package/scripts/tools/lolcat-wrap.sh +0 -20
  294. package/scripts/tools/pipes.sh +0 -49
  295. package/scripts/tuning/linux.sh +0 -186
  296. package/scripts/tuning/macos.sh +0 -56
  297. package/scripts/uninstall.sh +0 -86
  298. package/scripts/verify-release-versions +0 -156
  299. package/scripts/version-sync.sh +0 -714
  300. package/templates/chezmoi-data/geekom-a9.toml.example +0 -21
  301. package/templates/chezmoi-data/mac-m1.toml.example +0 -16
  302. package/templates/chezmoi-data/mac-t2-linux.toml.example +0 -21
  303. package/templates/chezmoi-data/surface-pro-7p.toml.example +0 -21
  304. package/templates/projects/go/.github/workflows/ci.yml +0 -31
  305. package/templates/projects/go/README.md +0 -7
  306. package/templates/projects/go/cmd/__PROJECT_NAME__/main.go +0 -8
  307. package/templates/projects/go/go.mod +0 -3
  308. package/templates/projects/go/go.sum +0 -0
  309. package/templates/projects/molecule/README.md +0 -7
  310. package/templates/projects/molecule/converge.yml +0 -7
  311. package/templates/projects/molecule/molecule.yml +0 -16
  312. package/templates/projects/node/.github/workflows/ci.yml +0 -30
  313. package/templates/projects/node/README.md +0 -7
  314. package/templates/projects/node/package-lock.json +0 -12
  315. package/templates/projects/node/package.json +0 -10
  316. package/templates/projects/node/src/index.js +0 -3
  317. package/templates/projects/packer/README.md +0 -15
  318. package/templates/projects/packer/main.pkr.hcl +0 -15
  319. package/templates/projects/python/.github/workflows/ci.yml +0 -34
  320. package/templates/projects/python/README.md +0 -7
  321. package/templates/projects/python/pyproject.toml +0 -25
  322. package/templates/projects/python/src/__PROJECT_NAME__/__init__.py +0 -2
  323. package/templates/projects/python/tests/test_basic.py +0 -3
@@ -1,690 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SPDX-License-Identifier: Apache-2.0 OR MIT
3
- # Copyright (c) 2015-2026 Sebastien Rousseau
4
- set -euo pipefail
5
-
6
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
- # shellcheck source=../../lib/dot/ui.sh
8
- # shellcheck disable=SC1091
9
- source "$SCRIPT_DIR/../../lib/dot/ui.sh"
10
-
11
- ui_init
12
- ui_header "Wallpaper Sync"
13
-
14
- WALLPAPER_DIR="${DOTFILES_WALLPAPER_DIR:-$HOME/Pictures/Wallpapers}"
15
- CHEZMOI_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/chezmoi/chezmoi.toml"
16
-
17
- # Resolve dotfiles repo root, then descend into chezmoi source subdir if
18
- # .chezmoiroot is present (where chezmoi files live in defaults/)
19
- _DOTFILES_ROOT="${HOME}/.dotfiles"
20
- [[ ! -d "$_DOTFILES_ROOT" && -d "${HOME}/.local/share/chezmoi" ]] && _DOTFILES_ROOT="${HOME}/.local/share/chezmoi"
21
- _CHEZMOI_SRC="$_DOTFILES_ROOT"
22
- if [[ -f "$_DOTFILES_ROOT/.chezmoiroot" ]]; then
23
- _sub="$(head -1 "$_DOTFILES_ROOT/.chezmoiroot" | tr -d '[:space:]')"
24
- [[ -n "$_sub" && -d "$_DOTFILES_ROOT/$_sub" ]] && _CHEZMOI_SRC="$_DOTFILES_ROOT/$_sub"
25
- fi
26
- DATA_FILE="${_CHEZMOI_SRC}/.chezmoidata.toml"
27
-
28
- WALLPAPER_DIR_EXISTS=true
29
- if [ ! -d "$WALLPAPER_DIR" ]; then
30
- WALLPAPER_DIR_EXISTS=false
31
- fi
32
-
33
- # Detect current color scheme (light/dark)
34
- detect_mode() {
35
- if command -v dms &>/dev/null; then
36
- local dms_mode
37
- dms_mode="$(dms ipc theme getMode 2>/dev/null || true)"
38
- case "$dms_mode" in
39
- dark | light)
40
- echo "$dms_mode"
41
- return 0
42
- ;;
43
- esac
44
- fi
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
-
56
- if command -v gsettings &>/dev/null; then
57
- local scheme
58
- scheme="$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null || echo "")"
59
- case "$scheme" in
60
- *dark*) echo "dark" ;;
61
- *) echo "light" ;;
62
- esac
63
- else
64
- echo "dark"
65
- fi
66
- }
67
-
68
- current_theme() {
69
- if [[ -f "$CHEZMOI_CFG" ]]; then
70
- local chezmoi_theme
71
- chezmoi_theme="$(awk -F'"' '/^theme =/ {print $2}' "$CHEZMOI_CFG" | head -n 1)"
72
- if [[ -n "$chezmoi_theme" ]]; then
73
- printf '%s\n' "$chezmoi_theme"
74
- return 0
75
- fi
76
- fi
77
-
78
- if [[ -f "$DATA_FILE" ]]; then
79
- awk -F'"' '/^theme =/ {print $2}' "$DATA_FILE" | head -n 1
80
- fi
81
- }
82
-
83
- wallpaper_for_theme() {
84
- local theme="${1:-}"
85
- local mode="${2:-}"
86
- local candidate=""
87
- local family=""
88
-
89
- [[ -n "$theme" ]] || return 1
90
- [[ -n "$mode" ]] || return 1
91
-
92
- family="${theme%-dark}"
93
- if [[ "$family" == "$theme" ]]; then
94
- family="${theme%-light}"
95
- fi
96
-
97
- # 1. Prefer pre-extracted frames (fast, no conversion needed).
98
- # Convention: {family}-0.png = light, {family}-1.png = dark.
99
- # These are created externally (e.g. ImageMagick/ffmpeg from dynamic HEIC).
100
- local frame_idx=0
101
- [[ "$mode" == "dark" ]] && frame_idx=1
102
- for ext in png jpg webp; do
103
- candidate="$WALLPAPER_DIR/${family}-${frame_idx}.${ext}"
104
- if [[ -f "$candidate" ]]; then
105
- printf '%s\n' "$candidate"
106
- return 0
107
- fi
108
- done
109
-
110
- # 2. Check exact theme name (e.g. hello-dark.png)
111
- for ext in png jpg webp heic; do
112
- candidate="$WALLPAPER_DIR/${theme}.${ext}"
113
- if [[ -f "$candidate" ]]; then
114
- printf '%s\n' "$candidate"
115
- return 0
116
- fi
117
- done
118
-
119
- # 3. Check family-mode variant (e.g. hello-dark.png)
120
- for ext in png jpg webp heic; do
121
- candidate="$WALLPAPER_DIR/${family}-${mode}.${ext}"
122
- if [[ -f "$candidate" ]]; then
123
- printf '%s\n' "$candidate"
124
- return 0
125
- fi
126
- done
127
-
128
- # 4. Check themes.toml stored wallpaper path (set by extract-theme.py)
129
- local themes_file="${_CHEZMOI_SRC}/.chezmoidata/themes.toml"
130
- if [[ -f "$themes_file" ]]; then
131
- local stored_wp
132
- stored_wp="$(awk -v n="$theme" '
133
- $0 == "[themes." n "]" { found=1; next }
134
- /^\[/ { found=0 }
135
- found && /^wallpaper/ { sub(/.*= *"/, ""); sub(/".*/, ""); print; exit }
136
- ' "$themes_file")"
137
- if [[ -n "$stored_wp" ]]; then
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
145
- if [[ ! -f "$stored_wp" ]]; then
146
- if [[ "$stored_wp" == /Users/* ]]; then
147
- # /Users/<user>/Pictures/... → $HOME/Pictures/... (pre-relativize data)
148
- stored_wp="${HOME}/${stored_wp#/Users/*/}"
149
- elif [[ "$stored_wp" == /System/* ]]; then
150
- # macOS system wallpapers don't exist on Linux — skip to next fallback
151
- stored_wp=""
152
- fi
153
- fi
154
- if [[ -n "$stored_wp" && -f "$stored_wp" ]]; then
155
- printf '%s\n' "$stored_wp"
156
- return 0
157
- fi
158
- fi
159
- fi
160
-
161
- # 5. Check family-only file (e.g. hello.heic — dynamic wallpaper).
162
- # For HEIC on Linux, extract frames first then return the correct one.
163
- for ext in png jpg webp; do
164
- candidate="$WALLPAPER_DIR/${family}.${ext}"
165
- if [[ -f "$candidate" ]]; then
166
- printf '%s\n' "$candidate"
167
- return 0
168
- fi
169
- done
170
- candidate="$WALLPAPER_DIR/${family}.heic"
171
- if [[ -f "$candidate" ]] && [[ "$(uname -s)" == "Linux" ]]; then
172
- # Extract frames from HEIC then return the mode-appropriate one
173
- ensure_linux_compatible "$candidate" >/dev/null
174
- # Re-check for extracted frames
175
- for fext in png jpg; do
176
- if [[ -f "$WALLPAPER_DIR/${family}-${frame_idx}.${fext}" ]]; then
177
- printf '%s\n' "$WALLPAPER_DIR/${family}-${frame_idx}.${fext}"
178
- return 0
179
- fi
180
- done
181
- elif [[ -f "$candidate" ]]; then
182
- printf '%s\n' "$candidate"
183
- return 0
184
- fi
185
-
186
- return 1
187
- }
188
-
189
- # Fallback: find a matching system wallpaper when no custom one exists.
190
- # Maps theme names to platform-native wallpapers shipped with the OS.
191
- system_wallpaper_for_theme() {
192
- local theme="${1:-}"
193
- local mode="${2:-}"
194
- local SYS_MAC="/System/Library/Desktop Pictures"
195
- local SYS_LINUX_DIRS=(
196
- /usr/share/backgrounds
197
- /usr/share/wallpapers
198
- )
199
-
200
- [[ -n "$theme" ]] || return 1
201
-
202
- # macOS: map theme names to Apple system wallpapers.
203
- # Plain case statement (not `local -A`) because macOS still ships
204
- # /bin/bash 3.2 which doesn't support associative arrays. Users who
205
- # have brew bash get the same behaviour; first-run users with stock
206
- # bash do too.
207
- if [[ "$(uname -s)" == "Darwin" && -d "$SYS_MAC" ]]; then
208
- # Extract family from theme name (strip -dark/-light)
209
- local family="${theme%-dark}"
210
- [[ "$family" == "$theme" ]] && family="${theme%-light}"
211
-
212
- local sys_wp=""
213
- case "$family" in
214
- "macos - sonoma") sys_wp="$SYS_MAC/Sonoma.heic" ;;
215
- "macos - blue") sys_wp="$SYS_MAC/Mac Blue.heic" ;;
216
- "macos - pink") sys_wp="$SYS_MAC/Mac Pink.heic" ;;
217
- "macos - purple") sys_wp="$SYS_MAC/Mac Purple.heic" ;;
218
- "macos - yellow") sys_wp="$SYS_MAC/Mac Yellow.heic" ;;
219
- "macos - orange") sys_wp="$SYS_MAC/iMac Orange.heic" ;;
220
- "macos - green") sys_wp="$SYS_MAC/iMac Green.heic" ;;
221
- "macos - silver") sys_wp="$SYS_MAC/iMac Silver.heic" ;;
222
- esac
223
- if [[ -n "$sys_wp" && -f "$sys_wp" ]]; then
224
- printf '%s\n' "$sys_wp"
225
- return 0
226
- fi
227
- fi
228
-
229
- # Linux: search system background directories for keyword match
230
- if [[ "$(uname -s)" == "Linux" ]]; then
231
- local keyword="${theme#macos-}"
232
- keyword="${keyword%-dark}"
233
- keyword="${keyword%-light}"
234
-
235
- for dir in "${SYS_LINUX_DIRS[@]}"; do
236
- [[ -d "$dir" ]] || continue
237
- local match
238
- match="$(find "$dir" -maxdepth 3 -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.webp" \) \
239
- -iname "*${keyword}*" 2>/dev/null | head -1)"
240
- if [[ -n "$match" ]]; then
241
- printf '%s\n' "$match"
242
- return 0
243
- fi
244
- done
245
- fi
246
-
247
- return 1
248
- }
249
-
250
- theme_wallpaper_pair() {
251
- local theme="${1:-}"
252
- local family=""
253
- local light_wp=""
254
- local dark_wp=""
255
-
256
- [[ -n "$theme" ]] || return 1
257
-
258
- family="${theme%-dark}"
259
- if [[ "$family" == "$theme" ]]; then
260
- family="${theme%-light}"
261
- fi
262
-
263
- light_wp="$(wallpaper_for_theme "${family}-light" "light" || true)"
264
- dark_wp="$(wallpaper_for_theme "${family}-dark" "dark" || true)"
265
-
266
- if [[ -n "$light_wp" && -n "$dark_wp" ]]; then
267
- printf '%s\n%s\n' "$light_wp" "$dark_wp"
268
- return 0
269
- fi
270
-
271
- return 1
272
- }
273
-
274
- # Pick a wallpaper matching the current mode
275
- pick_wallpaper() {
276
- local mode="$1"
277
- local theme="${2:-}"
278
- local files=()
279
-
280
- if [[ -n "$theme" ]]; then
281
- local matched
282
- matched="$(wallpaper_for_theme "$theme" "$mode" || true)"
283
- if [[ -n "$matched" ]]; then
284
- printf '%s\n' "$matched"
285
- return 0
286
- fi
287
- fi
288
-
289
- while IFS= read -r line; do
290
- files+=("$line")
291
- done < <(find "$WALLPAPER_DIR" -maxdepth 1 -type f \( -iname "*-${mode}.jpg" -o -iname "*-${mode}.png" -o -iname "*-${mode}.webp" -o -iname "*-${mode}.heic" \) | sort)
292
-
293
- # Fallback: search for extracted frames (-0 = light, -1 = dark)
294
- if [[ ${#files[@]} -eq 0 ]]; then
295
- local frame_suffix=0
296
- [[ "$mode" == "dark" ]] && frame_suffix=1
297
- while IFS= read -r line; do
298
- files+=("$line")
299
- done < <(find "$WALLPAPER_DIR" -maxdepth 1 -type f \( -iname "*-${frame_suffix}.jpg" -o -iname "*-${frame_suffix}.png" -o -iname "*-${frame_suffix}.webp" \) | sort)
300
- fi
301
-
302
- if [[ ${#files[@]} -eq 0 ]]; then
303
- return 1
304
- fi
305
-
306
- # Pick a random one
307
- if command -v shuf &>/dev/null; then
308
- printf '%s\n' "${files[@]}" | shuf -n 1
309
- else
310
- echo "${files[$RANDOM % ${#files[@]}]}"
311
- fi
312
- }
313
-
314
- THEME="$(current_theme || true)"
315
-
316
- # Derive mode from theme name (authoritative) instead of querying DMS
317
- # which may have just restarted and report stale state.
318
- if [[ "$THEME" == *-dark ]]; then
319
- MODE="dark"
320
- elif [[ "$THEME" == *-light ]]; then
321
- MODE="light"
322
- else
323
- MODE="$(detect_mode)"
324
- fi
325
-
326
- WALLPAPER=""
327
- if [[ "$WALLPAPER_DIR_EXISTS" == "true" ]]; then
328
- WALLPAPER="$(pick_wallpaper "$MODE" "$THEME" || true)"
329
- fi
330
-
331
- # Fallback: try OS-native system wallpapers
332
- if [[ -z "$WALLPAPER" ]]; then
333
- WALLPAPER="$(system_wallpaper_for_theme "$THEME" "$MODE" || true)"
334
- if [[ -n "$WALLPAPER" ]]; then
335
- ui_info "Wallpaper" "using system wallpaper: $(basename "$WALLPAPER")"
336
- fi
337
- fi
338
-
339
- if [[ -z "$WALLPAPER" ]]; then
340
- ui_info "Wallpaper" "no wallpaper for ${THEME:-unknown} (skipping — theme colors still apply)"
341
- exit 0
342
- fi
343
-
344
- # Convert .heic to .png on Linux (HEIC not universally supported)
345
- ensure_linux_compatible() {
346
- local wp="$1"
347
- [[ "$(uname -s)" == "Linux" ]] || {
348
- printf '%s\n' "$wp"
349
- return
350
- }
351
- [[ "${wp##*.}" == "heic" ]] || {
352
- printf '%s\n' "$wp"
353
- return
354
- }
355
-
356
- local png="${wp%.heic}.png"
357
- # Use cached PNG only if it exists, is newer than source, and is > 1MB
358
- # (corrupt/truncated conversions produce tiny files that crash matugen).
359
- if [[ -f "$png" ]] && [[ "$png" -nt "$wp" ]]; then
360
- local fsize
361
- fsize="$(stat -c%s "$png" 2>/dev/null || stat -f%z "$png" 2>/dev/null || echo 0)"
362
- if [[ "$fsize" -gt 1000000 ]]; then
363
- printf '%s\n' "$png"
364
- return
365
- fi
366
- fi
367
-
368
- # Write to temp file then atomic move — prevents DMS/matugen from
369
- # reading a partially-written PNG during multi-frame HEIC extraction.
370
- local tmp_png
371
- tmp_png="$(mktemp "${png%.png}.XXXXXX.png")"
372
-
373
- if command -v magick &>/dev/null; then
374
- magick "$wp" -quality 95 "$tmp_png" 2>/dev/null && {
375
- # Multi-frame HEIC: magick creates {name}-0.png, {name}-1.png etc.
376
- # Move each extracted frame to its final location atomically.
377
- local tmp_base="${tmp_png%.png}"
378
- local final_base="${png%.png}"
379
- if [[ -f "${tmp_base}-0.png" ]]; then
380
- for f in "${tmp_base}"-*.png; do
381
- local suffix="${f#"$tmp_base"}"
382
- mv -f "$f" "${final_base}${suffix}" 2>/dev/null
383
- done
384
- rm -f "$tmp_png" 2>/dev/null
385
- # Return the correct frame: -0 = light, -1 = dark
386
- if [[ -f "${final_base}-0.png" ]]; then
387
- printf '%s\n' "${final_base}-0.png"
388
- else
389
- printf '%s\n' "$png"
390
- fi
391
- else
392
- mv -f "$tmp_png" "$png" 2>/dev/null
393
- printf '%s\n' "$png"
394
- fi
395
- return
396
- }
397
- elif command -v heif-convert &>/dev/null; then
398
- heif-convert "$wp" "$tmp_png" 2>/dev/null && {
399
- mv -f "$tmp_png" "$png" 2>/dev/null
400
- printf '%s\n' "$png"
401
- return
402
- }
403
- elif command -v convert &>/dev/null; then
404
- convert "$wp" "$tmp_png" 2>/dev/null && {
405
- mv -f "$tmp_png" "$png" 2>/dev/null
406
- printf '%s\n' "$png"
407
- return
408
- }
409
- fi
410
-
411
- rm -f "$tmp_png" 2>/dev/null
412
- # Fallback: use original and hope the DE supports it
413
- printf '%s\n' "$wp"
414
- }
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
-
466
- # Apply wallpaper based on platform and compositor
467
- apply_wallpaper() {
468
- local wp="$1"
469
- local mode="$2"
470
-
471
- # Convert HEIC to PNG on Linux if needed
472
- wp="$(ensure_linux_compatible "$wp")"
473
- local wp_uri="file://${wp}"
474
-
475
- case "$(uname -s)" in
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
-
481
- # macOS Sonoma+ moved wallpaper state to ~/Library/Application Support/
482
- # com.apple.wallpaper/Store/Index.plist, owned by WallpaperAgent. Each
483
- # Space and Display has its own entry, and AppleScript's `every desktop`
484
- # only sees the active Space — so the only reliable way to cover all
485
- # desktops is to rewrite each entry in Index.plist directly.
486
- #
487
- # The Configuration field of each choice is itself a nested binary plist
488
- # of the form: {type: 'imageFile', url: {relative: 'file:///path'}}
489
- # The store has several shapes: Spaces[uuid].Default.Desktop (per-Space
490
- # wallpaper), Spaces[uuid].Default.Linked (desktop + screensaver share
491
- # one image), and per-display nesting under Spaces[uuid].Displays[uuid].
492
- # Walk the tree and patch any node that holds a wallpaper Choice list.
493
- # After updating, killall WallpaperAgent so launchd respawns it and it
494
- # re-reads the store.
495
- python3 - "$wp" <<'PYEOF' 2>/dev/null || true
496
- import plistlib, os, sys
497
- wp = sys.argv[1]
498
- uri = "file://" + wp
499
- store = os.path.expanduser(
500
- "~/Library/Application Support/com.apple.wallpaper/Store/Index.plist"
501
- )
502
- if not os.path.exists(store):
503
- sys.exit(0)
504
-
505
- def make_config(uri):
506
- return plistlib.dumps(
507
- {"type": "imageFile", "url": {"relative": uri}},
508
- fmt=plistlib.FMT_BINARY,
509
- )
510
-
511
- def patch_node(node, uri):
512
- """Patch a Desktop/Linked node in-place. Returns 1 if patched."""
513
- content = node.get("Content")
514
- if not isinstance(content, dict):
515
- return 0
516
- choices = content.get("Choices")
517
- if not isinstance(choices, list) or not choices:
518
- return 0
519
- first = choices[0]
520
- if not isinstance(first, dict):
521
- return 0
522
- first["Configuration"] = make_config(uri)
523
- first["Provider"] = "com.apple.wallpaper.choice.image"
524
- first["Files"] = []
525
- content["Choices"] = [first]
526
- content["Shuffle"] = "$null"
527
- # Leave EncodedOptionValues alone — it stores crop/color and is per-image.
528
- return 1
529
-
530
- # Recursively patch every wallpaper-bearing node. A node qualifies when it
531
- # has a Content.Choices list — this matches Desktop and Linked entries
532
- # wherever they appear (top-level Displays, AllSpacesAndDisplays, Spaces[*]
533
- # .Default, Spaces[*].Displays[*], etc.) and ignores Idle (screensaver) so
534
- # we don't clobber the user's lock-screen wallpaper.
535
- def walk(node, uri, key=None):
536
- count = 0
537
- if isinstance(node, dict):
538
- if key in ("Desktop", "Linked") and "Content" in node:
539
- count += patch_node(node, uri)
540
- else:
541
- for k, v in node.items():
542
- count += walk(v, uri, k)
543
- elif isinstance(node, list):
544
- for item in node:
545
- count += walk(item, uri)
546
- return count
547
-
548
- with open(store, "rb") as f:
549
- data = plistlib.load(f)
550
-
551
- # Safety backup before mutating.
552
- try:
553
- with open(store + ".dot-bak", "wb") as f:
554
- plistlib.dump(data, f, fmt=plistlib.FMT_BINARY)
555
- except Exception:
556
- pass
557
-
558
- updated = walk(data, uri)
559
-
560
- if updated:
561
- with open(store, "wb") as f:
562
- plistlib.dump(data, f, fmt=plistlib.FMT_BINARY)
563
-
564
- print(updated)
565
- PYEOF
566
-
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
586
-
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.
590
- if command -v wallpaper &>/dev/null; then
591
- wallpaper set "$wp" --screen all 2>/dev/null || true
592
- else
593
- osascript -e "
594
- tell application \"System Events\"
595
- set theFile to POSIX file \"${wp}\"
596
- repeat with d in (get every desktop)
597
- set picture of d to theFile
598
- end repeat
599
- end tell" 2>/dev/null || true
600
- fi
601
- sleep 0.5
602
- ;;
603
- Linux)
604
- # DMS owns wallpaper display and runs matugen for color extraction.
605
- # Set the wallpaper ONCE for the current mode — DMS handles the rest.
606
- # Multiple IPC calls (pair-setting, mode switching) cause matugen
607
- # worker crashes and slow transitions. Keep it simple.
608
- if command -v dms &>/dev/null; then
609
- local dms_result current_outputs output
610
- dms_result="$(dms ipc wallpaper set "$wp" 2>/dev/null || true)"
611
- if [[ "$dms_result" == SUCCESS:* ]]; then
612
- ui_info "Applied via" "dms ipc"
613
- elif [[ "$dms_result" == ERROR:\ Per-monitor\ mode\ enabled* ]]; then
614
- current_outputs="$(dms ipc outputs current 2>/dev/null | tr -d '[]"')"
615
- for output in ${current_outputs//,/ }; do
616
- [[ -n "$output" ]] || continue
617
- dms ipc wallpaper setFor "$output" "$wp" >/dev/null 2>&1 || true
618
- done
619
- ui_info "Applied via" "dms ipc (per-monitor)"
620
- fi
621
- fi
622
-
623
- # gsettings-based desktop state for GTK/freedesktop consumers
624
- if command -v gsettings &>/dev/null; then
625
- # Find the matching pair for picture-uri and picture-uri-dark.
626
- # Wallpapers use two naming conventions:
627
- # -light/-dark (e.g. hello-light.png, hello-dark.png)
628
- # -0/-1 (e.g. hello-0.png = light, hello-1.png = dark)
629
- local light_wp="" dark_wp="" base ext family_base
630
- ext="${wp##*.}"
631
- # Strip -light/-dark suffix to get family base
632
- base="${wp%-${mode}.${ext}}"
633
- # Strip -0/-1 suffix to get family base for frame naming
634
- family_base="${wp%-[01].${ext}}"
635
- # If neither pattern matched, both equal $wp — derive family from theme
636
- if [[ "$family_base" == "$wp" && "$base" == "$wp" ]]; then
637
- family_base="${WALLPAPER_DIR}/${THEME%-dark}"
638
- [[ "$family_base" == "${WALLPAPER_DIR}/${THEME}" ]] && family_base="${WALLPAPER_DIR}/${THEME%-light}"
639
- base="$family_base"
640
- fi
641
-
642
- for try_ext in "$ext" png jpg webp heic; do
643
- [[ -z "$light_wp" ]] || break
644
- if [[ -f "${base}-light.${try_ext}" ]] && [[ -f "${base}-dark.${try_ext}" ]]; then
645
- light_wp="${base}-light.${try_ext}"
646
- dark_wp="${base}-dark.${try_ext}"
647
- elif [[ -f "${family_base}-0.${try_ext}" ]] && [[ -f "${family_base}-1.${try_ext}" ]]; then
648
- light_wp="${family_base}-0.${try_ext}"
649
- dark_wp="${family_base}-1.${try_ext}"
650
- fi
651
- done
652
-
653
- if [[ -n "$light_wp" ]] && [[ -n "$dark_wp" ]]; then
654
- light_wp="$(ensure_linux_compatible "$light_wp")"
655
- dark_wp="$(ensure_linux_compatible "$dark_wp")"
656
- gsettings set org.gnome.desktop.background picture-uri "file://${light_wp}"
657
- gsettings set org.gnome.desktop.background picture-uri-dark "file://${dark_wp}"
658
- gsettings set org.gnome.desktop.screensaver picture-uri "file://${light_wp}"
659
- else
660
- gsettings set org.gnome.desktop.background picture-uri "$wp_uri"
661
- gsettings set org.gnome.desktop.background picture-uri-dark "$wp_uri"
662
- gsettings set org.gnome.desktop.screensaver picture-uri "$wp_uri"
663
- fi
664
- gsettings set org.gnome.desktop.background picture-options "zoom"
665
- ui_info "Applied via" "gsettings"
666
- elif command -v swaybg &>/dev/null; then
667
- pkill swaybg || true
668
- swaybg -i "$wp" -m fill &
669
- ui_info "Applied via" "swaybg"
670
- elif command -v feh &>/dev/null; then
671
- feh --bg-fill "$wp"
672
- ui_info "Applied via" "feh"
673
- else
674
- ui_err "Wallpaper setter" "not found (gsettings/swaybg/feh)"
675
- return 1
676
- fi
677
- ;;
678
- *)
679
- ui_err "Unsupported OS" "wallpaper sync"
680
- return 1
681
- ;;
682
- esac
683
- }
684
-
685
- apply_wallpaper "$WALLPAPER" "$MODE"
686
- if [[ -n "$THEME" ]]; then
687
- ui_ok "Applied wallpaper (${MODE})" "$(basename "$WALLPAPER") ← $THEME"
688
- else
689
- ui_ok "Applied wallpaper (${MODE})" "$(basename "$WALLPAPER")"
690
- fi