Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,12 @@ jobs:
shell: bash
run: |
set -euo pipefail
printf '%s\n' "${{ steps.version.outputs.next }}" > VERSION
rm -rf build
./build.sh
./build-wallpaper.sh
codesign --verify --deep --strict "build/MatrixScreenSaver.saver"
codesign --verify --deep --strict "build/MatrixWallpaper.app"

- name: Update CHANGELOG
id: changelog
Expand Down Expand Up @@ -195,7 +199,6 @@ jobs:
version="${{ steps.version.outputs.next }}"
branch="${{ steps.version.outputs.branch }}"

printf '%s\n' "${version}" > VERSION
git add VERSION CHANGELOG.md
git commit -m "chore(release): ${version}" -m "- bump VERSION to ${version}" -m "- update CHANGELOG.md"
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
Expand All @@ -214,7 +217,7 @@ jobs:
changelog_section="$(awk "/^## \[${version}\]/{found=1; next} found && /^## /{exit} found{print}" CHANGELOG.md)"

pr_body="$(
printf '## What'\''s changing\n\n%s\n\n## Validation\n- `./build.sh`\n' "${changelog_section}"
printf '## What'\''s changing\n\n%s\n\n## Validation\n- `./build.sh`\n- `./build-wallpaper.sh`\n- Strict codesign verification of both bundles\n' "${changelog_section}"
)"

# Close any existing release PR so the new one starts with a clean
Expand Down Expand Up @@ -406,13 +409,16 @@ jobs:
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Build screen saver
- name: Build screen saver and wallpaper app
if: steps.release.outputs.exists != 'true'
shell: bash
run: |
set -euo pipefail
rm -rf build
./build.sh
./build-wallpaper.sh
codesign --verify --deep --strict "build/MatrixScreenSaver.saver"
codesign --verify --deep --strict "build/MatrixWallpaper.app"

- name: Stage release files
if: steps.release.outputs.exists != 'true'
Expand All @@ -427,6 +433,7 @@ jobs:
rm -rf "${release_dir}"
mkdir -p "${release_dir}"
ditto "build/MatrixScreenSaver.saver" "${release_dir}/MatrixScreenSaver.saver"
ditto "build/MatrixWallpaper.app" "${release_dir}/MatrixWallpaper.app"

echo "dir=${release_dir}" >> "$GITHUB_OUTPUT"

Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,15 @@ jobs:

- name: Run tests
run: ./tests.sh

- name: Build screen saver
run: ./build.sh

- name: Build wallpaper app
run: ./build-wallpaper.sh

- name: Check wallpaper installation and rollback
run: ./wallpaper-install-tests.sh

- name: Run wallpaper AppKit checks
run: ./wallpaper-tests.sh
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ build/
DerivedData/
out/

# Local agent tooling
.serena/
.projectatlas/

# macOS
.DS_Store
*.DS_Store
Expand All @@ -12,6 +16,16 @@ out/
.LSOverride
.localized
__MACOSX/
.Spotlight-V100/
.Trashes/
.TemporaryItems/
.fseventsd/

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/

# Editors and IDEs
.idea/
Expand Down Expand Up @@ -49,6 +63,10 @@ fastlane/test_output
*.orig
*.swp
*.swo
.history/
.vim/
*.swn
*.bak
*.temp
*.tmp
*~
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).



## [0.3.0] - 2026-10-23

### Features

- add Matrix desktop wallpaper with Homebrew installation (#102)
- publish Homebrew tap updates automatically (#100)
- distribute MatrixScreenSaver through Homebrew (#99)

### Bug Fixes

- avoid pure white rain highlights (#98)
- publish release PR required checks (#95)
- dispatch release checks on release branch (#93)

## [0.2.0] - 2026-06-10

### Features
Expand Down
25 changes: 22 additions & 3 deletions Homebrew/Casks/matrix-screen-saver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,45 @@

url "https://github.com/patrickschaper/matrixScreenSaver/releases/download/#{version}/#{version}.zip"
name "MatrixScreenSaver"
desc "Native terminal-style Matrix screen saver"
desc "Native terminal-style Matrix screen saver and wallpaper"
homepage "https://github.com/patrickschaper/matrixScreenSaver"

depends_on arch: :arm64
depends_on macos: :sequoia

app "MatrixWallpaper.app"
screen_saver "MatrixScreenSaver.saver"

preflight_steps do
if_path_exists "MatrixScreenSaver.saver", base: :screen_saverdir do
# Homebrew also grants read access here when the native target is under HOME.
run "/bin/echo",
args: ["Existing screen saver at {{screen_saverdir}}/MatrixScreenSaver.saver. " \
args: ["Existing screen saver in Homebrew's configured screen-saver directory. " \
"To replace it while retaining options, run: " \
"brew install --cask --force patrickschaper/tap/matrix-screen-saver"],
print_stdout: true,
writable_paths: [{ path: "MatrixScreenSaver.saver", base: :screen_saverdir }]
end

if_path_exists "MatrixWallpaper.app", base: :appdir do
run "/bin/echo",
args: ["Existing app at {{appdir}}/MatrixWallpaper.app. " \
"To replace it while retaining options, run: " \
"brew install --cask --force patrickschaper/tap/matrix-screen-saver"],
print_stdout: true,
writable_paths: [{ path: "MatrixWallpaper.app", base: :appdir }]
end

run "/bin/echo",
args: ["Removing staged quarantine bypasses Gatekeeper quarantine checks; " \
"this does not make the saver notarized or Apple-approved."],
"this does not make either bundle notarized or Apple-approved."],
print_stdout: true
run "/usr/bin/xattr",
args: ["-dr", "com.apple.quarantine", "{{staged_path}}/MatrixScreenSaver.saver"],
writable_paths: ["{{staged_path}}/MatrixScreenSaver.saver"]
run "/usr/bin/xattr",
args: ["-dr", "com.apple.quarantine", "{{staged_path}}/MatrixWallpaper.app"],
writable_paths: ["{{staged_path}}/MatrixWallpaper.app"]
end

postflight_steps do
Expand All @@ -39,5 +52,11 @@
run "/bin/echo",
args: ["Open System Settings > Wallpaper > Screen Saver and select MatrixScreenSaver."],
print_stdout: true
run "/bin/echo",
args: ["Open {{appdir}}/MatrixWallpaper.app to start Matrix Wallpaper. " \
"Launch at Login is optional in its Matrix menu."],
print_stdout: true
end

uninstall quit: "dev.patsch.MatrixWallpaper", login_item: "Matrix Wallpaper"
end
73 changes: 53 additions & 20 deletions Homebrew/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ The public tap is [patrickschaper/homebrew-tap](https://github.com/patrickschape
`Casks/matrix-screen-saver.rb` here is the source payload for the same path in
that repository. Users add it with `brew tap patrickschaper/tap`.

The payload uses Homebrew's native `screen_saver` artifact, not the local installer.
The next release payload uses Homebrew's native `screen_saver` and `app` artifacts,
not the local installers, to manage `MatrixScreenSaver.saver` and `MatrixWallpaper.app`
together. The live tap gains this behavior only after a new dual-bundle release and
tap update; immutable older releases remain saver-only.
It retains the `dev.patsch.MatrixScreenSaver` preferences domain and has no zap
or preference-cleanup hook. Its published binary requires Apple Silicon (ARM64)
and macOS 15 (Sequoia) or newer.
Expand All @@ -28,26 +31,34 @@ The fully qualified install trusts this cask, not the entire tap. Explicit
`brew trust --cask` also permits subsequent installs using the short name.

**Security:** preflight removes quarantine recursively only from the
checksum-verified staged `MatrixScreenSaver.saver`, before Homebrew moves it.
This bypasses Gatekeeper quarantine checks; it does not make the bundle notarized
checksum-verified staged `MatrixScreenSaver.saver` and `MatrixWallpaper.app`, before
Homebrew moves them. This bypasses Gatekeeper quarantine checks; it does not make either bundle notarized
or Apple-approved. Install only if you trust the release publisher. The cask
prints this disclosure before invoking `/usr/bin/xattr`; failure aborts installation.

After installation, open **System Settings > Wallpaper > Screen Saver** and
select **MatrixScreenSaver** manually. Modern Homebrew's sandbox blocks opening
Settings, so automatic opening was intentionally removed. The cask never selects
the saver or opens a browser. Successful install, upgrade, and reinstall print:
the saver, launches the wallpaper on a fresh install, enables login startup, or opens
a browser. Open **MatrixWallpaper** from Homebrew's configured `appdir` (normally
`/Applications`) yourself, then use its single **Launch at Login** checkbox if wanted.
Homebrew may reopen an already-running app following an upgrade. Successful install,
upgrade, and reinstall print:

> There is no spoon. There is coffee: https://www.buymeacoffee.com/yesman82

They also print both the screen-saver selection instructions and the actual
installed app path to open for Matrix Wallpaper, including custom `appdir` values,
with a reminder that Launch at Login is optional.

An existing manual bundle is not silently replaced. The native artifact rejects
an unforced conflict; opt in to replacement while retaining saved options with:

```bash
brew install --cask --force patrickschaper/tap/matrix-screen-saver
```

The conditional guidance honors Homebrew's configured `screen_saverdir`.
The conditional guidance honors Homebrew's configured `screen_saverdir` and `appdir`.
Homebrew 7.0.7's `if_path_exists` checks `exist?`, not `symlink?`: a dangling
symlink is still rejected by the native artifact, but does not print the custom
migration message. Use the same explicit `--force` command to replace it.
Expand All @@ -65,34 +76,48 @@ Upgrades and reinstalls remain native Homebrew operations. An upgrade may print
the existing-target guidance because Homebrew can leave an empty target directory
during replacement; no prior-receipt snapshot or initial-install-only behavior
is needed. Custom destinations are honored, and other saver copies are not removed.
The native app and screen-saver artifacts are both installed and removed by the
same cask. Uninstall requests that `dev.patsch.MatrixWallpaper` quit and asks
Homebrew to remove its login items. Homebrew skips login-item cleanup when an
upgrade/reinstall successor exists; no unconditional unregister script runs.
This cleanup is best effort, not a guarantee of immediate `SMAppService` entry
removal. macOS may retain deleted entries until its maintenance runs. Disable
**Launch at Login** before uninstall if you want the app to unregister explicitly.
Migration, upgrade, reinstall, and ordinary uninstall retain the
`dev.patsch.MatrixScreenSaver` options domain. There is no zap hook or preference
rewrite. Uninstall prints neither coffee nor installation guidance.
rewrite. Uninstall prints neither coffee nor installation guidance. Manual copies
outside Homebrew's configured destinations are not removed. The local wallpaper
installer defaults to `~/Applications`; it can coexist with Homebrew's default
`/Applications` copy. Quit the manual copy before migrating, or select
`--appdir=~/Applications` and explicitly use `--force` to replace it.

## Release handoff

After publication, the Release workflow downloads the **existing published**
versioned zip, including on reruns. It checks the asset digest, bundle identity
and version, archive layout, thin ARM64 Mach-O bundle, and minimum macOS version.
versioned zip, including on reruns. Release preparation and publication build both
products and the archive contains both bundle roots. Validation checks the asset
digest, both bundle identities and matching versions, archive layout, thin ARM64
Mach-O bundle/executable types, and the greater of their minimum macOS versions.
It uploads `homebrew-handoff-VERSION-ATTEMPT`, containing `Casks/matrix-screen-saver.rb`
and `manifest.json` with version, URL, SHA-256, architecture, OS floor, and provenance.
and `manifest.json` with version, URL, SHA-256, architecture, OS floor, both bundle
identifiers, and provenance.
The workflow then automatically commits matching release metadata to the tap's
default branch when the target variable and dedicated token are configured.
Each workflow attempt gets a separate artifact so reruns preserve earlier handoffs
without upload-name conflicts. The artifacts remain available for manual recovery.

Offline validation:
Offline validation of a newly published dual-bundle release:

```bash
python3 -B Scripts/render-homebrew-release.py \
--version 0.2.0 --archive /path/to/0.2.0.zip \
--asset-url https://github.com/patrickschaper/matrixScreenSaver/releases/download/0.2.0/0.2.0.zip \
--digest sha256:7a60c8ec2855a2df4cb008da7f79ea9e8526385897e3048bb53bb42421e07acd \
--provenance manual-release-download --output /path/to/handoff
--version VERSION --provenance manual-release-download --output /path/to/handoff
```

Omit `--archive`, `--asset-url`, and `--digest` to retrieve the release through
`gh`; the renderer never builds a replacement archive. A new unsupported OS floor
Replace `VERSION` with the actual published version. To use an existing local
download, also provide `--archive`, its immutable `--asset-url`, and its
`--digest sha256:...`. Otherwise the renderer retrieves the release through `gh`;
it never builds or replaces a published archive. Saver-only old archives fail
validation rather than generating a cask with a missing app. A new unsupported OS floor
or architecture fails closed until the compatibility policy is updated.

## Automatic tap updates (token pending)
Expand All @@ -116,7 +141,10 @@ after validating the published asset and uploading the handoff. It writes only
`Casks/matrix-screen-saver.rb` on the tap's default branch; no tap PR or merge is
required. This replaces the previous optional, human-merged tap PR behavior.
Only version, SHA-256, and minimum macOS metadata may differ from the source
template. Unrelated cask edits or a missing cask fail closed for manual review.
template. For the first dual-bundle release, the exact frozen saver-only predecessor
is also accepted for a strictly newer version, using its normalized structure hash.
This one reviewed transition does not allow arbitrary structural edits. Unrelated
cask edits or a missing cask fail closed for manual review.
Identical reruns make no commit, older versions are skipped, and conflicting
same-version metadata is rejected. Each write includes the current file SHA;
concurrent changes cause a failure, never a blind retry or forced overwrite.
Expand Down Expand Up @@ -147,6 +175,7 @@ ruby -c Homebrew/Casks/matrix-screen-saver.rb
bash -n Tests/Homebrew/integration_lifecycle.sh
./tests.sh
./build.sh
./build-wallpaper.sh
```

`.github/workflows/homebrew-tests.yml` runs offline tests without publishing secrets,
Expand All @@ -158,16 +187,20 @@ authorization for a disposable environment first, then run:
bash Tests/Homebrew/integration_lifecycle.sh
```

The test requires an ARM64 macOS host and Homebrew 7.0.7. It redirects the native
The test requires an ARM64 macOS host and Homebrew 7.0.7 or newer. CI pins 7.0.7
as the compatibility baseline. It redirects the native
destination, all artifact directories, Caskroom, cache, logs, and fixture tap into
one temporary directory. It uses real installers, upgrade orchestration, downloads,
native moves, sandboxed install steps, echo stdout, and quarantine operations.
native moves for both bundles, sandboxed install steps, echo stdout, and quarantine operations.
Only the operating-system process/login-item command boundary is substituted, so
the fixture never quits a real app or changes real login settings. Homebrew's
actual uninstall dispatch and successor handling still run.
There is no Settings-launch mock: the cask contains no Settings-launch command.
A separate
sentinel outside the artifact destination verifies preference retention; it does
not access the actual defaults domain. HOME alone is not used as isolation.

Native integration covers install, conflict failure, forced migration, upgrade,
Native integration covers saver-only migration, dual install, conflict failure, forced migration, upgrade,
reinstall, uninstall, quarantine removal, broken-symlink conflict/force semantics,
and developer-mode acceptance without legacy warnings on definition load or
uninstall. Run workflow lint (for example `actionlint`) separately; never conceal
Expand Down
Loading
Loading