rename snaggle

This commit is contained in:
Alex E. Tafoya
2026-09-18 07:17:34 -05:00
parent fcb8d5b721
commit 0f52b9cc35
51 changed files with 1150 additions and 92 deletions
+14
View File
@@ -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
View File
@@ -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\""
+122
View File
@@ -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()
+104
View File
@@ -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