Build Mac App (DMG) #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Mac App (DMG) | |
| # Trigger manually from the Actions tab on GitHub. | |
| # Requires two repository secrets: CEREBRAS_KEY and GROQ_KEY. | |
| on: | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| runs-on: macos-latest | |
| timeout-minutes: 60 | |
| steps: | |
| # ── 1. Checkout ───────────────────────────────────────────────────────── | |
| - uses: actions/checkout@v4 | |
| # ── 2. Python 3.11 ────────────────────────────────────────────────────── | |
| - name: Set up Python 3.11 | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| # ── 3. Bake API keys into _bundled_keys.py ─────────────────────────────── | |
| - name: Write bundled API keys | |
| env: | |
| CEREBRAS_KEY: ${{ secrets.CEREBRAS_KEY }} | |
| GROQ_KEY: ${{ secrets.GROQ_KEY }} | |
| run: | | |
| printf 'CEREBRAS = "%s"\nGROQ = "%s"\n' \ | |
| "$CEREBRAS_KEY" "$GROQ_KEY" > _bundled_keys.py | |
| # ── 4. Install dependencies ────────────────────────────────────────────── | |
| - name: Install Python dependencies | |
| run: | | |
| pip install --upgrade pip | |
| pip install -r requirements_mac.txt | |
| pip install mouse pyinstaller | |
| # ── 5. Download Whisper base model (~150 MB) ───────────────────────────── | |
| - name: Download Whisper base model | |
| run: | | |
| python - <<'EOF' | |
| from huggingface_hub import snapshot_download | |
| import os | |
| os.makedirs('models/base', exist_ok=True) | |
| snapshot_download( | |
| 'Systran/faster-whisper-base', | |
| local_dir='models/base', | |
| local_dir_use_symlinks=False, | |
| ) | |
| print('Whisper base model ready.') | |
| EOF | |
| # ── 6. Generate app icon (ICNS) ────────────────────────────────────────── | |
| - name: Generate app icon | |
| run: | | |
| python - <<'EOF' | |
| import os | |
| from PIL import Image, ImageDraw, ImageFilter | |
| S = 8; B = 64 * S | |
| def _hex(h): | |
| h = h.lstrip('#') | |
| return tuple(int(h[i:i+2], 16) for i in (0, 2, 4)) | |
| def _grad_mask(mask, c1, c2): | |
| r1,g1,b1 = _hex(c1); r2,g2,b2 = _hex(c2) | |
| grad = Image.new('RGBA', (B, B)) | |
| dg = ImageDraw.Draw(grad) | |
| for y in range(B): | |
| t = y / (B - 1) | |
| dg.line([(0,y),(B,y)], fill=( | |
| int(r1+(r2-r1)*t), int(g1+(g2-g1)*t), int(b1+(b2-b1)*t), 255)) | |
| out = Image.new('RGBA', (B, B), (0,0,0,0)) | |
| out.paste(grad, mask=mask.split()[0]) | |
| return out | |
| base = Image.new('RGBA', (B, B), (0,0,0,0)) | |
| d = ImageDraw.Draw(base) | |
| d.rounded_rectangle([0, 0, B-1, B-1], radius=13*S, fill='#7c3aed') | |
| d.rounded_rectangle([3*S, 3*S, B-1-3*S, B-1-3*S], radius=11*S, fill='#080f1a') | |
| BOLT = [(x*S, y*S) for x,y in [(42,4),(10,34),(28,34),(22,60),(52,26),(36,26)]] | |
| bolt_mask = Image.new('RGBA', (B, B), (0,0,0,0)) | |
| ImageDraw.Draw(bolt_mask).polygon(BOLT, fill='white') | |
| glow = _grad_mask(bolt_mask.filter(ImageFilter.GaussianBlur(12)), '#7dd3fc', '#1e40af') | |
| base = Image.alpha_composite(base, glow) | |
| base = Image.alpha_composite(base, _grad_mask(bolt_mask, '#bae6fd', '#0f2a6e')) | |
| icon = base.resize((1024, 1024), Image.LANCZOS) | |
| os.makedirs('Hotkeys.iconset', exist_ok=True) | |
| for sz in [16, 32, 64, 128, 256, 512]: | |
| icon.resize((sz, sz ), Image.LANCZOS).save(f'Hotkeys.iconset/icon_{sz}x{sz}.png') | |
| icon.resize((sz*2, sz*2 ), Image.LANCZOS).save(f'Hotkeys.iconset/icon_{sz}x{sz}@2x.png') | |
| icon.save('Hotkeys.iconset/icon_1024x1024.png') | |
| print('Iconset generated.') | |
| EOF | |
| iconutil -c icns Hotkeys.iconset -o icon.icns | |
| echo "Icon size: $(du -sh icon.icns | cut -f1)" | |
| # ── 7. Build .app with PyInstaller ─────────────────────────────────────── | |
| - name: Build Mac .app | |
| run: pyinstaller hotkeys_mac.spec --noconfirm --clean | |
| # ── 8. Assemble DMG source folder ──────────────────────────────────────── | |
| - name: Prepare DMG contents | |
| run: | | |
| mkdir -p dmg_src | |
| cp -r dist/Hotkeys.app dmg_src/ | |
| cp open_hotkeys.command dmg_src/"Open Hotkeys.command" | |
| chmod +x "dmg_src/Open Hotkeys.command" | |
| # ── 9. Package as DMG ──────────────────────────────────────────────────── | |
| - name: Create DMG | |
| run: | | |
| brew install create-dmg | |
| create-dmg \ | |
| --volname "Hotkeys" \ | |
| --volicon "icon.icns" \ | |
| --window-pos 200 120 \ | |
| --window-size 620 400 \ | |
| --icon-size 100 \ | |
| --icon "Hotkeys.app" 130 200 \ | |
| --icon "Open Hotkeys.command" 340 200 \ | |
| --hide-extension "Hotkeys.app" \ | |
| --app-drop-link 530 200 \ | |
| "Hotkeys-mac.dmg" \ | |
| "dmg_src/" | |
| # ── 9. Upload DMG as workflow artifact ─────────────────────────────────── | |
| - name: Upload DMG | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Hotkeys-mac | |
| path: Hotkeys-mac.dmg | |
| retention-days: 90 |