From ee6c34e065bfd1dd80a4be8d7f5d3f64d2f6b53a Mon Sep 17 00:00:00 2001 From: delewer <108271242+IMDelewer@users.noreply.github.com> Date: Tue, 23 Jun 2026 13:36:20 +0300 Subject: [PATCH] ci: add better view on macos installation (#1019) --- .github/workflows/build.yml | 66 ++++++------- macos.py | 65 ++++++++----- packaging/dmg/assets/background-light.png | Bin 0 -> 4696 bytes packaging/dmg/assets/background-light@2x.png | Bin 0 -> 10680 bytes packaging/dmg/build_dmg.sh | 93 +++++++++++++++++++ 5 files changed, 166 insertions(+), 58 deletions(-) create mode 100644 packaging/dmg/assets/background-light.png create mode 100644 packaging/dmg/assets/background-light@2x.png create mode 100755 packaging/dmg/build_dmg.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8db960f..9fac5a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -272,30 +272,10 @@ jobs: python3.12 -m pip install . python3.12 -m pip install pyinstaller==6.13.0 - - name: Create macOS icon from ICO + - name: Create macOS icon run: | set -euo pipefail - python3.12 - <<'PY' - from PIL import Image - - image = Image.open('icon.ico') - image = image.resize((1024, 1024), Image.LANCZOS) - image.save('icon_1024.png', 'PNG') - PY - - mkdir -p icon.iconset - sips -z 16 16 icon_1024.png --out icon.iconset/icon_16x16.png - sips -z 32 32 icon_1024.png --out icon.iconset/icon_16x16@2x.png - sips -z 32 32 icon_1024.png --out icon.iconset/icon_32x32.png - sips -z 64 64 icon_1024.png --out icon.iconset/icon_32x32@2x.png - sips -z 128 128 icon_1024.png --out icon.iconset/icon_128x128.png - sips -z 256 256 icon_1024.png --out icon.iconset/icon_128x128@2x.png - sips -z 256 256 icon_1024.png --out icon.iconset/icon_256x256.png - sips -z 512 512 icon_1024.png --out icon.iconset/icon_256x256@2x.png - sips -z 512 512 icon_1024.png --out icon.iconset/icon_512x512.png - sips -z 1024 1024 icon_1024.png --out icon.iconset/icon_512x512@2x.png - iconutil -c icns icon.iconset -o icon.icns - rm -rf icon.iconset icon_1024.png + python3.12 macos.py --render-app-icon icon.icns - name: Build app with PyInstaller run: python3.12 -m PyInstaller packaging/macos.spec --noconfirm @@ -303,6 +283,11 @@ jobs: - name: Validate universal2 app bundle run: | set -euo pipefail + ICON_FILE="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' \ + 'dist/TG WS Proxy.app/Contents/Info.plist')" + test -n "$ICON_FILE" + test -f "dist/TG WS Proxy.app/Contents/Resources/$ICON_FILE" + found=0 while IFS= read -r -d '' file; do if file "$file" | grep -q "Mach-O"; then @@ -326,22 +311,31 @@ jobs: - name: Create DMG run: | set -euo pipefail - APP_NAME="TG WS Proxy" - DMG_TEMP="dist/dmg_temp" - - rm -rf "$DMG_TEMP" - mkdir -p "$DMG_TEMP" - cp -R "dist/${APP_NAME}.app" "$DMG_TEMP/" - ln -s /Applications "$DMG_TEMP/Applications" - - hdiutil create \ - -volname "$APP_NAME" \ - -srcfolder "$DMG_TEMP" \ - -ov \ - -format UDZO \ + packaging/dmg/build_dmg.sh \ + "dist/TG WS Proxy.app" \ + "TG WS Proxy" \ "dist/TgWsProxy_macos_universal.dmg" - rm -rf "$DMG_TEMP" + - name: Validate DMG + run: | + set -euo pipefail + for DMG in "dist/TgWsProxy_macos_universal.dmg"; do + MOUNT_DIR="$(mktemp -d)" + DEVICE="$(hdiutil attach \ + -readonly \ + -nobrowse \ + -mountpoint "$MOUNT_DIR" \ + "$DMG" \ + | awk '/^\/dev\// { print $1; exit }')" + + test -d "$MOUNT_DIR/TG WS Proxy.app" + test -L "$MOUNT_DIR/Applications" + test "$(readlink "$MOUNT_DIR/Applications")" = "/Applications" + test -f "$MOUNT_DIR/.background/background.tiff" + test -f "$MOUNT_DIR/.DS_Store" + hdiutil detach "$DEVICE" + rmdir "$MOUNT_DIR" + done - name: Upload artifact uses: actions/upload-artifact@v7 diff --git a/macos.py b/macos.py index d26ff7d..45950cb 100644 --- a/macos.py +++ b/macos.py @@ -9,16 +9,53 @@ import webbrowser from pathlib import Path from typing import Optional -try: - import rumps -except ImportError: - rumps = None - try: from PIL import Image, ImageDraw, ImageFont except ImportError: Image = ImageDraw = ImageFont = None + +def render_app_icon(size: int): + scale = size / 1024 + image = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + draw = ImageDraw.Draw(image) + outer = tuple(round(value * scale) for value in (92, 92, 932, 932)) + draw.ellipse(outer, fill=(0, 151, 221, 255)) + try: + font = ImageFont.truetype( + "/System/Library/Fonts/Helvetica.ttc", + round(430 * scale), + ) + except Exception: + font = ImageFont.load_default() + box = draw.textbbox((0, 0), "T", font=font) + width = box[2] - box[0] + height = box[3] - box[1] + draw.text( + ( + (size - width) / 2 - box[0], + (size - height) / 2 - box[1] - round(10 * scale), + ), + "T", + font=font, + fill=(255, 255, 255, 255), + ) + return image + + +if __name__ == "__main__" and len(sys.argv) > 1 and sys.argv[1] == "--render-app-icon": + if Image is None: + raise SystemExit("Pillow is required to render the macOS app icon") + output_path = sys.argv[2] if len(sys.argv) > 2 else "icon.icns" + render_app_icon(1024).save(output_path, format="ICNS") + raise SystemExit(0) + + +try: + import rumps +except ImportError: + rumps = None + try: import pyperclip except ImportError: @@ -144,26 +181,10 @@ def _ask_cfworker_domain(default: str) -> Optional[str]: def _make_menubar_icon(size: int = 44): if Image is None: return None - img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) - draw = ImageDraw.Draw(img) - margin = size // 11 - draw.ellipse([margin, margin, size - margin, size - margin], fill=(0, 0, 0, 255)) - try: - font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", size=int(size * 0.55)) - except Exception: - font = ImageFont.load_default() - bbox = draw.textbbox((0, 0), "T", font=font) - tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1] - draw.text( - ((size - tw) // 2 - bbox[0], (size - th) // 2 - bbox[1]), - "T", fill=(255, 255, 255, 255), font=font, - ) - return img + return render_app_icon(size) def _ensure_menubar_icon() -> None: - if MENUBAR_ICON_PATH.exists(): - return ensure_dirs() img = _make_menubar_icon(44) if img: diff --git a/packaging/dmg/assets/background-light.png b/packaging/dmg/assets/background-light.png new file mode 100644 index 0000000000000000000000000000000000000000..f4ab0cdb677d6fa648e7768a3da81e25e6464af8 GIT binary patch literal 4696 zcmcIoX;_ol75+dgPAQ-RI*te^D7b*&utaud5E#lfiXj3*5JWaH$d-f{0y>I{$^e2a zA&3i1gG5;b6B2^5iEJX9NJ!Yi7D6UK62g{#wAI$)*v|CnzwbHEz305|Ip?15zW3S> z&USKt-2O)Z0OahyKXMEJq;CU&)U%D!pe6FaKqUZtsb+uV+v6xaw;%J9w?DR;AJJ!; z?G^rQ)6Vp-zn4zlS9D`*fOcSjN8a%9MEgB)&w+=>@8w*-v;R(V;-8a)itqWc)z$B8 z($TNmmEIlPnUzWGP_MNLBj(5Xp3A=3i$t~cZ1TkiZZ}zuiNJrsm^YQJMrjKf!0L+b;EfA7~GHjDHLmA*_(IV0e~Iy z2R_BH9=!_pKa*SI_9H_pNXq9olG>LNldmfU9Nw`eVXn{Ma)58~%)b@l*VFO7R3EMQ z-OQBMBy0=~)_e`j&v(N|`FY0CZyNV!Ucg+Gk^(=E>pmyZ6MJR^a6|Cd& z%!lLZl0)|G%HgQud$RX%^DA=qTr&QS8t;7DzZX?%GBNLGd7;MeWVc>PLrtMbOXM52NH2281Gk2z`rzT^*swX=G9f}aX; zJDW-^m&s#Wtdu6?cS4aJbUdUS`8Q%C(X8`09aap`9p+3c3l z6@>>uDPwh{uZv3BoE+J0a;tdb_6=UAkEfgNIGG^uXKu zd-S_sBcv4(K1M%t9%y^kEZ0@!E=?=zo{YG81Z64N9D1|&4k3&ImbS)vmVlaR)L-!t7f4p}gsuZzts2_K9MmIfy!%7#!Yby} zCXHXr+O#yazye1K#64bo;ePw=!{x8Pu%*V6A<`}#CQcQheX-NmOD$1dY`XBpeOqP! z!LkIw>Re_QFb z#{A8Pi?2ddiinflfAI$yv%z%|q(@OV_4#ugZlql| zmdkR(3nr6odzpfG`B{Brs8zyS(bKaAn(vTbim%T|Rs`-`w!LX7OoDaE4RjD&(0#659VN2b0$cPk14 zV#p!|vSi^($jaXamVu@NTXQ{s!Y}Axt#;%H!BjR3KA|=Vt2jl2&=N zJ17g2Y5eU~h@NZ$!%kia{%-!JA&;`a*Ks*`@`=yF#YG?Ma}@=fuj)s{_1>qIlL`l` zmRh3}2v3JD7P**Xt;nU?4joeomM9@(O8{b?fo)KYm}{l!fO~GGLG(-$7}m2JIt+5W zr-gZ^{QRyknx);C)l*-GBe0X1rA*Xdj)?+Cp)$e6(T< z(cSF|WBnec)!D`ABYlt&i^ZixiGL03)(%y|N=%%AQ>k{J;Wsmr9dL1Pr3Ie|!&Cea zrOOGe3%j-X8>9<~``g}FE09xnf8A?NnZ&ad`jOeYAf=ZwA)7ES`|%miM$CKEB473g zmSq(sv6f(8Y2!i{(NBbGF*34)TjYbK)q~4DWMwGj7bKYx2QM_`wFI zTBrO*<<={QS7>S-#I!|Ku+XBTx+gkY#bx0<<#0&B6-MEo$eA>7)&^QncOiP`<`wPa z71I1e7J`bsyuA9#L*bMM<|7u%Wv)mUIkLHY{iD;Pa*-6F0cPPROj9bj_^F4b4${bw zz4BZk*Xz8Hqw0aNTNIX!U18`Qa7V8zPGF!3PR>T)@IDO=?o!z6563zO}7lvmKde6SiZ%Nn|{uqvFcvIKF-5#># za=1PW{I8oFg+i@qbz>4ShZPgq@#3;9IQeH27(7&8E>}66_w|89hJ#C{_38yMDCI_y z%Yz__q$@}U%pot}68XsL9VveTmAO>ks3k8i@9gL-6h?wqLI2(=Eix~2dpJ*Ta<-#&#+ zK4*Q1dszm^+1nI5I=urI^6j8x9dNa-fAn*T@sk?igRJ;HqH2v2`dBHt?iM)0rGc=+ zzfr5Ly8YdHY7I*z5QG#E_D>ajy|&8uU!={447`_3zi#+H|Cg$4^$7w$2CDlLtHN_l7JehJk6Yjay)|w>ZkkMM4gesZ@SmR( ol$h<(K=G&P{R7AD;w8wvUhV45Z8}!qixFUN<9vklozKO;0vIEOp#T5? literal 0 HcmV?d00001 diff --git a/packaging/dmg/assets/background-light@2x.png b/packaging/dmg/assets/background-light@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9012ddfcf7ab28f4a73396724dc4af281f98a512 GIT binary patch literal 10680 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 a>e3O-D!vN({=Wf^uN6oD literal 0 HcmV?d00001 diff --git a/packaging/dmg/build_dmg.sh b/packaging/dmg/build_dmg.sh new file mode 100755 index 0000000..f070d53 --- /dev/null +++ b/packaging/dmg/build_dmg.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP_PATH="${1:?Usage: build_dmg.sh [assets_dir]}" +VOL_NAME="${2:?missing volume name}" +OUT_DMG="${3:?missing output dmg path}" +ASSETS_DIR="${4:-$(cd "$(dirname "${BASH_SOURCE[0]}")/assets" && pwd)}" + +WIN_W=660 +WIN_H=440 +ICON_SIZE=128 +APP_X=145 +APPS_X=515 +ICON_Y=220 + +APP_NAME="$(basename "$APP_PATH")" +WORK="$(mktemp -d)" +STAGE="$WORK/stage" +RW_DMG="$WORK/rw.dmg" +MOUNT="/Volumes/$VOL_NAME" +DEVICE="" + +cleanup() { + if [ -n "$DEVICE" ]; then + hdiutil detach "$DEVICE" -force >/dev/null 2>&1 || true + fi + rm -rf "$WORK" +} +trap cleanup EXIT + +mkdir -p "$STAGE/.background" +cp -R "$APP_PATH" "$STAGE/" +ln -s /Applications "$STAGE/Applications" + +tiffutil -cathidpicheck \ + "$ASSETS_DIR/background-light.png" \ + "$ASSETS_DIR/background-light@2x.png" \ + -out "$STAGE/.background/background.tiff" + +hdiutil create \ + -volname "$VOL_NAME" \ + -srcfolder "$STAGE" \ + -fs HFS+ \ + -format UDRW \ + -ov \ + "$RW_DMG" + +DEVICE="$(hdiutil attach \ + -readwrite \ + -noverify \ + -noautoopen \ + -mountpoint "$MOUNT" \ + "$RW_DMG" \ + | awk '/^\/dev\// { print $1; exit }')" +test -n "$DEVICE" +test -d "$MOUNT/$APP_NAME" + +sleep 2 + +osascript </dev/null || true +sync + +hdiutil detach "$DEVICE" -force >/dev/null 2>&1 \ + || { sleep 3; hdiutil detach "$DEVICE" -force; } +DEVICE="" + +rm -f "$OUT_DMG" +hdiutil convert "$RW_DMG" -format UDZO -imagekey zlib-level=9 -ov -o "$OUT_DMG" + +echo "Created $OUT_DMG"