diff --git a/Design/AppIcon.icns b/Design/AppIcon.icns new file mode 100644 index 0000000..5ec0b5a Binary files /dev/null and b/Design/AppIcon.icns differ diff --git a/Package.swift b/Package.swift index 4cb6621..7104fd6 100644 --- a/Package.swift +++ b/Package.swift @@ -19,6 +19,7 @@ let package = Package( ] ), .executableTarget(name: "Sst", dependencies: ["SstCore", "SstWhisper", "SstData", "SstUI"]), + .executableTarget(name: "SstIcon", dependencies: ["SstUI"]), .executableTarget(name: "SstProbe", dependencies: ["SstCore", "SstWhisper"]), .executableTarget(name: "SstGallery", dependencies: ["SstUI"]), .testTarget(name: "SstCoreTests", dependencies: ["SstCore"]), diff --git a/Sources/SstIcon/IconMaker.swift b/Sources/SstIcon/IconMaker.swift new file mode 100644 index 0000000..1ed1852 --- /dev/null +++ b/Sources/SstIcon/IconMaker.swift @@ -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 ") + 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)") + } +} diff --git a/scripts/make-app.sh b/scripts/make-app.sh index 55b449a..fb54ee3 100755 --- a/scripts/make-app.sh +++ b/scripts/make-app.sh @@ -1,10 +1,14 @@ #!/usr/bin/env bash +# Builds build/Sst.app (a normal Dock app, no LSUIElement). Pass --install to copy it to /Applications. set -euo pipefail cd "$(dirname "$0")/.." +APP="build/Sst.app" +IDENTIFIER="local.sst.app" +VERSION="0.2.0" + swift build -c release --product Sst BIN=".build/release/Sst" -APP="build/Sst.app" rm -rf "$APP" 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 [ -e "$b" ] && cp -R "$b" "$APP/Contents/Resources/" 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" < - CFBundleIdentifierlocal.sst.app + CFBundleIdentifier$IDENTIFIER CFBundleNameSst + CFBundleDisplayNameSst CFBundleExecutableSst CFBundlePackageTypeAPPL - CFBundleShortVersionString0.1.0 + CFBundleShortVersionString$VERSION CFBundleVersion1 + CFBundleIconFileAppIcon LSMinimumSystemVersion14.0 - LSUIElement + LSApplicationCategoryTypepublic.app-category.productivity + NSHighResolutionCapable NSMicrophoneUsageDescription - Sst neemt je stem op zolang je Fn ingedrukt houdt, om die lokaal naar tekst om te zetten. + Sst neemt je stem op zolang je de sneltoets ingedrukt houdt, om die lokaal naar tekst om te zetten. 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 "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 diff --git a/scripts/make-icon.sh b/scripts/make-icon.sh new file mode 100755 index 0000000..8faf4f1 --- /dev/null +++ b/scripts/make-icon.sh @@ -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"