rename snaggle
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<?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>
|
||||
<!-- "developer-id" produces an app that can be notarised and distributed outside
|
||||
the App Store. Use "mac-application" for a local build with no Developer ID. -->
|
||||
<key>method</key>
|
||||
<string>developer-id</string>
|
||||
<key>signingStyle</key>
|
||||
<string>automatic</string>
|
||||
<key>teamID</key>
|
||||
<string>REPLACE_WITH_TEAM_ID</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Executable → Regular
+4
-4
@@ -1,17 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds GreenshotMac and drops the .app in build/Release.
|
||||
# Builds Snaggle and drops the .app in build/Release.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
xcodebuild \
|
||||
-project GreenshotMac.xcodeproj \
|
||||
-scheme GreenshotMac \
|
||||
-project Snaggle.xcodeproj \
|
||||
-scheme Snaggle \
|
||||
-configuration "${1:-Release}" \
|
||||
-destination 'platform=macOS,arch=arm64' \
|
||||
-derivedDataPath build/DerivedData \
|
||||
build
|
||||
|
||||
APP="build/DerivedData/Build/Products/${1:-Release}/GreenshotMac.app"
|
||||
APP="build/DerivedData/Build/Products/${1:-Release}/Snaggle.app"
|
||||
echo
|
||||
echo "Built: $APP"
|
||||
echo "Run it with: open \"$APP\""
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generates Snaggle's app icon set.
|
||||
|
||||
Draws an original mark - a rounded square with a capture viewfinder - at high
|
||||
resolution, then writes every size macOS asks for into the asset catalogue.
|
||||
Run it again after editing the colours or shapes below:
|
||||
|
||||
python3 scripts/make_appicon.py
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
|
||||
SUPERSAMPLE = 4
|
||||
BASE = 1024
|
||||
OUTPUT = Path(__file__).resolve().parent.parent / "Snaggle" / "Resources" / "Assets.xcassets" / "AppIcon.appiconset"
|
||||
|
||||
# Deep to bright green, reading as one surface rather than a flat fill.
|
||||
TOP_COLOR = (46, 173, 92)
|
||||
BOTTOM_COLOR = (18, 106, 62)
|
||||
MARK_COLOR = (255, 255, 255, 242)
|
||||
|
||||
|
||||
def vertical_gradient(size, top, bottom):
|
||||
gradient = Image.new("RGB", (1, size), top)
|
||||
for y in range(size):
|
||||
blend = y / max(1, size - 1)
|
||||
gradient.putpixel((0, y), tuple(
|
||||
round(top[channel] + (bottom[channel] - top[channel]) * blend) for channel in range(3)
|
||||
))
|
||||
return gradient.resize((size, size))
|
||||
|
||||
|
||||
def draw_icon(size):
|
||||
scale = size / BASE
|
||||
canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
|
||||
# macOS leaves a margin around the rounded square and uses a large corner radius.
|
||||
margin = round(100 * scale)
|
||||
side = size - margin * 2
|
||||
radius = round(185 * scale)
|
||||
|
||||
mask = Image.new("L", (size, size), 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle(
|
||||
[margin, margin, size - margin, size - margin], radius=radius, fill=255)
|
||||
|
||||
body = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
body.paste(vertical_gradient(size, TOP_COLOR, BOTTOM_COLOR), (0, 0))
|
||||
body.putalpha(mask)
|
||||
|
||||
# Soft highlight across the top, blurred so there is no hard glossy edge.
|
||||
highlight = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
ImageDraw.Draw(highlight).ellipse(
|
||||
[margin - side * 0.35, margin - side * 0.95, size - margin + side * 0.35, margin + side * 0.40],
|
||||
fill=(255, 255, 255, 34))
|
||||
highlight = highlight.filter(ImageFilter.GaussianBlur(round(60 * scale)))
|
||||
highlight.putalpha(Image.composite(highlight.split()[3], Image.new("L", (size, size), 0), mask))
|
||||
body = Image.alpha_composite(body, highlight)
|
||||
|
||||
canvas = Image.alpha_composite(canvas, body)
|
||||
|
||||
# The mark: four viewfinder corners around an aperture ring.
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
stroke = max(1, round(46 * scale))
|
||||
centre = size / 2
|
||||
reach = side * 0.30 # how far the corners sit from the centre
|
||||
arm = side * 0.13 # length of each corner arm
|
||||
|
||||
def round_cap(x, y):
|
||||
draw.ellipse([x - stroke / 2, y - stroke / 2, x + stroke / 2, y + stroke / 2], fill=MARK_COLOR)
|
||||
|
||||
for x_sign in (-1, 1):
|
||||
for y_sign in (-1, 1):
|
||||
x = centre + x_sign * reach
|
||||
y = centre + y_sign * reach
|
||||
# One polyline per corner keeps the elbow smooth instead of notched.
|
||||
start = (x - x_sign * arm, y)
|
||||
elbow = (x, y)
|
||||
end = (x, y - y_sign * arm)
|
||||
draw.line([start, elbow, end], fill=MARK_COLOR, width=stroke, joint="curve")
|
||||
for point in (start, elbow, end):
|
||||
round_cap(*point)
|
||||
|
||||
ring = side * 0.135
|
||||
draw.ellipse([centre - ring, centre - ring, centre + ring, centre + ring],
|
||||
outline=MARK_COLOR, width=stroke)
|
||||
|
||||
# Shadow under the rounded square, the way system icons sit on a surface.
|
||||
shadow = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
ImageDraw.Draw(shadow).rounded_rectangle(
|
||||
[margin, margin + round(18 * scale), size - margin, size - margin + round(18 * scale)],
|
||||
radius=radius, fill=(0, 0, 0, 70))
|
||||
shadow = shadow.filter(ImageFilter.GaussianBlur(round(18 * scale)))
|
||||
return Image.alpha_composite(shadow, canvas)
|
||||
|
||||
|
||||
def main():
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
master = draw_icon(BASE * SUPERSAMPLE).resize((BASE, BASE), Image.LANCZOS)
|
||||
|
||||
# (point size, scale) pairs macOS expects for a Mac app icon.
|
||||
entries = []
|
||||
for point, scales in [(16, [1, 2]), (32, [1, 2]), (128, [1, 2]), (256, [1, 2]), (512, [1, 2])]:
|
||||
for factor in scales:
|
||||
pixels = point * factor
|
||||
name = f"icon_{point}x{point}{'@2x' if factor == 2 else ''}.png"
|
||||
master.resize((pixels, pixels), Image.LANCZOS).save(OUTPUT / name)
|
||||
entries.append(f""" {{
|
||||
"filename" : "{name}",
|
||||
"idiom" : "mac",
|
||||
"scale" : "{factor}x",
|
||||
"size" : "{point}x{point}"
|
||||
}}""")
|
||||
|
||||
contents = "{\n \"images\" : [\n" + ",\n".join(entries) + \
|
||||
"\n ],\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
|
||||
(OUTPUT / "Contents.json").write_text(contents)
|
||||
print(f"Wrote {len(entries)} icon sizes to {OUTPUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Builds a distributable Snaggle.app and wraps it in a DMG.
|
||||
#
|
||||
# ./scripts/release.sh # ad-hoc signed (no Apple Developer account needed)
|
||||
# ./scripts/release.sh --developer-id # Developer ID + notarisation (paid account)
|
||||
#
|
||||
# The ad-hoc build runs anywhere, but macOS marks it as unverified: whoever downloads it has
|
||||
# to allow it once under System Settings > Privacy & Security. See README "Installing".
|
||||
#
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
MODE="adhoc"
|
||||
[[ "${1:-}" == "--developer-id" ]] && MODE="developer-id"
|
||||
|
||||
# Xcode itself is required; the Command Line Tools package alone cannot build a project.
|
||||
if ! xcodebuild -version >/dev/null 2>&1; then
|
||||
echo "error: xcodebuild needs a full Xcode installation." >&2
|
||||
echo "Active developer directory: $(xcode-select -p 2>/dev/null)" >&2
|
||||
echo >&2
|
||||
echo "Point it at Xcode and try again:" >&2
|
||||
echo " sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BUILD_DIR="build"
|
||||
DERIVED="$BUILD_DIR/DerivedData"
|
||||
EXPORT_DIR="$BUILD_DIR/export"
|
||||
DMG="$BUILD_DIR/Snaggle.dmg"
|
||||
NOTARY_PROFILE="${NOTARY_PROFILE:-SnaggleNotary}"
|
||||
|
||||
rm -rf "$EXPORT_DIR" "$DMG"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
if [[ "$MODE" == "adhoc" ]]; then
|
||||
echo "==> Building (ad-hoc signature)"
|
||||
xcodebuild \
|
||||
-project Snaggle.xcodeproj \
|
||||
-scheme Snaggle \
|
||||
-configuration Release \
|
||||
-destination 'platform=macOS,arch=arm64' \
|
||||
-derivedDataPath "$DERIVED" \
|
||||
CODE_SIGN_IDENTITY="-" \
|
||||
CODE_SIGN_STYLE=Manual \
|
||||
DEVELOPMENT_TEAM="" \
|
||||
build
|
||||
|
||||
mkdir -p "$EXPORT_DIR"
|
||||
cp -R "$DERIVED/Build/Products/Release/Snaggle.app" "$EXPORT_DIR/"
|
||||
APP="$EXPORT_DIR/Snaggle.app"
|
||||
|
||||
# Apple silicon refuses to run a binary with no signature at all, so re-sign ad-hoc
|
||||
# to be certain every nested item is covered.
|
||||
echo "==> Ad-hoc signing"
|
||||
codesign --force --deep --sign - --options runtime "$APP"
|
||||
codesign --verify --verbose=2 "$APP"
|
||||
else
|
||||
ARCHIVE="$BUILD_DIR/Snaggle.xcarchive"
|
||||
rm -rf "$ARCHIVE"
|
||||
|
||||
echo "==> Archiving"
|
||||
xcodebuild archive \
|
||||
-project Snaggle.xcodeproj \
|
||||
-scheme Snaggle \
|
||||
-configuration Release \
|
||||
-destination 'platform=macOS,arch=arm64' \
|
||||
-archivePath "$ARCHIVE"
|
||||
|
||||
echo "==> Exporting"
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath "$ARCHIVE" \
|
||||
-exportOptionsPlist scripts/ExportOptions.plist \
|
||||
-exportPath "$EXPORT_DIR"
|
||||
APP="$EXPORT_DIR/Snaggle.app"
|
||||
|
||||
echo "==> Notarising (this can take a few minutes)"
|
||||
ZIP="$BUILD_DIR/Snaggle-notarize.zip"
|
||||
rm -f "$ZIP"
|
||||
ditto -c -k --keepParent "$APP" "$ZIP"
|
||||
xcrun notarytool submit "$ZIP" --keychain-profile "$NOTARY_PROFILE" --wait
|
||||
xcrun stapler staple "$APP"
|
||||
rm -f "$ZIP"
|
||||
fi
|
||||
|
||||
echo "==> Building DMG"
|
||||
STAGING="$BUILD_DIR/dmg-staging"
|
||||
rm -rf "$STAGING"
|
||||
mkdir -p "$STAGING"
|
||||
cp -R "$APP" "$STAGING/"
|
||||
ln -s /Applications "$STAGING/Applications"
|
||||
cp docs/INSTALL.txt "$STAGING/" 2>/dev/null || true
|
||||
hdiutil create -volname "Snaggle" -srcfolder "$STAGING" -ov -format UDZO "$DMG"
|
||||
rm -rf "$STAGING"
|
||||
|
||||
echo
|
||||
echo "App: $APP"
|
||||
echo "DMG: $DMG"
|
||||
if [[ "$MODE" == "adhoc" ]]; then
|
||||
echo
|
||||
echo "Ad-hoc signed. Tell people to open it once via"
|
||||
echo " System Settings > Privacy & Security > Open Anyway"
|
||||
echo "Control-click > Open no longer works on macOS 15 and later."
|
||||
fi
|
||||
Reference in New Issue
Block a user