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 SoundCraft colour, with SoundCraft.app and the Applications link side by side.
Same approach as PhotoCraft's and VectorCraft's (storytold/photocraft#1201,
storytold/vectorcraft#493).

- packaging/macos/dmg/: background.svg (source; text outlined, no paper grain,
  the figure an <image> of assets/app-icon), background.tiff (1x + 2x HiDPI,
  16-colour palette, Deflate, sRGB; ~28 KB)
  and dmg-layout.DS_Store (Finder view settings: window, icon size 128,
  positions, background).
- generate.py writes the TIFF and the layout on any OS, byte-for-byte
  reproducible, with nothing from the machine that ran it (no paths, users,
  disk names or volume UUIDs).
- package.sh copies them into the staging folder; the image is still built with
  hdiutil makehybrid. Nothing extra runs in the signing job.
- 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 <[email protected]>
This commit is contained in:
Xus BadiaandClaude Opus 5.5 committed 2026-10-09 07:43:45 +02:00
1 parent c51e5d5ec5
commit 617888baeb
8 files changed
+243 -1

No files matched your search

+1
View File
@@ -38,6 +38,7 @@ and, at runtime, the operating system's own UI fonts when available (never redis
| `docs/brand/artcraft-mark-black.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `docs/brand/artcraft-mark-black.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms |
| `assets/app-icon/icon.svg` | SoundCraft contributors | original (hand-placed SVG paths; placeholder app icon) | MIT OR Apache-2.0 |
| `packaging/macos/dmg/` (all files) | @XusBadia | original (macOS DMG window background and layout, from the app icon; see `packaging/macos/dmg/README.md`) | MIT OR Apache-2.0 |
| `assets/app-icon/soundcraft-1024.png` | SoundCraft contributors | original, rendered from `icon.svg` | MIT OR Apache-2.0 |
| `assets/app-icon/soundcraft-macos-512.png` | SoundCraft contributors | original, rendered from `icon.svg` | MIT OR Apache-2.0 |
| `assets/app-icon/soundcraft.icns` | SoundCraft contributors | original, rendered from `icon.svg` | MIT OR Apache-2.0 |
+3
View File
@@ -94,6 +94,9 @@ plugins), and WebAudio in the browser.
- **Notarization:** the app is zipped and sent with `xcrun notarytool submit --wait`, then the
ticket is stapled to the app. The app goes on a DMG (`hdiutil`, with an `Applications` link).
The DMG is signed, notarized and stapled too.
Its Finder window (background, icon size and positions) comes from
[`packaging/macos/dmg/`](../packaging/macos/dmg/README.md), and its volume is named `SoundCraft`
without the version, which the window's background needs; the DMG file name keeps the version.
- **CLI:** the universal `soundcraft-cli` is signed with the hardened runtime, zipped and
notarized.
+44
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` copies these files into the image; nothing here is
generated at build time, so the DMG still builds with `hdiutil makehybrid` (no mounted device, no
Finder scripting on CI, nothing extra installed in the signing job).
| File | What |
|---|---|
| `background.svg` | Source of the background: the app icon (`assets/app-icon/icon.svg`, linked, not copied) cropped as a cover on the SoundCraft colour field (`#14a9c4`), Ink and Paper. Its text (Inter, JetBrains Mono) is outlined, so rendering it needs no fonts. No paper grain: noise doesn't compress, and this keeps `background.tiff` small. |
| `background.tiff` | The background at 1x (660 × 400 px, 72 dpi) and 2x (1320 × 800 px, 144 dpi) in one HiDPI TIFF (16-colour palette, Deflate, sRGB). Goes to `.background/background.tiff`. |
| `dmg-layout.DS_Store` | Finder's view settings for the volume: window size, icon size 128, SoundCraft.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 `background.tiff` and `dmg-layout.DS_Store` from the SVG and the layout above. |
## The volume name has no version
The mounted volume is called `SoundCraft`, not `SoundCraft <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 (`soundcraft-<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
Edit `background.svg` (or the layout constants in `generate.py`), then run, on any OS:
```sh
pip install 'pillow>=12' ds_store==1.3.3 mac_alias==2.2.3
python3 packaging/macos/dmg/generate.py # needs resvg on PATH
```
Both outputs are byte-for-byte reproducible and carry nothing from the machine that ran it: the
SVG renders without any fonts, and `dmg-layout.DS_Store` is written from scratch, its background
alias holding only the volume name and `/.background/background.tiff`. If you add text to the SVG,
outline it (`usvg` from resvg converts text to paths). 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).
+31
View File
@@ -0,0 +1,31 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="660" height="400" viewBox="0 0 660 400">
<defs><clipPath id="panel"><rect width="232" height="400"/></clipPath><pattern id="hatch" width="2.6" height="2.6" patternUnits="userSpaceOnUse" patternTransform="rotate(-38)">
<rect width="2.6" height="1.05" fill="#0b0b0c"/></pattern><filter id="grain" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="7"/>
<feColorMatrix values="0 0 0 0 0.04 0 0 0 0 0.04 0 0 0 0 0.05 0.5 0 0 0 -0.225"/>
</filter></defs>
<rect width="660" height="400" fill="#efe9dc"/>
<g clip-path="url(#panel)"><rect width="232" height="400" fill="#14a9c4"/><g transform="translate(260.0,29.6) scale(-0.8,0.8)"><image href="../../../assets/app-icon/icon.svg" width="512" height="512"/></g></g>
<line x1="232.5" y1="0" x2="232.5" y2="400" stroke="#0b0b0c" stroke-width="1.2"/>
<line x1="236.5" y1="0" x2="236.5" y2="400" stroke="#0b0b0c" stroke-width="0.5"/>
<line x1="267" y1="88.5" x2="624" y2="88.5" stroke="#0b0b0c" stroke-width="0.6"/>
<path d="M271.5,81.5 L276,88.5 L271.5,95.5 L267,88.5 Z" fill="#14a9c4"/>
<path d="M422,187 Q438,205 422,223" fill="none" stroke="#14a9c4" stroke-width="3.4" stroke-linecap="round"/><path d="M440,182 Q456,205 440,228" fill="none" stroke="#14a9c4" stroke-width="3.0" stroke-linecap="round"/><path d="M458,177 Q474,205 458,233" fill="none" stroke="#14a9c4" stroke-width="2.6" stroke-linecap="round"/><path d="M476,172 Q492,205 476,238" fill="none" stroke="#14a9c4" stroke-width="2.2" stroke-linecap="round"/>
<line x1="267" y1="335.5" x2="624" y2="335.5" stroke="#0b0b0c" stroke-width="0.6"/>
<g id="text">
<g>
<path fill="#0b0b0c" stroke="none" d="M 275.8584 72.336914 Q 272.0498 72.336914 269.78662 70.5791 Q 267.52344 68.82129 267.3916 65.583984 L 271.24414 65.583984 Q 271.37598 67.3125 272.6797 68.154785 Q 273.9834 68.99707 275.8291 68.99707 Q 277.7627 68.99707 278.98584 68.12549 Q 280.20898 67.25391 280.20898 65.84766 Q 280.20898 64.57324 279.125 63.921387 Q 278.04102 63.26953 276.26855 62.815434 L 273.79297 62.170902 Q 271.0537 61.46777 269.52295 60.046875 Q 267.9922 58.625977 267.9922 56.31152 Q 267.9922 54.377926 269.03223 52.93506 Q 270.07227 51.492188 271.87402 50.686527 Q 273.67578 49.88086 275.96094 49.88086 Q 278.29004 49.88086 280.04053 50.686527 Q 281.79102 51.492188 282.7871 52.913086 Q 283.7832 54.333984 283.82715 56.15039 L 280.0332 56.15039 Q 279.88672 54.75879 278.78076 53.98242 Q 277.6748 53.20606 275.90234 53.20606 Q 274.05664 53.20606 273.00195 54.019043 Q 271.94727 54.83203 271.94727 56.07715 Q 271.94727 57 272.51123 57.578613 Q 273.0752 58.157227 273.93213 58.50879 Q 274.78906 58.860348 275.66797 59.08008 L 277.7041 59.607426 Q 279.31543 60.00293 280.7876 60.742676 Q 282.25977 61.482426 283.18994 62.727535 Q 284.12012 63.972652 284.12012 65.87695 Q 284.12012 67.7959 283.13867 69.25342 Q 282.15723 70.71094 280.31152 71.523926 Q 278.46582 72.336914 275.8584 72.336914 Z M 294.1377 72.336914 Q 291.75 72.336914 289.9922 71.2749 Q 288.23438 70.21289 287.2749 68.31592 Q 286.31543 66.418945 286.31543 63.899414 Q 286.31543 61.350586 287.2749 59.438965 Q 288.23438 57.527344 289.9922 56.472656 Q 291.75 55.41797 294.1377 55.41797 Q 296.5254 55.41797 298.2832 56.472656 Q 300.04102 57.527344 301.0078 59.438965 Q 301.9746 61.350586 301.9746 63.899414 Q 301.9746 66.418945 301.0078 68.31592 Q 300.04102 70.21289 298.2832 71.2749 Q 296.5254 72.336914 294.1377 72.336914 Z M 294.1377 69.24609 Q 295.4707 69.24609 296.3496 68.52832 Q 297.22852 67.81055 297.66797 66.59473 Q 298.10742 65.37891 298.10742 63.884766 Q 298.10742 62.375977 297.66797 61.160156 Q 297.22852 59.944336 296.3496 59.226563 Q 295.4707 58.50879 294.1377 58.50879 Q 292.81934 58.50879 291.94043 59.226563 Q 291.06152 59.944336 290.6294 61.160156 Q 290.19727 62.375977 290.19727 63.884766 Q 290.19727 65.37891 290.6294 66.59473 Q 291.06152 67.81055 291.94043 68.52832 Q 292.81934 69.24609 294.1377 69.24609 Z M 310.41016 72.20508 Q 307.87598 72.20508 306.3379 70.59375 Q 304.7998 68.98242 304.7998 66.038086 L 304.7998 55.623047 L 308.6084 55.623047 L 308.6084 65.45215 Q 308.6084 67.09277 309.46533 68.03027 Q 310.32227 68.96777 311.8164 68.96777 Q 313.33984 68.96777 314.3213 67.98633 Q 315.30273 67.00488 315.30273 65.21777 L 315.30273 55.623047 L 319.12598 55.623047 L 319.12598 72 L 315.5078 72 L 315.47852 68.71875 Q 314.01367 72.20508 310.41016 72.20508 Z M 326.47754 62.405273 L 326.47754 72 L 322.66895 72 L 322.66895 55.623047 L 326.27246 55.623047 L 326.3164 58.889645 Q 327.78125 55.41797 331.38477 55.41797 Q 333.91895 55.41797 335.4497 57.0293 Q 336.98047 58.640625 336.98047 61.58496 L 336.98047 72 L 333.17188 72 L 333.17188 62.170902 Q 333.17188 60.530273 332.32227 59.592777 Q 331.47266 58.655273 329.97852 58.655273 Q 328.45508 58.655273 327.4663 59.63672 Q 326.47754 60.618164 326.47754 62.405273 Z M 346.5586 72.29297 Q 344.625 72.29297 343.09424 71.3042 Q 341.56348 70.31543 340.68457 68.42578 Q 339.80566 66.53613 339.80566 63.84082 Q 339.80566 61.11621 340.69922 59.233883 Q 341.59277 57.351563 343.12354 56.38477 Q 344.65433 55.41797 346.54398 55.41797 Q 348.0088 55.41797 348.93896 55.90869 Q 349.86914 56.399418 350.41113 57.080566 Q 350.95316 57.76172 351.2461 58.33301 L 351.40723 58.33301 L 351.40723 50.17383 L 355.21582 50.17383 L 355.21582 72 L 351.48047 72 L 351.48047 69.40723 L 351.2461 69.40723 Q 350.93848 69.993164 350.38184 70.66699 Q 349.8252 71.34082 348.89502 71.816895 Q 347.9648 72.29297 346.5586 72.29297 Z M 347.59863 69.1582 Q 349.45895 69.1582 350.46973 67.686035 Q 351.48047 66.21387 351.48047 63.82617 Q 351.48047 61.438473 350.47708 59.995605 Q 349.47363 58.552734 347.59863 58.552734 Q 345.66504 58.552734 344.6836 60.046875 Q 343.70215 61.54102 343.70215 63.82617 Q 343.70215 66.12598 344.69095 67.64209 Q 345.67966 69.1582 347.59863 69.1582 Z M 368.22168 72.29297 Q 365.3506 72.29297 363.09473 70.95996 Q 360.83887 69.62695 359.53516 67.12207 Q 358.23145 64.61719 358.23145 61.101563 Q 358.23145 57.571285 359.5425 55.059082 Q 360.85352 52.54688 363.10938 51.213863 Q 365.36523 49.88086 368.22168 49.88086 Q 370.60934 49.88086 372.56494 50.774414 Q 374.5205 51.66797 375.79492 53.359863 Q 377.06934 55.051754 377.42087 57.439453 L 373.4805 57.439453 Q 373.11426 55.50586 371.6787 54.443848 Q 370.24316 53.381836 368.2803 53.381836 Q 365.5996 53.381836 363.8784 55.381344 Q 362.15723 57.38086 362.15723 61.101563 Q 362.15723 64.86621 363.88574 66.8291 Q 365.61426 68.79199 368.26563 68.79199 Q 370.22852 68.79199 371.67136 67.72998 Q 373.11426 66.66797 373.4805 64.734375 L 377.43555 64.734375 Q 377.12793 66.8291 375.92676 68.54297 Q 374.7256Line truncated
</g>
<g>
<path fill="#0b0b0c" fill-opacity="0.7" stroke="none" d="M 270.6 112 L 270.6 110.56 L 267.63 110.56 L 267.63 109.291 L 270.024 105.42999 L 270.942 105.42999 L 268.44 109.47099 L 268.44 109.82201 L 270.6 109.82201 L 270.6 108.21999 L 271.41 108.21999 L 271.41 112 Z M 275.1 112.09 Q 274.479 112.09 274.00198 111.84701 Q 273.525 111.604 273.2595 111.167496 Q 272.994 110.730995 272.994 110.164 Q 272.994 109.54301 273.3495 109.09301 Q 273.705 108.643 274.29898 108.544 L 274.29898 108.517 Q 273.78598 108.4 273.47998 107.9995 Q 273.17398 107.599 273.17398 107.07701 Q 273.17398 106.564 273.417 106.1725 Q 273.66 105.781006 274.0965 105.5605 Q 274.533 105.34 275.1 105.34 Q 275.676 105.34 276.108 105.5605 Q 276.54 105.781006 276.783 106.1725 Q 277.026 106.564 277.026 107.07701 Q 277.026 107.599 276.72 107.9995 Q 276.414 108.4 275.901 108.517 L 275.901 108.544 Q 276.495 108.643 276.8505 109.09301 Q 277.206 109.54301 277.206 110.164 Q 277.206 110.730995 276.9405 111.167496 Q 276.675 111.604 276.20248 111.84701 Q 275.72998 112.09 275.1 112.09 Z M 275.1 108.202 Q 275.604 108.202 275.91 107.90501 Q 276.216 107.608 276.216 107.13099 Q 276.216 106.65401 275.91 106.357 Q 275.604 106.06 275.1 106.06 Q 274.60498 106.06 274.2945 106.357 Q 273.984 106.65401 273.984 107.13099 Q 273.984 107.608 274.2945 107.90501 Q 274.60498 108.202 275.1 108.202 Z M 275.1 111.361 Q 275.676 111.361 276.03598 111.023506 Q 276.396 110.68599 276.396 110.146 Q 276.396 109.597 276.03598 109.2595 Q 275.676 108.922 275.1 108.922 Q 274.524 108.922 274.164 109.2595 Q 273.804 109.597 273.804 110.146 Q 273.804 110.68599 274.164 111.023506 Q 274.524 111.361 275.1 111.361 Z M 284.073 112 L 284.073 105.42999 L 284.88303 105.42999 L 284.88303 109.09301 L 285.828 109.09301 L 287.21402 107.05 L 288.14102 107.05 L 286.548 109.408 L 288.23102 112 L 287.286 112 L 285.846 109.795 L 284.88303 109.795 L 284.88303 112 Z M 289.437 112 L 289.437 105.42999 L 290.247 105.42999 L 290.247 108.238 L 292.353 108.238 L 292.353 105.42999 L 293.163 105.42999 L 293.163 112 L 292.353 112 L 292.353 108.976 L 290.247 108.976 L 290.247 112 Z M 294.81 112 L 294.81 111.19 L 297.582 107.788 L 294.882 107.788 L 294.882 107.05 L 298.491 107.05 L 298.491 107.86 L 295.665 111.262 L 298.59 111.262 L 298.59 112 Z M 307.5 109.66 Q 307.167 109.66 306.96448 109.462 Q 306.762 109.26399 306.762 108.949005 Q 306.762 108.616 306.96448 108.409 Q 307.167 108.202 307.5 108.202 Q 307.83298 108.202 308.0355 108.409 Q 308.23798 108.616 308.23798 108.949005 Q 308.23798 109.26399 308.0355 109.462 Q 307.83298 109.66 307.5 109.66 Z M 316.374 112 L 316.374 111.253006 L 318.561 108.913 Q 319.038 108.409 319.236 108.01301 Q 319.43402 107.617 319.43402 107.23899 Q 319.43402 106.699 319.1235 106.3795 Q 318.81302 106.06 318.282 106.06 Q 317.733 106.06 317.4135 106.388504 Q 317.094 106.717 317.094 107.275 L 316.284 107.275 Q 316.311 106.384 316.851 105.862 Q 317.391 105.34 318.282 105.34 Q 319.191 105.34 319.7265 105.853 Q 320.262 106.36599 320.262 107.24801 Q 320.262 107.716 320.0055 108.238 Q 319.749 108.76 319.128 109.408 L 317.355 111.262 L 320.34302 111.262 L 320.34302 112 Z M 324.6 112 L 324.6 110.56 L 321.63 110.56 L 321.63 109.291 L 324.024 105.42999 L 324.942 105.42999 L 322.44 109.47099 L 322.44 109.82201 L 324.6 109.82201 L 324.6 108.21999 L 325.41 108.21999 L 325.41 112 Z M 327.66 109.39 L 327.66 108.670006 L 330.54 108.670006 L 330.54 109.39 Z M 334.788 112.09 Q 334.22998 112.09 333.87 111.8155 Q 333.50998 111.541 333.447 111.055 L 333.438 111.055 L 333.438 112 L 332.628 112 L 332.628 105.42999 L 333.438 105.42999 L 333.438 106.87 L 333.41998 107.995 L 333.429 107.995 Q 333.50098 107.51799 333.86548 107.23899 Q 334.22998 106.95999 334.788 106.95999 Q 335.53497 106.95999 335.97598 107.45501 Q 336.417 107.95 336.417 108.805 L 336.417 110.254 Q 336.417 111.1 335.97598 111.595 Q 335.53497 112.09 334.788 112.09 Z M 334.51797 111.38799 Q 335.03098 111.38799 335.319 111.118 Q 335.607 110.848 335.607 110.24501 L 335.607 108.805 Q 335.607 108.193 335.319 107.9275 Q 335.03098 107.661995 334.51797 107.661995 Q 334.02298 107.661995 333.7305 107.977 Q 333.438 108.29201 333.438 108.85 L 333.438 110.19999 Q 333.438 110.758 333.7305 111.07299 Q 334.02298 111.38799 334.51797 111.38799 Z M 337.96503 112 L 337.96503 111.262 L 339.72 111.262 L 339.72 107.788 L 338.14502 107.788 L 338.14502 107.05 L 340.53 107.05 L 340.53 111.262 L 342.195 111.262 L 342.195 112 Z M 340.035 106.159004 Q 339.738 106.159004 339.56702 106.006 Q 339.39603 105.853 339.39603 105.592 Q 339.39603 105.32199 339.56702 105.16449 Q 339.738 105.007 340.035 105.007 Q 340.332 105.007 340.50302 105.16449 Q 340.674 105.32199 340.674 105.592 Q 340.674 105.853 340.50302 106.006 Q 340.332 106.159004 340.035 106.159004 Z M 345.795 112 Q 345.183 112 344.81396 111.6445 Q 344.445 111.28901 344.445 110.69501 L 344.445 107.788 L 343.023 107.788 L 343.023 107.05 L 344.445 107.05 L 344.445 105.655 L 345.255 105.655 L 345.255 107.05 L 347.28003 107.05 L 347.28003 10Line truncated
</g>
<g>
<path fill="#0b0b0c" fill-opacity="0.65" stroke="none" d="M 270.4912 358 L 267.88623 358 L 267.88623 349.99707 L 270.5664 349.99707 Q 271.74805 349.99707 272.5913 350.4778 Q 273.43457 350.9585 273.88574 351.8501 Q 274.3369 352.7417 274.3369 353.9878 Q 274.3369 355.23923 273.88306 356.13623 Q 273.4292 357.0332 272.56714 357.5166 Q 271.70508 358 270.4912 358 Z M 269.11084 356.92578 L 270.4214 356.92578 Q 271.78027 356.92578 272.45703 356.16574 Q 273.1338 355.40573 273.1338 353.9878 Q 273.1338 352.5752 272.4651 351.82327 Q 271.7964 351.07126 270.48584 351.07126 L 269.11084 351.07126 Z M 277.3247 358 L 277.3247 349.99707 L 280.19287 349.99707 Q 281.1167 349.99707 281.73438 350.31934 Q 282.35205 350.6416 282.6582 351.20825 Q 282.96436 351.77487 282.96436 352.51074 Q 282.96436 353.3164 282.59912 353.90457 Q 282.2339 354.49268 281.50342 354.7666 L 283.26514 358 L 281.87402 358 L 280.25195 354.97607 Q 280.21973 354.97607 280.18213 354.97607 L 278.54932 354.97607 L 278.54932 358 Z M 278.54932 353.9287 L 280.06396 353.9287 Q 280.94482 353.9287 281.33423 353.55542 Q 281.72363 353.18216 281.72363 352.51074 Q 281.72363 351.83398 281.33154 351.4392 Q 280.93945 351.04443 280.05322 351.04443 L 278.54932 351.04443 Z M 285.33984 358 L 288.18652 349.99707 L 289.69043 349.99707 L 292.59082 358 L 291.26416 358 L 290.52832 355.88916 L 287.38086 355.88916 L 286.6665 358 Z M 287.72998 354.86862 L 290.16846 354.86862 L 289.70654 353.54733 Q 289.5454 353.06934 289.35742 352.43823 Q 289.16943 351.80713 288.9331 350.9585 Q 288.69678 351.81784 288.51147 352.4543 Q 288.32617 353.09082 288.17578 353.54733 Z M 298.21045 358.10742 Q 297.1255 358.10742 296.3037 357.6079 Q 295.48193 357.1084 295.0254 356.1899 Q 294.56885 355.27148 294.56885 354.00388 Q 294.56885 352.72562 295.03345 351.80444 Q 295.49805 350.8833 296.30908 350.38647 Q 297.12012 349.88965 298.15674 349.88965 Q 299.02148 349.88965 299.7251 350.2253 Q 300.4287 350.56104 300.88794 351.15454 Q 301.34717 351.74805 301.48145 352.52686 L 300.2246 352.52686 Q 300.0044 351.82327 299.4807 351.41504 Q 298.95703 351.00684 298.16748 351.00684 Q 297.49072 351.00684 296.95093 351.3506 Q 296.41113 351.6943 296.09692 352.36304 Q 295.7827 353.03174 295.7827 353.99854 Q 295.7827 354.96533 296.09692 355.63135 Q 296.41113 356.29736 296.95898 356.6438 Q 297.50684 356.99023 298.21582 356.99023 Q 299.1665 356.99023 299.74927 356.42896 Q 300.33203 355.86768 300.35352 354.90625 L 298.39307 354.90625 L 298.39307 353.89648 L 301.55127 353.89648 L 301.55127 354.81494 Q 301.55127 355.8247 301.1189 356.56323 Q 300.68652 357.30173 299.93457 357.7046 Q 299.18262 358.10742 298.21045 358.10742 Z M 312.33936 358.10742 Q 311.30273 358.10742 310.48364 357.61328 Q 309.66455 357.11914 309.19458 356.20068 Q 308.7246 355.2822 308.7246 354.00388 Q 308.7246 352.7202 309.19458 351.7991 Q 309.66455 350.87793 310.48364 350.3838 Q 311.30273 349.88965 312.33936 349.88965 Q 313.37598 349.88965 314.1897 350.3838 Q 315.00342 350.87793 315.4734 351.7991 Q 315.94336 352.7202 315.94336 354.00388 Q 315.94336 355.2822 315.4734 356.20337 Q 315.00342 357.1245 314.1897 357.616 Q 313.37598 358.10742 312.33936 358.10742 Z M 312.33936 356.99023 Q 313.02148 356.99023 313.56128 356.6465 Q 314.10107 356.30273 314.41528 355.63672 Q 314.7295 354.9707 314.7295 354.00388 Q 314.7295 353.03174 314.41528 352.36304 Q 314.10107 351.6943 313.56128 351.3506 Q 313.02148 351.00684 312.33936 351.00684 Q 311.65186 351.00684 311.10938 351.3506 Q 310.5669 351.6943 310.2527 352.36304 Q 309.93848 353.03174 309.93848 354.00388 Q 309.93848 354.9707 310.2527 355.63672 Q 310.5669 356.30273 311.10938 356.6465 Q 311.65186 356.99023 312.33936 356.99023 Z M 318.93652 358 L 318.93652 349.99707 L 320.34912 349.99707 L 323.3623 354.76123 Q 323.5503 355.062 323.80273 355.51053 Q 324.05518 355.95898 324.32373 356.5337 Q 324.28076 355.9536 324.26196 355.42453 Q 324.24316 354.8955 324.24316 354.55176 L 324.24316 349.99707 L 325.4839 349.99707 L 325.4839 358 L 324.06592 358 L 321.36426 353.74072 Q 321.16553 353.4238 320.9856 353.1123 Q 320.80566 352.80078 320.59082 352.41135 Q 320.37598 352.02194 320.08594 351.48483 Q 320.12354 352.23682 320.1504 352.82764 Q 320.17725 353.41846 320.17725 353.72998 L 320.17725 358 Z M 328.35352 351.0498 L 328.35352 349.99707 L 334.56787 349.99707 L 334.56787 351.0498 L 332.08105 351.0498 L 332.08105 358 L 330.85107 358 L 330.85107 351.0498 Z M 340.43994 358.10742 Q 339.40332 358.10742 338.58423 357.61328 Q 337.76514 357.11914 337.29517 356.20068 Q 336.8252 355.2822 336.8252 354.00388 Q 336.8252 352.7202 337.29517 351.7991 Q 337.76514 350.87793 338.58423 350.3838 Q 339.40332 349.88965 340.43994 349.88965 Q 341.47656 349.88965 342.29028 350.3838 Q 343.104 350.87793 343.57397 351.7991 Q 344.0439 352.7202 344.0439 354.00388 Q 344.0439 355.2822 343.57397 356.20337 Q 343.104 357.1245 342.29028 357.616 Q 341.47656 358.10742 340.43994 358.10742 Z M 340.43994 356.99023 Q 341.12207 356.99023 341.66187 356.6465 Q 342.20166 356.30273 342.51587 35Line truncated
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.
Binary file not shown.
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""Regenerate background.tiff and dmg-layout.DS_Store for the macOS DMG window (see README.md here).
Needs resvg on PATH and:
pip install 'pillow>=12' ds_store==1.3.3 mac_alias==2.2.3
Runs on any OS, no Mac needed. Both outputs come from this script alone, so they carry nothing
from the machine that made them: no local paths, user or disk names, dates or volume UUIDs.
python3 packaging/macos/dmg/generate.py
"""
import datetime
import os
import shutil
import subprocess
import sys
import tempfile
from ds_store import DSStore
from mac_alias import Alias, TargetInfo, VolumeInfo
from PIL import Image, ImageCms
HERE = os.path.dirname(os.path.abspath(__file__))
# Keep in sync with package.sh (volume name) and README.md (layout).
VOLUME = "SoundCraft"
WIDTH, HEIGHT = 660, 400 # window content, pt; the background's 1x size
TITLE_BAR = 32
ICON_SIZE = 128
PALETTE = 16 # colours in background.tiff
ICONS = {"SoundCraft.app": (326, 205), "Applications": (574, 205)}
# A fixed date for the alias and the colour profile. 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 render(svg, width, out):
# The SVG's text is already outlined and no fonts are loaded, so every machine draws the
# same pixels.
subprocess.run(
["resvg", "--skip-system-fonts", "-w", str(width), svg, out],
check=True,
)
with Image.open(out) as im:
return im.convert("RGB")
def quantize(im):
# No dithering: dither noise compresses badly and the artwork has flat fills.
return im.quantize(PALETTE, method=Image.Quantize.FASTOCTREE, dither=Image.Dither.NONE)
def srgb_profile():
# LittleCMS's built-in sRGB ("No copyright, use freely"), with its creation date fixed.
icc = bytearray(ImageCms.ImageCmsProfile(ImageCms.createProfile("sRGB")).tobytes())
d = FIXED_DATE
icc[24:36] = b"".join(v.to_bytes(2, "big") for v in (d.year, d.month, d.day, 0, 0, 0))
if icc[84:100] != bytes(16):
sys.exit("error: the sRGB profile has an ID; recompute it after fixing the date")
return bytes(icc)
def write_tiff(svg, out):
"""1x (72 dpi) and 2x (144 dpi) in one TIFF, as `tiffutil -cathidpicheck` writes them."""
with tempfile.TemporaryDirectory() as tmp:
one, two = (
quantize(render(svg, WIDTH * s, os.path.join(tmp, f"{s}x.png"))) for s in (1, 2)
)
two.encoderinfo = {"dpi": (144, 144)} # the rest as the first page
# Opaque 16-colour palette, Deflate: the artwork is three inks plus antialiasing, and a palette
# keeps the file under ~100 KB (repository assets must stay small). As PhotoCraft's.
one.save(
out,
"TIFF",
save_all=True,
append_images=[two],
compression="tiff_adobe_deflate", # brand-ok: Pillow's name for TIFF compression 8 (Deflate)
dpi=(72, 72),
icc_profile=srgb_profile(),
)
with Image.open(out) as im:
im.seek(1)
if im.info.get("dpi") != (144, 144):
sys.exit("error: the 2x page isn't 144 dpi (needs Pillow 12 or later)")
strips = []
for page in (0, 1):
im.seek(page)
strips += zip(im.tag_v2[273], im.tag_v2[279]) # StripOffsets, StripByteCounts
# TIFF keeps directories and tag data on even offsets. When a strip ends on an odd one, libtiff
# leaves the padding byte after it uninitialised, so the file changed from run to run with
# identical pixels. Zero it: nothing refers to that byte.
starts = {o for o, _ in strips}
with open(out, "r+b") as f:
for o, n in strips:
if (o + n) % 2 and o + n not in starts:
f.seek(o + n)
f.write(bytes(1))
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():
if not shutil.which("resvg"):
sys.exit("error: resvg not found (brew install resvg / cargo install resvg --locked)")
write_tiff(os.path.join(HERE, "background.svg"), os.path.join(HERE, "background.tiff"))
write_ds_store(os.path.join(HERE, "dmg-layout.DS_Store"))
if __name__ == "__main__":
main()
+6 -1
View File
@@ -129,10 +129,15 @@ STAGE="$WORK/dmg"
mkdir -p "$STAGE"
ditto "$APP" "$STAGE/SoundCraft.app"
ln -s /Applications "$STAGE/Applications"
# Finder window layout: background, icon size and positions (packaging/macos/dmg/README.md).
mkdir -p "$STAGE/.background"
cp "$HERE/dmg/background.tiff" "$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 "SoundCraft $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 "SoundCraft" -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"