mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-12 22:25:06 +00:00
主窗口服务器分区(D11,issue #32):NavigationSplitView 分组树(订阅子树 只读、仅启用开关;行内 checkbox;右键激活/启停/重命名/新建分组/移动/删除, 非空手动组二次确认;拖拽移动,跨来源拒绝;空组保留可编辑)、服务器详情 表单(插件区只读展示「本版本未提供」状态)、分组详情。添加三入口(剪贴板 ss://、导入 URL、二维码图片识别:文件选择 + 拖图入窗)共用批量导入落点, 每次新建身份不去重;分享区二维码弹窗 + 复制 ss://。SsUri 按 SIP002 编解码 (含 legacy base64 兼容、SIP022 明文 userinfo、IPv6、插件 name;opts 逐字节 同构),二维码走 CoreImage 生成 + Vision 识别。目录提交后接 postCommit 钩 子,代理运行时立即重展开(#27 catalogDidCommit)。
49 lines
1.9 KiB
Swift
49 lines
1.9 KiB
Swift
import AppKit
|
|
import CoreImage
|
|
import Foundation
|
|
import Vision
|
|
|
|
/// 二维码编解码(issue #32:分享生成二维码、识别二维码图片导入)。生成走
|
|
/// CoreImage `CIQRCodeGenerator`(纠错级 M),识别走 Vision 条码检测,返回
|
|
/// 全部文本负载。屏幕扫码明确不做(spec #21 D11),此处只处理静态图片。
|
|
enum QrCodeCodec {
|
|
enum QrCodeError: Error, Equatable {
|
|
/// 系统二维码滤镜产出为空(理论不可达,防御位)。
|
|
case generationFailed
|
|
/// 输入不是可解码的位图。
|
|
case undecodableImage
|
|
}
|
|
|
|
/// 生成二维码 PNG 数据;`scale` 控制输出像素密度(每个模块的像素数)。
|
|
static func generatePNG(for payload: String, scale: CGFloat = 8) throws -> Data {
|
|
guard let filter = CIFilter(name: "CIQRCodeGenerator") else {
|
|
throw QrCodeError.generationFailed
|
|
}
|
|
filter.setValue(Data(payload.utf8), forKey: "inputMessage")
|
|
filter.setValue("M", forKey: "inputCorrectionLevel")
|
|
guard let output = filter.outputImage else { throw QrCodeError.generationFailed }
|
|
let scaled = output.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
|
|
let context = CIContext()
|
|
guard let cgImage = context.createCGImage(scaled, from: scaled.extent) else {
|
|
throw QrCodeError.generationFailed
|
|
}
|
|
let rep = NSBitmapImageRep(cgImage: cgImage)
|
|
guard let png = rep.representation(using: .png, properties: [:]) else {
|
|
throw QrCodeError.generationFailed
|
|
}
|
|
return png
|
|
}
|
|
|
|
/// 识别静态图片中的全部二维码/条码文本负载。
|
|
static func detectPayloads(in imageData: Data) throws -> [String] {
|
|
let request = VNDetectBarcodesRequest()
|
|
let handler = VNImageRequestHandler(data: imageData, options: [:])
|
|
do {
|
|
try handler.perform([request])
|
|
} catch {
|
|
throw QrCodeError.undecodableImage
|
|
}
|
|
return request.results?.compactMap(\.payloadStringValue) ?? []
|
|
}
|
|
}
|