fix: deliver TinySTT direct input with T icon and ding feedback

This commit is contained in:
eccstartup committed 2026-10-23 10:47:59 +08:00
1 parent fe4af6b34a
commit ab02af1855
45 files changed
+336 -289

No files matched your search

+11 -6
View File
@@ -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
View File
@@ -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)
+5 -5
View File
@@ -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