macOS DMG: branded window background and icon layout

The DMG opened as a plain Finder window. It now shows the app icon's figure on
the EffectCraft colour, with EffectCraft.app and the Applications link side by
side.

- packaging/macos/dmg/: background.svg (source; text outlined, the figure an
  <image> of assets/app-icon) and dmg-layout.DS_Store (Finder view settings:
  window, icon size 128, positions, background), written by generate.py on any
  OS, byte-for-byte reproducible, with nothing from the machine that ran it.
- package.sh renders the SVG with resvg (1x + 2x) into a HiDPI TIFF at package
  time, so no bitmap is committed; release.yml installs resvg 0.48.1 (pinned,
  --locked) on the macOS runner. The image is still built with hdiutil
  makehybrid.
- The volume name drops the version: the layout finds the background through
  an alias that includes the volume name. The DMG file name keeps the version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Xus Badia 2026-10-08 15:12:32 +02:00
parent cae67546dd
commit 5826377d17
8 changed files with 185 additions and 1 deletions

View File

@ -88,6 +88,9 @@ jobs:
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: packaging/macos/import-cert.sh
# Renders the DMG background from packaging/macos/dmg/background.svg (package.sh).
- name: Install resvg
run: cargo install resvg --locked --version 0.48.1
- name: Build, sign, notarize
env:
APPLE_ID: ${{ secrets.APPLE_ID }}

View File

@ -17,6 +17,7 @@ iconography, images or other Adobe assets; open licences only; every asset attri
| `assets/fonts/NotoSerif-Regular.ttf` | The Noto Project Authors | https://github.com/notofonts/latin-greek-cyrillic | OFL-1.1 (`assets/fonts/OFL-NotoSerif.txt`) |
| `assets/fonts/JetBrainsMono-Regular.ttf` | The JetBrains Mono Project Authors | https://github.com/JetBrains/JetBrainsMono | OFL-1.1 (`assets/fonts/OFL-JetBrainsMono.txt`) |
| `apps/effectcraft-web/web/favicon.svg` | EffectCraft contributors | Original work: web app icon, a copy of `assets/app-icon/effectcraft-small.svg` (the unicorn app icon) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `packaging/macos/dmg/background.svg` | @XusBadia | Original work: macOS DMG Finder window background, derived from the EffectCraft app icon (`assets/app-icon/effectcraft.svg`); rendered to a TIFF by `packaging/macos/package.sh` at package time | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) |
| `assets/app-icon/effectcraft-1024.png` | EffectCraft contributors | Original work: effectCraft app icon, 1024 px (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/effectcraft-macos-512.png` | EffectCraft contributors | Original work: effectCraft runtime Dock icon (macOS) (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/effectcraft-small.svg` | EffectCraft contributors | Original work: effectCraft app icon, light vector (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |

View File

@ -0,0 +1,44 @@
# macOS DMG window
What Finder shows when the DMG opens: a 660 × 400 pt background with the app icon and the
`Applications` link side by side. `package.sh` renders the background from `background.svg` and copies it
and the layout into the image, so no bitmap is committed. The DMG still builds with
`hdiutil makehybrid` (no mounted device, no Finder scripting on CI).
| File | What |
|---|---|
| `background.svg` | Source of the background: the app icon (`assets/app-icon/effectcraft.svg`, linked, not copied) cropped as a cover on the EffectCraft colour field (`#e0368f`), Ink and Paper. Its text (Inter, JetBrains Mono) is outlined, so rendering it needs no fonts. `package.sh` renders it with [resvg](https://github.com/linebender/resvg) 0.48.1 at 1x (660 × 400 px) and 2x (1320 × 800 px) and joins both into `.background/background.tiff` (`tiffutil -cathidpicheck`); `release.yml` installs that resvg on the macOS runner. |
| `dmg-layout.DS_Store` | Finder's view settings for the volume: window size, icon size 128, EffectCraft.app at (326, 205), `Applications` at (574, 205), and the background. Goes to `.DS_Store` in the image; named so it isn't mistaken for (or ignored like) a Finder-generated `.DS_Store`. |
| `generate.py` | Writes `dmg-layout.DS_Store` from the layout above. |
## The volume name has no version
The mounted volume is called `EffectCraft`, not `EffectCraft <version>`. The layout points at the background
through an alias that includes the volume name, and Finder resolves it by that name: with a
versioned name the window keeps its size and icon positions but shows no background (tested).
The DMG file name (`effectcraft-<version>-macos-<arch>.dmg`) still carries the version.
## Rules
- **Finder draws the icon labels in black in light and dark mode** when a window has a background,
so the area under both icons stays light (Paper).
- **Nothing goes inside the icon boxes:** artwork keeps 10 pt clear of each 128 pt icon box and of
the label strip under it.
## Regenerate
- **The background:** edit `background.svg`. `package.sh` renders it on every build; to preview it,
`resvg --skip-system-fonts -w 1320 background.svg /tmp/bg@2x.png`. If you add text, outline it
(`usvg` from resvg converts text to paths), since no fonts are loaded.
- **The layout:** edit the constants in `generate.py`, then run, on any OS:
```sh
pip install ds_store==1.3.3 mac_alias==2.2.3
python3 packaging/macos/dmg/generate.py
```
`dmg-layout.DS_Store` is written from scratch and byte-for-byte reproducible: its background
alias holds only the volume name and `/.background/background.tiff`, nothing from the machine that
ran it. The window is 660 × 432 with Finder's 32 pt title bar; the content area is 660 × 400.
This follows VectorCraft's `packaging/macos/dmg/` (storytold/vectorcraft#493).

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,8 @@
asset: background.svg
title: EffectCraft macOS DMG window background
author: @XusBadia
source: original work
license: MIT OR Apache-2.0
license-file: LICENSE-MIT, LICENSE-APACHE
added: 2026-10-08 by @XusBadia
notes: Original work for this repository, derived from the EffectCraft app icon (assets/app-icon/effectcraft.svg, referenced via <image>, not copied). Used for the Finder window of the macOS DMG; packaging/macos/package.sh renders it to a TIFF at package time. Text is outlined Inter/JetBrains Mono glyph paths.

Binary file not shown.

View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Regenerate dmg-layout.DS_Store for the macOS DMG window (see README.md here).
Needs:
pip install ds_store==1.3.3 mac_alias==2.2.3
Runs on any OS, no Mac needed. The file comes from this script alone, so it carries nothing
from the machine that made it: no local paths, user or disk names, dates or volume UUIDs.
(background.tiff is rendered from background.svg by packaging/macos/package.sh at package time.)
python3 packaging/macos/dmg/generate.py
"""
import datetime
import os
from ds_store import DSStore
from mac_alias import Alias, TargetInfo, VolumeInfo
HERE = os.path.dirname(os.path.abspath(__file__))
# Keep in sync with package.sh (volume name) and README.md (layout).
VOLUME = "EffectCraft"
WIDTH, HEIGHT = 660, 400 # window content, pt; the background's 1x size
TITLE_BAR = 32
ICON_SIZE = 128
ICONS = {"EffectCraft.app": (326, 205), "Applications": (574, 205)}
# A fixed date for the alias. Finder never matches it against the image
# (each build is a new volume): it finds the background by volume name and path.
FIXED_DATE = datetime.datetime(2026, 10, 8, tzinfo=datetime.timezone.utc)
def background_alias():
# What Finder stores for /.background/background.tiff on the volume, minus anything about the
# machine (dmgbuild writes the same kind of alias from a mounted image). Disk type 5 (ejectable),
# the volume flags and the folder/file ids (25, 26) are what Finder wrote on a test image; the ids
# are only hints, as the volume's creation date.
volume = VolumeInfo(VOLUME, FIXED_DATE, b"H+", 5, 0x0D02, b"\0\0")
volume.posix_path = f"/Volumes/{VOLUME}"
target = TargetInfo(0, "background.tiff", 25, 26, FIXED_DATE, b"\0\0\0\0", b"\0\0\0\0")
target.folder_name = ".background"
target.cnid_path = [25]
target.carbon_path = f"{VOLUME}:.background:\0background.tiff"
target.posix_path = "/.background/background.tiff"
return Alias(volume=volume, target=target).to_bytes()
def write_ds_store(out):
if os.path.exists(out):
os.remove(out)
with DSStore.open(out, "w+") as d:
d["."]["bwsp"] = {
"ShowStatusBar": False,
"ShowToolbar": False,
"ShowTabView": False,
"ContainerShowSidebar": False,
"ShowSidebar": False,
"WindowBounds": f"{{{{200, 528}}, {{{WIDTH}, {HEIGHT + TITLE_BAR}}}}}",
}
d["."]["icvp"] = {
"viewOptionsVersion": 1,
"backgroundType": 2,
"backgroundImageAlias": background_alias(),
"backgroundColorRed": 1.0,
"backgroundColorGreen": 1.0,
"backgroundColorBlue": 1.0,
"gridOffsetX": 0.0,
"gridOffsetY": 0.0,
"gridSpacing": 100.0,
"arrangeBy": "none",
"showIconPreview": True,
"showItemInfo": False,
"labelOnBottom": True,
"textSize": 16.0,
"iconSize": float(ICON_SIZE),
}
d["."]["vSrn"] = ("long", 1)
for name, pos in ICONS.items():
d[name]["Iloc"] = pos
def main():
write_ds_store(os.path.join(HERE, "dmg-layout.DS_Store"))
if __name__ == "__main__":
main()

View File

@ -129,10 +129,20 @@ STAGE="$WORK/dmg"
mkdir -p "$STAGE"
ditto "$APP" "$STAGE/EffectCraft.app"
ln -s /Applications "$STAGE/Applications"
# Finder window layout: background, icon size and positions (packaging/macos/dmg/README.md).
# The background is rendered from its SVG (1x + 2x in one HiDPI TIFF), so no bitmap is committed.
command -v resvg >/dev/null || { echo "resvg not found (cargo install resvg --locked --version 0.48.1)" >&2; exit 1; }
mkdir -p "$STAGE/.background"
resvg --skip-system-fonts -w 660 "$HERE/dmg/background.svg" "$WORK/background.png"
resvg --skip-system-fonts -w 1320 "$HERE/dmg/background.svg" "$WORK/background@2x.png"
sips -s dpiWidth 144 -s dpiHeight 144 "$WORK/background@2x.png" >/dev/null
tiffutil -cathidpicheck "$WORK/background.png" "$WORK/background@2x.png" -out "$STAGE/.background/background.tiff"
cp "$HERE/dmg/dmg-layout.DS_Store" "$STAGE/.DS_Store"
rm -f "$DMG" "$WORK/raw.dmg"
# makehybrid + convert builds the image without attaching a device, unlike `create -srcfolder`,
# which is flaky on CI runners ("Resource busy") and hangs in sandboxed sessions.
hdiutil makehybrid -hfs -hfs-volume-name "EffectCraft $VERSION" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE"
# The volume name has no version: .DS_Store finds the background through an alias that includes it.
hdiutil makehybrid -hfs -hfs-volume-name "EffectCraft" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE"
hdiutil convert "$WORK/raw.dmg" -format UDZO -imagekey zlib-level=9 -o "$DMG"
rm -f "$WORK/raw.dmg"
sign "$DMG"