feat: app icon generator, signed app bundle script with install option
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -19,6 +19,7 @@ let package = Package(
|
|||||||
]
|
]
|
||||||
),
|
),
|
||||||
.executableTarget(name: "Sst", dependencies: ["SstCore", "SstWhisper", "SstData", "SstUI"]),
|
.executableTarget(name: "Sst", dependencies: ["SstCore", "SstWhisper", "SstData", "SstUI"]),
|
||||||
|
.executableTarget(name: "SstIcon", dependencies: ["SstUI"]),
|
||||||
.executableTarget(name: "SstProbe", dependencies: ["SstCore", "SstWhisper"]),
|
.executableTarget(name: "SstProbe", dependencies: ["SstCore", "SstWhisper"]),
|
||||||
.executableTarget(name: "SstGallery", dependencies: ["SstUI"]),
|
.executableTarget(name: "SstGallery", dependencies: ["SstUI"]),
|
||||||
.testTarget(name: "SstCoreTests", dependencies: ["SstCore"]),
|
.testTarget(name: "SstCoreTests", dependencies: ["SstCore"]),
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import AppKit
|
||||||
|
import SstUI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// The app icon: a brushed-aluminium plate with a VU meter and the red record lamp, drawn with the design tokens.
|
||||||
|
/// Canvas is 256 pt; the macOS icon grid puts the plate at 206 pt with a 46 pt corner radius.
|
||||||
|
struct IconView: View {
|
||||||
|
static let canvas: CGFloat = 256
|
||||||
|
private let plate: CGFloat = 206
|
||||||
|
private let corner: CGFloat = 46
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
BrushedBackground()
|
||||||
|
.frame(width: plate, height: plate)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: corner, style: .continuous))
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: corner, style: .continuous)
|
||||||
|
.strokeBorder(Palette.lineSeamDark, lineWidth: Border.hairline))
|
||||||
|
.shadow(color: .black.opacity(Opacity.mark), radius: Space.s3, y: Space.s2)
|
||||||
|
|
||||||
|
VStack(spacing: Space.s5) {
|
||||||
|
VUMeterView(levelDbfs: -26, isActive: true, brand: "", initialFraction: 0.62)
|
||||||
|
.frame(width: 150)
|
||||||
|
HStack(spacing: Space.s4) {
|
||||||
|
LampView(isOn: true, large: true).scaleEffect(1.5)
|
||||||
|
SilkLabel("REC", style: Typography.heading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.offset(y: Space.s2)
|
||||||
|
}
|
||||||
|
.frame(width: Self.canvas, height: Self.canvas)
|
||||||
|
.environment(\.colorScheme, .light)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@main
|
||||||
|
@MainActor
|
||||||
|
struct IconMaker {
|
||||||
|
static func main() {
|
||||||
|
guard CommandLine.arguments.count > 1 else {
|
||||||
|
print("usage: SstIcon <output-folder.iconset>")
|
||||||
|
exit(2)
|
||||||
|
}
|
||||||
|
let folder = URL(fileURLWithPath: CommandLine.arguments[1], isDirectory: true)
|
||||||
|
try? FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
|
||||||
|
|
||||||
|
// (file name, pixel size) as iconutil expects them
|
||||||
|
let outputs: [(String, CGFloat)] = [
|
||||||
|
("icon_16x16", 16), ("icon_16x16@2x", 32), ("icon_32x32", 32), ("icon_32x32@2x", 64),
|
||||||
|
("icon_128x128", 128), ("icon_128x128@2x", 256), ("icon_256x256", 256), ("icon_256x256@2x", 512),
|
||||||
|
("icon_512x512", 512), ("icon_512x512@2x", 1024),
|
||||||
|
]
|
||||||
|
for (name, pixels) in outputs {
|
||||||
|
let renderer = ImageRenderer(content: IconView())
|
||||||
|
renderer.scale = pixels / IconView.canvas
|
||||||
|
guard let tiff = renderer.nsImage?.tiffRepresentation,
|
||||||
|
let png = NSBitmapImageRep(data: tiff)?.representation(using: .png, properties: [:])
|
||||||
|
else {
|
||||||
|
print("kon \(name) niet renderen")
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
try? png.write(to: folder.appending(path: "\(name).png"))
|
||||||
|
}
|
||||||
|
print("iconset geschreven naar \(folder.path)")
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
-8
@@ -1,10 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# Builds build/Sst.app (a normal Dock app, no LSUIElement). Pass --install to copy it to /Applications.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
APP="build/Sst.app"
|
||||||
|
IDENTIFIER="local.sst.app"
|
||||||
|
VERSION="0.2.0"
|
||||||
|
|
||||||
swift build -c release --product Sst
|
swift build -c release --product Sst
|
||||||
BIN=".build/release/Sst"
|
BIN=".build/release/Sst"
|
||||||
APP="build/Sst.app"
|
|
||||||
|
|
||||||
rm -rf "$APP"
|
rm -rf "$APP"
|
||||||
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
||||||
@@ -13,26 +17,49 @@ cp "$BIN" "$APP/Contents/MacOS/Sst"
|
|||||||
for b in .build/release/*.bundle; do
|
for b in .build/release/*.bundle; do
|
||||||
[ -e "$b" ] && cp -R "$b" "$APP/Contents/Resources/"
|
[ -e "$b" ] && cp -R "$b" "$APP/Contents/Resources/"
|
||||||
done
|
done
|
||||||
|
if [ -f Design/AppIcon.icns ]; then
|
||||||
|
cp Design/AppIcon.icns "$APP/Contents/Resources/AppIcon.icns"
|
||||||
|
else
|
||||||
|
echo "Let op: Design/AppIcon.icns ontbreekt; run scripts/make-icon.sh."
|
||||||
|
fi
|
||||||
|
|
||||||
cat > "$APP/Contents/Info.plist" <<'PLIST'
|
cat > "$APP/Contents/Info.plist" <<PLIST
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleIdentifier</key><string>local.sst.app</string>
|
<key>CFBundleIdentifier</key><string>$IDENTIFIER</string>
|
||||||
<key>CFBundleName</key><string>Sst</string>
|
<key>CFBundleName</key><string>Sst</string>
|
||||||
|
<key>CFBundleDisplayName</key><string>Sst</string>
|
||||||
<key>CFBundleExecutable</key><string>Sst</string>
|
<key>CFBundleExecutable</key><string>Sst</string>
|
||||||
<key>CFBundlePackageType</key><string>APPL</string>
|
<key>CFBundlePackageType</key><string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key><string>0.1.0</string>
|
<key>CFBundleShortVersionString</key><string>$VERSION</string>
|
||||||
<key>CFBundleVersion</key><string>1</string>
|
<key>CFBundleVersion</key><string>1</string>
|
||||||
|
<key>CFBundleIconFile</key><string>AppIcon</string>
|
||||||
<key>LSMinimumSystemVersion</key><string>14.0</string>
|
<key>LSMinimumSystemVersion</key><string>14.0</string>
|
||||||
<key>LSUIElement</key><true/>
|
<key>LSApplicationCategoryType</key><string>public.app-category.productivity</string>
|
||||||
|
<key>NSHighResolutionCapable</key><true/>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Sst neemt je stem op zolang je Fn ingedrukt houdt, om die lokaal naar tekst om te zetten.</string>
|
<string>Sst neemt je stem op zolang je de sneltoets ingedrukt houdt, om die lokaal naar tekst om te zetten.</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
PLIST
|
PLIST
|
||||||
|
|
||||||
codesign --force --sign - --identifier local.sst.app "$APP"
|
# A stable signing identity keeps the macOS permissions (Microphone, Accessibility, Input Monitoring) across rebuilds.
|
||||||
|
IDENTITY="${SST_SIGN_IDENTITY:-$(security find-identity -v -p codesigning | sed -n 's/.*"\(Apple Development: [^"]*\)".*/\1/p' | head -1)}"
|
||||||
|
if [ -n "$IDENTITY" ]; then
|
||||||
|
codesign --force --sign "$IDENTITY" --identifier "$IDENTIFIER" "$APP"
|
||||||
|
echo "Ondertekend met: $IDENTITY"
|
||||||
|
else
|
||||||
|
codesign --force --sign - --identifier "$IDENTIFIER" "$APP"
|
||||||
|
echo "WAARSCHUWING: geen Apple Development-certificaat gevonden; ad-hoc ondertekend (rechten gaan na elke herbouw verloren)."
|
||||||
|
fi
|
||||||
|
codesign --verify --strict "$APP"
|
||||||
echo "Gebouwd: $APP"
|
echo "Gebouwd: $APP"
|
||||||
echo "Na een herbouw: tccutil reset Accessibility local.sst.app; tccutil reset ListenEvent local.sst.app; tccutil reset Microphone local.sst.app (en geef de rechten opnieuw)."
|
echo "Eenmalig na het wisselen van ondertekening: tccutil reset Accessibility $IDENTIFIER; tccutil reset ListenEvent $IDENTIFIER; tccutil reset Microphone $IDENTIFIER (en geef de rechten opnieuw)."
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--install" ]; then
|
||||||
|
rm -rf /Applications/Sst.app
|
||||||
|
cp -R "$APP" /Applications/Sst.app
|
||||||
|
echo "Geïnstalleerd: /Applications/Sst.app"
|
||||||
|
fi
|
||||||
|
|||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Renders the app icon from the design tokens and stores it as Design/AppIcon.icns (committed).
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
swift build --product SstIcon
|
||||||
|
rm -rf build/AppIcon.iconset
|
||||||
|
.build/debug/SstIcon build/AppIcon.iconset
|
||||||
|
iconutil -c icns build/AppIcon.iconset -o Design/AppIcon.icns
|
||||||
|
echo "Icoon geschreven: Design/AppIcon.icns"
|
||||||
Reference in New Issue
Block a user