@sebastienrousseau/dotfiles 0.2.520 → 0.2.521
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 +18 -0
- package/LICENSE-APACHE +190 -0
- package/LICENSE-MIT +21 -0
- package/README.md +43 -37
- package/install.sh +76 -10
- package/package.json +7 -7
- package/tools/README.md +49 -0
- package/tools/ci/install-chezmoi-verified.sh +68 -0
- package/docs/.vitepress/reports/localization-readability-audit.md +0 -73
- package/docs/AI.md +0 -179
- package/docs/ARCHITECTURE.md +0 -117
- package/docs/CNAME +0 -1
- package/docs/CONFIG_STRATEGY.md +0 -124
- package/docs/COPYRIGHT +0 -7
- package/docs/ECOSYSTEM.md +0 -220
- package/docs/GOLD-STANDARD-AUDIT.md +0 -352
- package/docs/GOVERNANCE.md +0 -98
- package/docs/MAINTAINERS.md +0 -41
- package/docs/MINIMUM-TOOLCHAIN.md +0 -100
- package/docs/NAMING_CONVENTIONS.md +0 -102
- package/docs/OPENCODE.md +0 -127
- package/docs/README.md +0 -84
- package/docs/STRUCTURE.md +0 -102
- package/docs/adr/ADR-001-ci-cd-pipeline.md +0 -118
- package/docs/adr/ADR-002-shell-performance.md +0 -130
- package/docs/adr/ADR-003-security-first.md +0 -158
- package/docs/adr/ADR-004-cli-architecture.md +0 -171
- package/docs/adr/ADR-005-chezmoi-choice.md +0 -99
- package/docs/adr/ADR-006-shell-selection.md +0 -124
- package/docs/adr/ADR-007-multi-shell-parity.md +0 -62
- package/docs/adr/ADR-008-alias-system-architecture.md +0 -95
- package/docs/adr/ADR-009-wallpaper-driven-theming.md +0 -131
- package/docs/adr/ADR-010-starship-transient-prompt.md +0 -144
- package/docs/adr/ADR-011-nushell-tier3-keep.md +0 -144
- package/docs/adr/ADR-012-ai-fleet-local-proxy.md +0 -79
- package/docs/adr/README.md +0 -40
- package/docs/architecture/AI_COST_OPTIMIZATION.md +0 -144
- package/docs/architecture/ARCHITECTURE.md +0 -20
- package/docs/architecture/INTEROP.md +0 -44
- package/docs/architecture/REPO_LAYOUT.md +0 -241
- package/docs/architecture/WALKTHROUGH.md +0 -86
- package/docs/architecture/fleet-deployment.md +0 -77
- package/docs/archive/EUXIS_2026_REVIEW.md +0 -127
- package/docs/archive/LEGACY_ROADMAP.md +0 -6
- package/docs/archive/MILESTONE_v0.2.493.md +0 -47
- package/docs/archive/PLAN.md +0 -199
- package/docs/archive/REPO_AUDIT.md +0 -31
- package/docs/articles/.pages +0 -6
- package/docs/articles/2026-07-05-custom-mkdocs-material-dark-theme.md +0 -216
- package/docs/articles/2026-07-05-fish-startup-abbr.md +0 -153
- package/docs/articles/2026-07-05-master-to-main-rename-runbook.md +0 -128
- package/docs/articles/index.md +0 -36
- package/docs/guides/INSTALL.md +0 -144
- package/docs/guides/MACOS_ICLOUD_SYMLINKS.md +0 -121
- package/docs/guides/NEOVIM_IDE_GUIDE.md +0 -61
- package/docs/guides/THEMING.md +0 -230
- package/docs/guides/TROUBLESHOOTING.md +0 -176
- package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +0 -792
- package/docs/index.md +0 -132
- package/docs/interop/A2A.md +0 -39
- package/docs/interop/POWERSHELL.md +0 -102
- package/docs/manual/00-introduction.md +0 -89
- package/docs/manual/01-concepts/01-architecture.md +0 -138
- package/docs/manual/01-concepts/02-trust-model.md +0 -183
- package/docs/manual/01-concepts/03-theme-engine.md +0 -186
- package/docs/manual/01-concepts/04-fleet.md +0 -148
- package/docs/manual/01-concepts/05-self-healing.md +0 -204
- package/docs/manual/02-tutorials/01-first-install.md +0 -197
- package/docs/manual/02-tutorials/02-add-wallpaper.md +0 -216
- package/docs/manual/02-tutorials/03-create-profile.md +0 -244
- package/docs/manual/02-tutorials/04-encrypt-secret.md +0 -281
- package/docs/manual/02-tutorials/05-deploy-fleet.md +0 -283
- package/docs/manual/03-reference/01-dot-cli.md +0 -475
- package/docs/manual/03-reference/02-config-files.md +0 -265
- package/docs/manual/03-reference/03-environment.md +0 -124
- package/docs/manual/03-reference/04-templates.md +0 -190
- package/docs/manual/03-reference/05-feature-flags.md +0 -187
- package/docs/manual/04-cookbook/01-recipes.md +0 -285
- package/docs/manual/04-cookbook/02-troubleshooting.md +0 -351
- package/docs/manual/04-cookbook/03-faq.md +0 -175
- package/docs/manual/05-appendices/A-platform-matrix.md +0 -101
- package/docs/manual/05-appendices/B-security-checklist.md +0 -85
- package/docs/manual/05-appendices/C-glossary.md +0 -40
- package/docs/manual/05-appendices/D-bibliography.md +0 -58
- package/docs/manual/05-appendices/E-license.md +0 -38
- package/docs/manual/_toc.yml +0 -58
- package/docs/manual/command-index.md +0 -175
- package/docs/manual/concept-index.md +0 -170
- package/docs/manual/index.md +0 -66
- package/docs/migration/README.md +0 -81
- package/docs/migration/from-bare-git-repo.md +0 -156
- package/docs/migration/from-gnu-stow.md +0 -165
- package/docs/migration/from-plain-chezmoi.md +0 -148
- package/docs/migration/from-yadm.md +0 -187
- package/docs/operations/ARCHITECTURE_ROADMAP.md +0 -7
- package/docs/operations/ATTESTATION.md +0 -44
- package/docs/operations/CI_CADENCE.md +0 -107
- package/docs/operations/CI_COMPOSITES.md +0 -156
- package/docs/operations/COMPLETIONS.md +0 -123
- package/docs/operations/COVERAGE.md +0 -204
- package/docs/operations/DRIFT.md +0 -107
- package/docs/operations/HARD_AUDIT_2026.md +0 -631
- package/docs/operations/MAINTENANCE.md +0 -63
- package/docs/operations/MANIFEST.md +0 -127
- package/docs/operations/MIGRATION.md +0 -109
- package/docs/operations/OPERATIONS.md +0 -188
- package/docs/operations/PERFORMANCE.md +0 -133
- package/docs/operations/PERFORMANCE_BUDGETS.md +0 -196
- package/docs/operations/REGISTRY.md +0 -90
- package/docs/operations/RELEASE_PIPELINE.md +0 -128
- package/docs/operations/RELIABILITY.md +0 -122
- package/docs/operations/RFC_v0_2_503_reorganization.md +0 -280
- package/docs/operations/ROADMAP.md +0 -10
- package/docs/operations/ROADMAP_2026.md +0 -7
- package/docs/operations/ROADMAP_V0_2_503.md +0 -10
- package/docs/operations/TESTING.md +0 -216
- package/docs/operations/TRACEABILITY.md +0 -44
- package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +0 -65
- package/docs/operations/VERSION_SYNC.md +0 -393
- package/docs/packaging.md +0 -222
- package/docs/reference/ALIASES.md +0 -131
- package/docs/reference/ALIASES_CHEATSHEET.md +0 -32
- package/docs/reference/ALIASES_DEPRECATIONS.md +0 -13
- package/docs/reference/FEATURE-MATRIX.md +0 -646
- package/docs/reference/FEATURES.md +0 -66
- package/docs/reference/FONTS.md +0 -112
- package/docs/reference/POWERSHELL_PARITY.md +0 -82
- package/docs/reference/PROFILES.md +0 -69
- package/docs/reference/SCREENSHOTS.md +0 -121
- package/docs/reference/SCRIPTS.md +0 -71
- package/docs/reference/SUPPORT_MATRIX.md +0 -80
- package/docs/reference/THEMES.md +0 -117
- package/docs/reference/TOOLS.md +0 -110
- package/docs/reference/UTILS.md +0 -243
- package/docs/registry.json +0 -6
- package/docs/schema/dot-env-v1.json +0 -110
- package/docs/schema/dot-registry-v1.json +0 -33
- package/docs/security/AI_ACT_COMPLIANCE.md +0 -94
- package/docs/security/AUDIT_BYPASS.md +0 -103
- package/docs/security/AUTOMATION_SECRETS.md +0 -26
- package/docs/security/CI_EGRESS_ALLOWLIST.md +0 -127
- package/docs/security/CI_PINNING.md +0 -129
- package/docs/security/COMMIT_SIGNING.md +0 -138
- package/docs/security/COMPLIANCE.md +0 -458
- package/docs/security/DEPS_DEV_EXCEPTIONS.md +0 -86
- package/docs/security/DISCLOSURE.md +0 -130
- package/docs/security/ENCRYPTION.md +0 -57
- package/docs/security/FMEA.md +0 -159
- package/docs/security/FUZZING.md +0 -209
- package/docs/security/HISTORY_FILTERING.md +0 -132
- package/docs/security/INCIDENT_RESPONSE.md +0 -579
- package/docs/security/INSTALL_VERIFICATION.md +0 -122
- package/docs/security/KEYS.md +0 -49
- package/docs/security/KEY_ROTATION.md +0 -303
- package/docs/security/MCP_POLICY.md +0 -78
- package/docs/security/POLICY_RELEASES.md +0 -37
- package/docs/security/README.md +0 -28
- package/docs/security/SCORECARD.md +0 -195
- package/docs/security/SECRETS.md +0 -158
- package/docs/security/SECURITY.md +0 -45
- package/docs/security/SECURITY_CHECKLIST.md +0 -55
- package/docs/security/SHELL_EXEMPTIONS.md +0 -145
- package/docs/security/SOUP_REGISTER.md +0 -36
- package/docs/security/THREAT_MODEL.md +0 -130
- package/docs/security/VERIFICATION_VALIDATION.md +0 -228
- package/docs/security/VERIFY_RELEASE.md +0 -201
- package/docs/security/security-pubkey.asc +0 -15
- package/docs/stylesheets/extra.css +0 -444
- package/docs/themes/README.md +0 -10
- package/docs/themes/VISUAL_INTEGRITY_REPORT.md +0 -30
- package/docs/themes/hero-shot.svg +0 -78
- package/scripts/README.md +0 -123
- package/scripts/ci/check-copyright-headers.sh +0 -8
- package/scripts/ci/check-shell-preamble.sh +0 -8
- package/scripts/ci/guard-gitleaks-checkout.sh +0 -8
- package/scripts/demo/record.sh +0 -43
- package/scripts/diagnostics/a2a-conformance.sh +0 -163
- package/scripts/diagnostics/alias-governance.sh +0 -165
- package/scripts/diagnostics/aliases-cheatsheet.sh +0 -74
- package/scripts/diagnostics/aliases-manifest.sh +0 -77
- package/scripts/diagnostics/attest-verify.sh +0 -147
- package/scripts/diagnostics/benchmark.sh +0 -408
- package/scripts/diagnostics/conflicts.sh +0 -73
- package/scripts/diagnostics/doctor-unified.sh +0 -43
- package/scripts/diagnostics/doctor.sh +0 -797
- package/scripts/diagnostics/drift-dashboard.sh +0 -203
- package/scripts/diagnostics/health.sh +0 -656
- package/scripts/diagnostics/history-analysis.sh +0 -86
- package/scripts/diagnostics/mcp-doctor.sh +0 -582
- package/scripts/diagnostics/perf.sh +0 -453
- package/scripts/diagnostics/scorecard.sh +0 -120
- package/scripts/diagnostics/secret-governance.sh +0 -65
- package/scripts/diagnostics/security-score.sh +0 -467
- package/scripts/diagnostics/smoke-test.sh +0 -88
- package/scripts/diagnostics/snapshot.sh +0 -90
- package/scripts/diagnostics/verify.sh +0 -108
- package/scripts/diagnostics/verify_state.sh +0 -73
- package/scripts/diagnostics/version-locks.sh +0 -94
- package/scripts/diagnostics/workstation-attestation.sh +0 -212
- package/scripts/dot/commands/agent.sh +0 -535
- package/scripts/dot/commands/agents.sh +0 -352
- package/scripts/dot/commands/ai.sh +0 -600
- package/scripts/dot/commands/aliases.sh +0 -277
- package/scripts/dot/commands/appearance.sh +0 -110
- package/scripts/dot/commands/completion.sh +0 -171
- package/scripts/dot/commands/core.sh +0 -217
- package/scripts/dot/commands/diagnostics.sh +0 -265
- package/scripts/dot/commands/env-emit.sh +0 -203
- package/scripts/dot/commands/fleet.sh +0 -711
- package/scripts/dot/commands/init.sh +0 -185
- package/scripts/dot/commands/lint.sh +0 -208
- package/scripts/dot/commands/manual.sh +0 -169
- package/scripts/dot/commands/meta.sh +0 -438
- package/scripts/dot/commands/patterns.sh +0 -55
- package/scripts/dot/commands/registry.sh +0 -455
- package/scripts/dot/commands/restore.sh +0 -232
- package/scripts/dot/commands/secrets.sh +0 -296
- package/scripts/dot/commands/security.sh +0 -102
- package/scripts/dot/commands/tools.sh +0 -570
- package/scripts/dot/data/alias-deprecations.tsv +0 -2
- package/scripts/dot/powershell/Dot.psm1 +0 -319
- package/scripts/fonts/install-nerd-fonts.sh +0 -75
- package/scripts/fonts/patch-fonts.sh +0 -36
- package/scripts/git-hooks/install.sh +0 -12
- package/scripts/git-hooks/pre-commit +0 -12
- package/scripts/git-hooks/pre-commit-audit.sh +0 -146
- package/scripts/git-hooks/pre-push +0 -105
- package/scripts/git-hooks/prepare-commit-msg +0 -29
- package/scripts/lib/secrets_provider.sh +0 -200
- package/scripts/nvim/headless-upgrade.lua +0 -81
- package/scripts/ops/ai-setup.sh +0 -71
- package/scripts/ops/bundle.sh +0 -104
- package/scripts/ops/chaos.sh +0 -50
- package/scripts/ops/chezmoi-apply.sh +0 -333
- package/scripts/ops/chezmoi-diff.sh +0 -16
- package/scripts/ops/chezmoi-remove.sh +0 -46
- package/scripts/ops/chezmoi-update.sh +0 -67
- package/scripts/ops/heal-chezmoi.sh +0 -87
- package/scripts/ops/heal-system.sh +0 -129
- package/scripts/ops/heal-tools.sh +0 -297
- package/scripts/ops/heal.sh +0 -223
- package/scripts/ops/post-apply-repair.sh +0 -107
- package/scripts/ops/prewarm.sh +0 -128
- package/scripts/ops/release.sh +0 -262
- package/scripts/ops/rollback.sh +0 -613
- package/scripts/ops/setup.sh +0 -138
- package/scripts/ops/teleport.sh +0 -34
- package/scripts/qa/check-feature-matrix.sh +0 -296
- package/scripts/qa/check-version-consistency.sh +0 -12
- package/scripts/qa/coverage-baseline.sh +0 -61
- package/scripts/qa/docs-coverage.sh +0 -118
- package/scripts/qa/examples-coverage.sh +0 -94
- package/scripts/qa/powershell-contract.ps1 +0 -95
- package/scripts/qa/reliability-audit.sh +0 -139
- package/scripts/qa/scorecard-snapshot.sh +0 -128
- package/scripts/qa/traceability-coverage.sh +0 -124
- package/scripts/qa/validate-examples.sh +0 -90
- package/scripts/qa/wsl-contract.sh +0 -12
- package/scripts/secrets/age-init.sh +0 -82
- package/scripts/secrets/create-secrets-file.sh +0 -46
- package/scripts/secrets/encrypt-ssh-key.sh +0 -44
- package/scripts/security/backup.sh +0 -58
- package/scripts/security/check-disclosure-key-expiry.sh +0 -111
- package/scripts/security/dns-doh.sh +0 -52
- package/scripts/security/encryption-check.sh +0 -55
- package/scripts/security/enforce-policies.sh +0 -552
- package/scripts/security/firewall.sh +0 -91
- package/scripts/security/lock-configs.sh +0 -67
- package/scripts/security/lock-screen.sh +0 -56
- package/scripts/security/manage-secrets.sh +0 -429
- package/scripts/security/ssh-cert.sh +0 -204
- package/scripts/security/telemetry-kill.sh +0 -51
- package/scripts/security/usb-safety.sh +0 -52
- package/scripts/theme/apply-gnome-theme.sh +0 -333
- package/scripts/theme/extract-heic-frames.sh +0 -115
- package/scripts/theme/extract-theme.py +0 -1020
- package/scripts/theme/install-boot-logo.sh +0 -63
- package/scripts/theme/install-catppuccin-themes.sh +0 -371
- package/scripts/theme/install-cursors.sh +0 -26
- package/scripts/theme/install-file-icons.sh +0 -27
- package/scripts/theme/install-grub-theme.sh +0 -62
- package/scripts/theme/install-lock-icon.sh +0 -31
- package/scripts/theme/merge-wallpaper.sh +0 -146
- package/scripts/theme/rebuild-themes.sh +0 -603
- package/scripts/theme/switch.sh +0 -476
- package/scripts/theme/wallpaper-rotate.sh +0 -137
- package/scripts/theme/wallpaper-sync.sh +0 -690
- package/scripts/tools/cmatrix.sh +0 -22
- package/scripts/tools/detect-collisions.py +0 -103
- package/scripts/tools/emoji-picker.sh +0 -49
- package/scripts/tools/figlet-banner.sh +0 -19
- package/scripts/tools/log-rotate.sh +0 -31
- package/scripts/tools/lolcat-wrap.sh +0 -20
- package/scripts/tools/pipes.sh +0 -49
- package/scripts/tuning/linux.sh +0 -186
- package/scripts/tuning/macos.sh +0 -56
- package/scripts/uninstall.sh +0 -86
- package/scripts/verify-release-versions +0 -156
- package/scripts/version-sync.sh +0 -714
- package/templates/chezmoi-data/geekom-a9.toml.example +0 -21
- package/templates/chezmoi-data/mac-m1.toml.example +0 -16
- package/templates/chezmoi-data/mac-t2-linux.toml.example +0 -21
- package/templates/chezmoi-data/surface-pro-7p.toml.example +0 -21
- package/templates/projects/go/.github/workflows/ci.yml +0 -31
- package/templates/projects/go/README.md +0 -7
- package/templates/projects/go/cmd/__PROJECT_NAME__/main.go +0 -8
- package/templates/projects/go/go.mod +0 -3
- package/templates/projects/go/go.sum +0 -0
- package/templates/projects/molecule/README.md +0 -7
- package/templates/projects/molecule/converge.yml +0 -7
- package/templates/projects/molecule/molecule.yml +0 -16
- package/templates/projects/node/.github/workflows/ci.yml +0 -30
- package/templates/projects/node/README.md +0 -7
- package/templates/projects/node/package-lock.json +0 -12
- package/templates/projects/node/package.json +0 -10
- package/templates/projects/node/src/index.js +0 -3
- package/templates/projects/packer/README.md +0 -15
- package/templates/projects/packer/main.pkr.hcl +0 -15
- package/templates/projects/python/.github/workflows/ci.yml +0 -34
- package/templates/projects/python/README.md +0 -7
- package/templates/projects/python/pyproject.toml +0 -25
- package/templates/projects/python/src/__PROJECT_NAME__/__init__.py +0 -2
- 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
|