diff --git a/branding/icons/BearBrowser.icns b/branding/icons/BearBrowser.icns new file mode 100644 index 0000000..67d864a Binary files /dev/null and b/branding/icons/BearBrowser.icns differ diff --git a/branding/icons/bear-1024.png b/branding/icons/bear-1024.png new file mode 100644 index 0000000..ee9cf53 Binary files /dev/null and b/branding/icons/bear-1024.png differ diff --git a/branding/icons/bear-128.png b/branding/icons/bear-128.png new file mode 100644 index 0000000..72553d5 Binary files /dev/null and b/branding/icons/bear-128.png differ diff --git a/branding/icons/bear-16.png b/branding/icons/bear-16.png new file mode 100644 index 0000000..54f5571 Binary files /dev/null and b/branding/icons/bear-16.png differ diff --git a/branding/icons/bear-256.png b/branding/icons/bear-256.png new file mode 100644 index 0000000..623aa4c Binary files /dev/null and b/branding/icons/bear-256.png differ diff --git a/branding/icons/bear-32.png b/branding/icons/bear-32.png new file mode 100644 index 0000000..b65ebd1 Binary files /dev/null and b/branding/icons/bear-32.png differ diff --git a/branding/icons/bear-48.png b/branding/icons/bear-48.png new file mode 100644 index 0000000..3559fdd Binary files /dev/null and b/branding/icons/bear-48.png differ diff --git a/branding/icons/bear-512.png b/branding/icons/bear-512.png new file mode 100644 index 0000000..5aee39f Binary files /dev/null and b/branding/icons/bear-512.png differ diff --git a/branding/icons/bear-64.png b/branding/icons/bear-64.png new file mode 100644 index 0000000..c828f46 Binary files /dev/null and b/branding/icons/bear-64.png differ diff --git a/branding/icons/bear.ico b/branding/icons/bear.ico new file mode 100644 index 0000000..8fba641 Binary files /dev/null and b/branding/icons/bear.ico differ diff --git a/scripts/apply-bearbrowser-branding.sh b/scripts/apply-bearbrowser-branding.sh index 9e76c13..02c77c4 100644 --- a/scripts/apply-bearbrowser-branding.sh +++ b/scripts/apply-bearbrowser-branding.sh @@ -132,6 +132,19 @@ while IFS= read -r -d '' icon_dir; do [ "$icon_dirs_seen" -ge 50 ] && break done < <(find "$workspace" -type d \( -iname '*icon*' -o -iname '*branding*' -o -iname '*browser*' \) -print0) +# Stage the pre-generated raster icon set (branding/icons/, built by +# scripts/generate-brand-icons.sh) into the workspace so `make dir` +# (scripts/bearbrowser-patches.py) can overwrite the librewolf-derived +# browser/branding/bearbrowser rasters (.icns/.ico/sized .png) with the bear. +# SVG-swapping alone (above) never changed the packaged app icons — the OSes +# consume the rasters compiled into the branding dir, not the SVGs. +if [ -d "$repo_root/branding/icons" ] && [ "$dry_run" != "true" ]; then + mkdir -p "$workspace/branding-icons" + cp "$repo_root/branding/icons/"* "$workspace/branding-icons/" + echo "branding-icons: staged $(ls "$repo_root/branding/icons" | wc -l | tr -d ' ') raster assets -> workspace/branding-icons/" + changed=$((changed + 1)) +fi + # Ensure a BearBrowser app id marker exists for downstream package steps. if [ "$dry_run" != "true" ]; then mkdir -p "$workspace/.bearbrowser" diff --git a/scripts/bearbrowser-package-source-build.sh b/scripts/bearbrowser-package-source-build.sh index ee514bf..27440a7 100755 --- a/scripts/bearbrowser-package-source-build.sh +++ b/scripts/bearbrowser-package-source-build.sh @@ -162,7 +162,18 @@ PY # ── Step 3: Install BearBrowser icon ───────────────────────────────────────── echo "[3/6] Installing BearBrowser icon..." +# Info.plist sets CFBundleIconFile=BearBrowser, which macOS resolves to +# Contents/Resources/BearBrowser.icns — an SVG is NOT a valid bundle icon, so +# the .icns (generated by scripts/generate-brand-icons.sh) is the load-bearing +# file here. The SVG is kept alongside for in-app/about uses. +icon_icns="$repo_root/branding/icons/BearBrowser.icns" icon_svg="$repo_root/branding/bearbrowser.svg" +if [ -f "$icon_icns" ]; then + cp "$icon_icns" "$out_app/Contents/Resources/BearBrowser.icns" + echo " BearBrowser.icns installed (CFBundleIconFile target)" +else + echo " WARNING: $icon_icns not found — app will show a generic icon" +fi if [ -f "$icon_svg" ]; then cp "$icon_svg" "$out_app/Contents/Resources/BearBrowser.svg" echo " BearBrowser.svg installed" diff --git a/scripts/bearbrowser-patches.py b/scripts/bearbrowser-patches.py index d00974d..836b2e6 100755 --- a/scripts/bearbrowser-patches.py +++ b/scripts/bearbrowser-patches.py @@ -133,6 +133,47 @@ def _strip_l10n_base(_path): _shutil.copytree(str(_lw_brand), str(_bb_brand)) print(f"Created {_bb_brand} from {_lw_brand}") + # ── Bear icons: overwrite the librewolf-derived rasters ────────────────── + # The branding dir copied above still carries LibreWolf's raster icons — + # the files the OSes actually display (.icns compiled into the .app, .ico + # compiled into firefox.exe by rc, sized .png shipped for Linux). Overwrite + # them with the bear set staged by apply-bearbrowser-branding.sh + # (workspace/branding-icons/, generated from branding/bearbrowser.svg by + # scripts/generate-brand-icons.sh). Square .png slots are matched by their + # real IHDR pixel size (exact match only — never ship a wrong-size png); + # non-square art (wordmarks) is left alone. + _icons_src = Path("../branding-icons") + if _icons_src.is_dir() and _bb_brand.exists(): + def _png_size(p): + try: + with open(p, "rb") as f: + h = f.read(24) + if h[:8] != b"\x89PNG\r\n\x1a\n": + return None + return (int.from_bytes(h[16:20], "big"), int.from_bytes(h[20:24], "big")) + except OSError: + return None + _icns = _icons_src / "BearBrowser.icns" + _ico = _icons_src / "bear.ico" + _replaced = 0 + for _f in sorted(_bb_brand.rglob("*")): + if not _f.is_file(): + continue + _suf = _f.suffix.lower() + if _suf == ".icns" and _icns.exists(): + _shutil.copy(str(_icns), str(_f)); _replaced += 1 + elif _suf == ".ico" and _ico.exists(): + _shutil.copy(str(_ico), str(_f)); _replaced += 1 + elif _suf == ".png": + _sz = _png_size(_f) + if _sz and _sz[0] == _sz[1]: + _bear_png = _icons_src / f"bear-{_sz[0]}.png" + if _bear_png.exists(): + _shutil.copy(str(_bear_png), str(_f)); _replaced += 1 + print(f"-> Bear icons: replaced {_replaced} raster(s) in {_bb_brand}") + else: + print("note: ../branding-icons not staged — keeping librewolf rasters") + # copy the right search-config.json file exec('cp -v ../assets/search-config.json services/settings/dumps/main/search-config.json') diff --git a/scripts/generate-brand-icons.sh b/scripts/generate-brand-icons.sh new file mode 100755 index 0000000..2fce327 --- /dev/null +++ b/scripts/generate-brand-icons.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# generate-brand-icons.sh — rasterize branding/bearbrowser.svg into the platform +# icon formats the OSes actually use, committed under branding/icons/: +# +# bear-{16,32,48,64,128,256,512,1024}.png (Linux window/desktop icons + sources) +# BearBrowser.icns (macOS app bundle icon) +# bear.ico (Windows exe/installer icon, multi-size) +# +# Run manually on macOS after changing the SVG (uses qlmanage + iconutil + sips + +# Pillow — all present on a stock dev Mac). CI never runs this; the build lanes +# consume the committed assets (scripts/bearbrowser-patches.py overwrites the +# librewolf-derived browser/branding/bearbrowser rasters with these). +# +# Gotcha encoded: qlmanage renders SVG at high quality but bakes an OPAQUE WHITE +# background — the rounded-rect corners must be re-masked to transparent or the +# icon ships with white corners. The mask radius mirrors the SVG (rx=112 at 512). +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +svg="$repo_root/branding/bearbrowser.svg" +out="$repo_root/branding/icons" +[ -f "$svg" ] || { echo "ERROR: $svg missing" >&2; exit 1; } +command -v qlmanage >/dev/null || { echo "ERROR: qlmanage (macOS) required" >&2; exit 1; } +command -v iconutil >/dev/null || { echo "ERROR: iconutil (macOS) required" >&2; exit 1; } +python3 -c 'import PIL' 2>/dev/null || { echo "ERROR: Pillow required (pip install Pillow)" >&2; exit 1; } + +mkdir -p "$out" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +echo "[1/4] Rendering SVG at 1024px (QuickLook)..." +qlmanage -t -s 1024 -o "$tmp" "$svg" >/dev/null +base="$tmp/$(basename "$svg").png" +[ -f "$base" ] || { echo "ERROR: qlmanage produced no PNG" >&2; exit 1; } + +echo "[2/4] Masking rounded corners to transparent + generating PNG sizes..." +python3 - "$base" "$out" <<'PY' +import sys +from PIL import Image, ImageDraw + +base_path, out = sys.argv[1], sys.argv[2] +im = Image.open(base_path).convert("RGBA") +w = im.width # 1024 +radius = round(112 / 512 * w) # SVG: rx=112 on a 512 viewBox + +mask = Image.new("L", (w, w), 0) +ImageDraw.Draw(mask).rounded_rectangle([0, 0, w - 1, w - 1], radius=radius, fill=255) +im.putalpha(mask) + +for size in (16, 32, 48, 64, 128, 256, 512, 1024): + scaled = im if size == w else im.resize((size, size), Image.LANCZOS) + scaled.save(f"{out}/bear-{size}.png") + print(f" bear-{size}.png") +PY + +echo "[3/4] Building BearBrowser.icns (iconutil)..." +iconset="$tmp/BearBrowser.iconset" +mkdir -p "$iconset" +cp "$out/bear-16.png" "$iconset/icon_16x16.png" +cp "$out/bear-32.png" "$iconset/icon_16x16@2x.png" +cp "$out/bear-32.png" "$iconset/icon_32x32.png" +cp "$out/bear-64.png" "$iconset/icon_32x32@2x.png" +cp "$out/bear-128.png" "$iconset/icon_128x128.png" +cp "$out/bear-256.png" "$iconset/icon_128x128@2x.png" +cp "$out/bear-256.png" "$iconset/icon_256x256.png" +cp "$out/bear-512.png" "$iconset/icon_256x256@2x.png" +cp "$out/bear-512.png" "$iconset/icon_512x512.png" +cp "$out/bear-1024.png" "$iconset/icon_512x512@2x.png" +iconutil -c icns "$iconset" -o "$out/BearBrowser.icns" +echo " BearBrowser.icns" + +echo "[4/4] Building bear.ico (Pillow, multi-size)..." +python3 - "$out" <<'PY' +import sys +from PIL import Image +out = sys.argv[1] +im = Image.open(f"{out}/bear-256.png") +im.save(f"{out}/bear.ico", sizes=[(16,16),(24,24),(32,32),(48,48),(64,64),(128,128),(256,256)]) +print(" bear.ico") +PY + +echo "Done → $out" +ls -la "$out"