2026-05-22 17:27:30 +00:00
|
|
|
#!/bin/sh
|
2026-05-31 12:23:28 +00:00
|
|
|
# Wrap the built desktop binary in `OpenPencil.app` and code-sign it
|
|
|
|
|
# with the project's Developer ID so it carries the SAME signing
|
|
|
|
|
# identity as the shipped TS app (`dev.openpencil.app`, Team
|
|
|
|
|
# CYHUA8SZF6).
|
2026-05-22 17:27:30 +00:00
|
|
|
#
|
2026-05-31 12:23:28 +00:00
|
|
|
# Why the identity matters: macOS TCC keys folder-access grants
|
|
|
|
|
# (Desktop / Documents / Downloads) on an app's *designated
|
|
|
|
|
# requirement* — bundle id + signing team — not on the exact binary.
|
|
|
|
|
# A bare/ad-hoc binary has no durable identity, so it is denied; a
|
|
|
|
|
# bundle signed with the same Developer ID + bundle id as an
|
|
|
|
|
# already-granted app inherits that grant. This is why the TS app can
|
|
|
|
|
# commit a `.op` file on the Desktop and an unsigned dev build cannot.
|
2026-05-22 17:27:30 +00:00
|
|
|
#
|
|
|
|
|
# Usage: tools/bundle-macos.sh (after `cargo build -p op-host-desktop --release`)
|
2026-05-31 12:23:28 +00:00
|
|
|
# Then launch via LaunchServices so the app is its own TCC responsible
|
|
|
|
|
# process: open target/release/OpenPencil.app
|
|
|
|
|
#
|
|
|
|
|
# Signing identity is configurable:
|
|
|
|
|
# OPENPENCIL_SIGN_ID="Developer ID Application: ... (TEAMID)" (default below)
|
|
|
|
|
# OPENPENCIL_SIGN_ID=- ad-hoc sign (no durable identity / no grant)
|
|
|
|
|
# When the identity is not in the keychain the script warns and leaves
|
|
|
|
|
# the bundle linker-signed so it still runs locally.
|
2026-05-22 17:27:30 +00:00
|
|
|
set -e
|
|
|
|
|
|
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
2026-07-15 13:10:08 +00:00
|
|
|
CANONICAL_VERSION="$("$ROOT/scripts/workspace-version.sh")"
|
|
|
|
|
APP_VERSION="${OPENPENCIL_VERSION:-$CANONICAL_VERSION}"
|
|
|
|
|
if [ "$APP_VERSION" != "$CANONICAL_VERSION" ]; then
|
|
|
|
|
printf 'bundle-macos: error: OPENPENCIL_VERSION (%s) must match Cargo workspace version (%s)\n' \
|
|
|
|
|
"$APP_VERSION" "$CANONICAL_VERSION" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-07-15 13:10:08 +00:00
|
|
|
if [ "${OPENPENCIL_VALIDATE_VERSION_ONLY:-}" = 1 ]; then
|
|
|
|
|
printf '%s\n' "$APP_VERSION"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
2026-05-22 17:27:30 +00:00
|
|
|
BIN="$ROOT/target/release/openpencil-desktop"
|
|
|
|
|
ICON="$ROOT/crates/op-host-desktop/assets/icon.icns"
|
2026-05-31 12:23:28 +00:00
|
|
|
ENTITLEMENTS="$ROOT/crates/op-host-desktop/entitlements.plist"
|
2026-05-22 17:27:30 +00:00
|
|
|
APP="$ROOT/target/release/OpenPencil.app"
|
|
|
|
|
|
2026-05-31 12:23:28 +00:00
|
|
|
# Match the TS app exactly so the two share one TCC designated requirement.
|
|
|
|
|
BUNDLE_ID="dev.openpencil.app"
|
|
|
|
|
SIGN_ID="${OPENPENCIL_SIGN_ID:-Developer ID Application: Shanghai Yixing Intelligence Technology Co., Ltd. (CYHUA8SZF6)}"
|
|
|
|
|
|
2026-05-22 17:27:30 +00:00
|
|
|
if [ ! -x "$BIN" ]; then
|
|
|
|
|
echo "bundle-macos: $BIN not found — run 'cargo build -p op-host-desktop --release' first" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
rm -rf "$APP"
|
|
|
|
|
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
|
|
|
|
cp "$BIN" "$APP/Contents/MacOS/openpencil-desktop"
|
|
|
|
|
cp "$ICON" "$APP/Contents/Resources/icon.icns"
|
|
|
|
|
|
2026-05-31 12:23:28 +00:00
|
|
|
cat > "$APP/Contents/Info.plist" <<PLIST
|
2026-05-22 17:27:30 +00:00
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
|
|
|
<plist version="1.0">
|
|
|
|
|
<dict>
|
|
|
|
|
<key>CFBundleName</key><string>OpenPencil</string>
|
|
|
|
|
<key>CFBundleDisplayName</key><string>OpenPencil</string>
|
|
|
|
|
<key>CFBundleExecutable</key><string>openpencil-desktop</string>
|
2026-05-31 12:23:28 +00:00
|
|
|
<key>CFBundleIdentifier</key><string>${BUNDLE_ID}</string>
|
feat(panels,canvas): editable gradients/effects + SVG/image import + locale-aware dialogs
Continues the gradient + property-panel polish from the previous
commit and rounds out two new flows the TS app already has:
Gradient stops + effects:
- ColorTarget gains GradientStop(i) + EffectColor(i); HSV picker
preserves alpha across hue/SV drags so a transparent stop stays
transparent. Hex pill stays 6-char; alpha is reattached at commit
and the swatch sits on a 2x2 alpha checker so #00000000 reads as
transparent rather than empty.
- Effects section reflowed into card-style blocks (image #9 spec):
title + minus, X/Y and Blur/Spread 2-col grids, color row with
swatch + rgba(...) text; clicking the swatch opens an HSV picker
bound to that effect index via SetEffectColor.
- Press dispatch on both hosts anchors picker overlays at the
clicked y so they pop adjacent to the swatch instead of the top.
Image + SVG import (toolbar + Fill section "图片" row):
- New FileAction::ImportImageOrSvg / PickFillImage; persistence_image
pops rfd, decodes raster as data: URL, inserts an Image node or
rewrites the selected node's primary fill.
- ImageNode actually renders on the canvas: NodePayload + SceneNode
carry image_src, canvas_viewport_paint.rs decodes the data URL
once and hands raw bytes to RenderBackend::draw_image with a
src-hash cache id. Grey placeholder paints only when decode fails
so transparent PNGs don't get a grey matte underneath.
- SVG import ported to TS-parity (packages/pen-engine svg-parser):
recursive <g> tree walk with inherited fill/stroke/style="...",
viewBox-aware scaling with maxDim cap, multi-subpath split, raw
d preserved on PathNode. Imports land wrapped in a Group named
after the source file.
Locale-aware first run:
- settings_io detects the OS locale (LC_ALL/LANG/LC_MESSAGES with
zh-Hans/zh-Hant heuristics) and seeds editor_ui.locale before
settings.json is read; persisted user choice still wins.
- macOS bundle declares CFBundleLocalizations + AllowMixedLocalizations
so NSOpenPanel / NSSavePanel render in the same language as the
rest of the chrome.
Web host kept exhaustive across the new variants (PickFillImage,
OpenEffectColorPicker, ColorTarget::EffectColor, GradientStop). Two
new files: persistence_image.rs (file-pick handlers, ≤120 lines) and
svg_path_data.rs (path-d tokenizer + bbox + normaliser, split from
svg_import.rs to stay under the 800-line cap). 277 op-editor-core
tests pass.
2026-05-23 15:11:38 +00:00
|
|
|
<key>CFBundleAllowMixedLocalizations</key><true/>
|
|
|
|
|
<key>CFBundleDevelopmentRegion</key><string>en</string>
|
|
|
|
|
<key>CFBundleLocalizations</key>
|
|
|
|
|
<array>
|
|
|
|
|
<string>en</string>
|
|
|
|
|
<string>zh-Hans</string>
|
|
|
|
|
<string>zh-Hant</string>
|
|
|
|
|
<string>ja</string>
|
|
|
|
|
<string>ko</string>
|
|
|
|
|
<string>fr</string>
|
|
|
|
|
<string>es</string>
|
|
|
|
|
<string>de</string>
|
|
|
|
|
<string>pt</string>
|
|
|
|
|
<string>ru</string>
|
|
|
|
|
<string>hi</string>
|
|
|
|
|
<string>tr</string>
|
|
|
|
|
<string>th</string>
|
|
|
|
|
<string>vi</string>
|
|
|
|
|
<string>id</string>
|
|
|
|
|
</array>
|
2026-05-22 17:27:30 +00:00
|
|
|
<key>CFBundleIconFile</key><string>icon</string>
|
|
|
|
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
|
|
|
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
2026-07-15 13:10:08 +00:00
|
|
|
<key>CFBundleShortVersionString</key><string>${APP_VERSION}</string>
|
2026-05-22 17:27:30 +00:00
|
|
|
<key>NSHighResolutionCapable</key><true/>
|
|
|
|
|
</dict>
|
|
|
|
|
</plist>
|
|
|
|
|
PLIST
|
|
|
|
|
|
2026-05-31 12:23:28 +00:00
|
|
|
echo "bundle-macos: assembled $APP (bundle id $BUNDLE_ID)"
|
|
|
|
|
|
|
|
|
|
# --- Code-sign so the bundle's identity matches the TS app -----------
|
|
|
|
|
if [ "$SIGN_ID" = "-" ]; then
|
|
|
|
|
echo "bundle-macos: ad-hoc signing (OPENPENCIL_SIGN_ID=-) — no TCC grant inheritance"
|
|
|
|
|
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign - "$APP"
|
|
|
|
|
elif security find-identity -v -p codesigning | grep -qF "$SIGN_ID"; then
|
|
|
|
|
echo "bundle-macos: signing with \"$SIGN_ID\""
|
|
|
|
|
# --options runtime → hardened runtime (matches TS flags=runtime).
|
|
|
|
|
# entitlements relax library validation for the Homebrew OpenSSL dylibs.
|
|
|
|
|
codesign --force --options runtime --entitlements "$ENTITLEMENTS" \
|
|
|
|
|
--sign "$SIGN_ID" --timestamp=none "$APP"
|
|
|
|
|
echo "bundle-macos: verifying signature"
|
|
|
|
|
codesign --verify --strict --verbose=2 "$APP"
|
|
|
|
|
codesign -dvv "$APP" 2>&1 | grep -E 'Identifier|Authority|TeamIdentifier|flags' || true
|
|
|
|
|
else
|
|
|
|
|
echo "bundle-macos: WARNING signing identity not found in keychain:" >&2
|
|
|
|
|
echo " \"$SIGN_ID\"" >&2
|
|
|
|
|
echo " leaving bundle linker-signed (runs locally, but won't inherit the TS TCC grant)." >&2
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "bundle-macos: done — launch with: open \"$APP\""
|