fix: deliver TinySTT direct input with T icon and ding feedback
This commit is contained in:
45 files changed
+336
-289
No files matched your search
+11
-6
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# 构建 TinyTTS.app:swift build → 组装 bundle → 签名
|
||||
# 构建 TinySTT.app:swift build → 组装 bundle → 签名
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
@@ -11,21 +11,21 @@ CONFIG="${1:-release}"
|
||||
echo "==> 编译 ${CONFIG}"
|
||||
swift build -c "${CONFIG}" --disable-sandbox
|
||||
|
||||
BIN="$ROOT/.build/${CONFIG}/TinyTTS"
|
||||
APP="$ROOT/dist/TinyTTS.app"
|
||||
BIN="$ROOT/.build/${CONFIG}/TinySTT"
|
||||
APP="$ROOT/dist/TinySTT.app"
|
||||
|
||||
echo "==> 组装 bundle"
|
||||
rm -rf "$APP"
|
||||
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
||||
cp "$BIN" "$APP/Contents/MacOS/TinyTTS"
|
||||
cp "$BIN" "$APP/Contents/MacOS/TinySTT"
|
||||
cp "$ROOT/Resources/Info.plist" "$APP/Contents/Info.plist"
|
||||
cp "$ROOT/Resources/AppIcon.icns" "$APP/Contents/Resources/AppIcon.icns"
|
||||
|
||||
# 优先使用显式指定 > Apple Development > 本地自签名 > 临时签名。
|
||||
# 固定身份很关键:否则每次重建都会让「辅助功能」授权失效。
|
||||
resolve_identity() {
|
||||
if [ -n "${TINYTTS_SIGN_IDENTITY:-}" ]; then
|
||||
echo "${TINYTTS_SIGN_IDENTITY}"
|
||||
if [ -n "${TINYSTT_SIGN_IDENTITY:-${TINYTTS_SIGN_IDENTITY:-}}" ]; then
|
||||
echo "${TINYSTT_SIGN_IDENTITY:-${TINYTTS_SIGN_IDENTITY}}"
|
||||
return
|
||||
fi
|
||||
local valid
|
||||
@@ -34,6 +34,11 @@ resolve_identity() {
|
||||
echo "${valid}" | grep "Apple Development" | head -1 | sed -E 's/.*"(.*)"/\1/'
|
||||
return
|
||||
fi
|
||||
if echo "${valid}" | grep -q "TinySTT Dev"; then
|
||||
echo "TinySTT Dev"
|
||||
return
|
||||
fi
|
||||
# Keep using a pre-rename identity when one is already installed.
|
||||
if echo "${valid}" | grep -q "TinyTTS Dev"; then
|
||||
echo "TinyTTS Dev"
|
||||
return
|
||||
|
||||
+16
-151
@@ -1,162 +1,18 @@
|
||||
#!/usr/bin/env swift
|
||||
//
|
||||
// make-icon.swift — 生成 TinyTTS 应用图标
|
||||
//
|
||||
// 设计:白色麦克风由三段「文字条」镂空而成 —— 声音落成文字。
|
||||
// 深靛蓝渐变底 + 纯白图形,16pt 到 1024pt 都保持清晰轮廓。
|
||||
//
|
||||
// 用法:swift scripts/make-icon.swift <输出目录>
|
||||
//
|
||||
|
||||
// Package the TinySTT T-monogram source into all macOS icon sizes.
|
||||
// Usage: swift scripts/make-icon.swift Resources
|
||||
import AppKit
|
||||
import Foundation
|
||||
|
||||
let BASE: CGFloat = 1024
|
||||
|
||||
/// 超椭圆(squircle),n 越大越接近方形;n=4 接近 Apple 图标的连续曲率
|
||||
func squirclePath(in rect: NSRect, n: CGFloat = 4.0, samples: Int = 720) -> NSBezierPath {
|
||||
let path = NSBezierPath()
|
||||
let a = rect.width / 2
|
||||
let b = rect.height / 2
|
||||
for i in 0...samples {
|
||||
let t = CGFloat(i) / CGFloat(samples) * 2 * .pi
|
||||
let ct = cos(t)
|
||||
let st = sin(t)
|
||||
let x = rect.midX + a * (ct < 0 ? -1 : 1) * pow(abs(ct), 2 / n)
|
||||
let y = rect.midY + b * (st < 0 ? -1 : 1) * pow(abs(st), 2 / n)
|
||||
if i == 0 { path.move(to: NSPoint(x: x, y: y)) } else { path.line(to: NSPoint(x: x, y: y)) }
|
||||
}
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
|
||||
func drawIcon(size: CGFloat) -> NSBitmapImageRep {
|
||||
let px = Int(size.rounded())
|
||||
let rep = NSBitmapImageRep(
|
||||
bitmapDataPlanes: nil, pixelsWide: px, pixelsHigh: px,
|
||||
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true,
|
||||
isPlanar: false, colorSpaceName: .deviceRGB,
|
||||
bytesPerRow: 0, bitsPerPixel: 0
|
||||
)!
|
||||
rep.size = NSSize(width: px, height: px)
|
||||
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
guard let ctx = NSGraphicsContext(bitmapImageRep: rep) else { return rep }
|
||||
NSGraphicsContext.current = ctx
|
||||
ctx.imageInterpolation = .high
|
||||
|
||||
let s = CGFloat(px) / BASE
|
||||
// 小尺寸线条会糊,做一点加粗补偿
|
||||
let bold: CGFloat = px <= 32 ? 1.16 : (px <= 64 ? 1.06 : 1.0)
|
||||
// 尺寸分级:越小越简化,否则 16/32px 会糊成一团
|
||||
let isTiny = px <= 32
|
||||
let isSmall = px <= 64
|
||||
let plateInset: CGFloat = isTiny ? 52 : (isSmall ? 72 : 100)
|
||||
let glyphScale: CGFloat = isTiny ? 1.34 : (isSmall ? 1.20 : 1.10)
|
||||
let showHoles = px >= 128 // 镂空文字在小图上会糊成一格一格
|
||||
let showGlyphShadow = px >= 128
|
||||
let showPlateShadow = px >= 128
|
||||
|
||||
func len(_ v: CGFloat) -> CGFloat { v * s }
|
||||
/// 1024 基准坐标,y 按「距顶部」度量
|
||||
func P(_ x: CGFloat, _ yTop: CGFloat) -> NSPoint { NSPoint(x: x * s, y: (BASE - yTop) * s) }
|
||||
/// 矩形(x, 距顶 y, 宽, 高)
|
||||
func R(_ x: CGFloat, _ yTop: CGFloat, _ w: CGFloat, _ h: CGFloat) -> NSRect {
|
||||
NSRect(x: len(x), y: (BASE - yTop - h) * s, width: len(w), height: len(h))
|
||||
}
|
||||
func roundRect(_ x: CGFloat, _ yTop: CGFloat, _ w: CGFloat, _ h: CGFloat, _ radius: CGFloat) -> NSBezierPath {
|
||||
NSBezierPath(roundedRect: R(x, yTop, w, h), xRadius: len(radius), yRadius: len(radius))
|
||||
}
|
||||
|
||||
// MARK: 底板
|
||||
|
||||
let inset = len(plateInset)
|
||||
let plate = NSRect(x: inset, y: inset,
|
||||
width: CGFloat(px) - inset * 2,
|
||||
height: CGFloat(px) - inset * 2)
|
||||
let shape = squirclePath(in: plate)
|
||||
|
||||
let shadow = NSShadow()
|
||||
shadow.shadowColor = NSColor.black.withAlphaComponent(0.30)
|
||||
shadow.shadowBlurRadius = len(24)
|
||||
shadow.shadowOffset = NSSize(width: 0, height: -len(9))
|
||||
if showPlateShadow { shadow.set() }
|
||||
|
||||
let topColor = NSColor(srgbRed: 0.38, green: 0.50, blue: 1.00, alpha: 1) // #617FFF
|
||||
let bottomColor = NSColor(srgbRed: 0.21, green: 0.19, blue: 0.75, alpha: 1) // #3630BF
|
||||
NSGradient(starting: topColor, ending: bottomColor)?.draw(in: shape, angle: -90)
|
||||
|
||||
NSShadow().set() // 之后不再带投影
|
||||
|
||||
// 顶部柔光:整块区域线性渐变,避免出现横向硬边
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
shape.addClip()
|
||||
NSGradient(colors: [
|
||||
NSColor.white.withAlphaComponent(0.20),
|
||||
NSColor.white.withAlphaComponent(0.0),
|
||||
])?.draw(in: plate, angle: -90)
|
||||
NSGraphicsContext.restoreGraphicsState()
|
||||
|
||||
NSColor.white.withAlphaComponent(0.10).setStroke()
|
||||
shape.lineWidth = max(len(2), 0.5)
|
||||
shape.stroke()
|
||||
|
||||
// MARK: 麦克风
|
||||
|
||||
let glyphShadow = NSShadow()
|
||||
glyphShadow.shadowColor = NSColor.black.withAlphaComponent(0.20)
|
||||
glyphShadow.shadowBlurRadius = len(14)
|
||||
glyphShadow.shadowOffset = NSSize(width: 0, height: -len(7))
|
||||
if showGlyphShadow { glyphShadow.set() }
|
||||
|
||||
NSColor.white.setFill()
|
||||
NSColor.white.setStroke()
|
||||
|
||||
// 腔体:胶囊上挖出三条文字
|
||||
let capX: CGFloat = 412, capW: CGFloat = 200
|
||||
let capY: CGFloat = 225, capH: CGFloat = 300
|
||||
NSGraphicsContext.current?.cgContext.concatenate(CGAffineTransform(a: glyphScale, b: 0, c: 0, d: glyphScale, tx: len(512) * (1 - glyphScale), ty: len(512) * (1 - glyphScale)))
|
||||
let body = roundRect(capX, capY, capW, capH, capW / 2)
|
||||
|
||||
let lineX: CGFloat = 452
|
||||
let lineH: CGFloat = 24 * bold
|
||||
let lineY: [CGFloat] = [305, 363, 421]
|
||||
let linePath = NSBezierPath()
|
||||
for (i, y) in lineY.enumerated() {
|
||||
let w: CGFloat = [120, 88, 104][i]
|
||||
linePath.append(roundRect(lineX, y, w, lineH, lineH / 2))
|
||||
}
|
||||
|
||||
let bodyWithHoles = NSBezierPath()
|
||||
bodyWithHoles.append(body)
|
||||
if showHoles { bodyWithHoles.append(linePath) }
|
||||
bodyWithHoles.windingRule = .evenOdd
|
||||
bodyWithHoles.fill()
|
||||
|
||||
// 托架
|
||||
let arc = NSBezierPath()
|
||||
arc.appendArc(withCenter: P(512, 525), radius: len(148),
|
||||
startAngle: 180, endAngle: 360, clockwise: false)
|
||||
arc.lineWidth = len(54 * bold)
|
||||
arc.lineCapStyle = .round
|
||||
arc.stroke()
|
||||
|
||||
// 立柱
|
||||
roundRect(512 - 27, 660, 54, 92, 27).fill()
|
||||
|
||||
// 底座
|
||||
roundRect(402, 745, 220, 54 * bold, 27 * bold).fill()
|
||||
|
||||
NSShadow().set()
|
||||
NSGraphicsContext.restoreGraphicsState()
|
||||
return rep
|
||||
}
|
||||
|
||||
// MARK: - 输出
|
||||
|
||||
let outDir = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "Resources"
|
||||
let fm = FileManager.default
|
||||
let iconset = (outDir as NSString).appendingPathComponent("AppIcon.iconset")
|
||||
let sourceURL = URL(fileURLWithPath: outDir).appendingPathComponent("AppIcon-source.png")
|
||||
guard let source = NSImage(contentsOf: sourceURL) else {
|
||||
fatalError("缺少图标源文件:\(sourceURL.path)")
|
||||
}
|
||||
|
||||
try? fm.removeItem(atPath: iconset)
|
||||
try fm.createDirectory(atPath: iconset, withIntermediateDirectories: true)
|
||||
@@ -170,7 +26,16 @@ let variants: [(String, Int)] = [
|
||||
]
|
||||
|
||||
for (name, px) in variants {
|
||||
let rep = drawIcon(size: CGFloat(px))
|
||||
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: px, pixelsHigh: px,
|
||||
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true,
|
||||
isPlanar: false, colorSpaceName: .deviceRGB,
|
||||
bytesPerRow: 0, bitsPerPixel: 0)!
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep)
|
||||
NSGraphicsContext.current?.imageInterpolation = .high
|
||||
source.draw(in: NSRect(x: 0, y: 0, width: px, height: px),
|
||||
from: .zero, operation: .copy, fraction: 1)
|
||||
NSGraphicsContext.restoreGraphicsState()
|
||||
guard let data = rep.representation(using: .png, properties: [:]) else {
|
||||
FileHandle.standardError.write("生成 \(name) 失败\n".data(using: .utf8)!)
|
||||
exit(1)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# 生成一个本地自签名代码签名证书,保证每次重建后「辅助功能」授权不会失效。
|
||||
set -euo pipefail
|
||||
|
||||
IDENTITY="${TINYTTS_SIGN_IDENTITY:-TinyTTS Dev}"
|
||||
IDENTITY="${TINYSTT_SIGN_IDENTITY:-${TINYTTS_SIGN_IDENTITY:-TinySTT Dev}}"
|
||||
|
||||
if security find-identity -v -p codesigning 2>/dev/null | grep -q "$IDENTITY"; then
|
||||
echo "签名身份已存在:$IDENTITY"
|
||||
@@ -18,7 +18,7 @@ distinguished_name = dn
|
||||
x509_extensions = ext
|
||||
prompt = no
|
||||
[dn]
|
||||
CN = TinyTTS Dev
|
||||
CN = TinySTT Dev
|
||||
[ext]
|
||||
basicConstraints = critical,CA:false
|
||||
keyUsage = critical,digitalSignature
|
||||
@@ -31,15 +31,15 @@ openssl req -x509 -newkey rsa:2048 -nodes \
|
||||
|
||||
openssl pkcs12 -export -out "$WORK/id.p12" \
|
||||
-inkey "$WORK/key.pem" -in "$WORK/cert.pem" \
|
||||
-name "$IDENTITY" -passout pass:tinytss \
|
||||
-name "$IDENTITY" -passout pass:tinystt \
|
||||
-legacy -macalg sha1 -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES >/dev/null 2>&1 \
|
||||
|| openssl pkcs12 -export -out "$WORK/id.p12" \
|
||||
-inkey "$WORK/key.pem" -in "$WORK/cert.pem" \
|
||||
-name "$IDENTITY" -passout pass:tinytss \
|
||||
-name "$IDENTITY" -passout pass:tinystt \
|
||||
-macalg sha1 -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES >/dev/null 2>&1
|
||||
|
||||
security import "$WORK/id.p12" -k "$HOME/Library/Keychains/login.keychain-db" \
|
||||
-P tinytss -T /usr/bin/codesign >/dev/null
|
||||
-P tinystt -T /usr/bin/codesign >/dev/null
|
||||
|
||||
echo "已创建签名身份:$IDENTITY"
|
||||
security find-identity -v -p codesigning | grep "$IDENTITY" || true
|
||||
Reference in new issue
Block a user