feat: 饭卡打印助手 Flutter 版(Phase 1/2:协议服务器 + macOS CUPS 真机打印)

- 协议与 C# 版 card-printer-agent 逐字段对齐(38200-38202 顺延、CORS *、串行任务队列、30 分钟保留)
- macOS CUPS 后端:lpoptions 队列发现 + lp 参数列表提交(LC_ALL=C 防本地化粘连坑)
- --mock 模拟模式 + bin/agent.dart headless 入口 + 测试卡生成工具
- 真机验收:HiTi CS-200e status 就绪 + 真实出卡;macOS 通用二进制构建通过
- App Sandbox 已关闭(服务需绑回环端口并调用 lp 子进程);zaxprint URL scheme 已注册
This commit is contained in:
dzc committed 2026-09-21 12:03:27 +08:00
commit 689a07ca44
46 files changed
+2986

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
import 'dart:async';
import 'print/backend.dart';
import 'print/mock_backend.dart';
import 'server/http_server.dart';
import 'server/print_queue.dart';
/// 启动参数:--mock 模拟打印;--port=NNN / --port NNN 指定监听端口(默认 38200-38202 顺延)。
class AgentOptions {
AgentOptions({this.mock = false, this.port});
final bool mock;
final int? port;
factory AgentOptions.parse(List<String> args) {
var mock = false;
int? port;
for (var i = 0; i < args.length; i++) {
final arg = args[i];
if (arg == '--mock') {
mock = true;
} else if (arg.startsWith('--port=')) {
port = int.tryParse(arg.substring('--port='.length));
} else if ((arg == '--port' || arg == '-p') && i + 1 < args.length) {
port = int.tryParse(args[++i]);
}
}
return AgentOptions(mock: mock, port: port);
}
}
/// 运行日志环形缓冲(GUI 展示用),同时打到 stdout。
class AgentLog {
final lines = <String>[];
final _controller = StreamController<String>.broadcast();
Stream<String> get stream => _controller.stream;
void write(String line) {
final stamped = '${DateTime.now().toIso8601String().substring(11, 19)} $line';
lines.add(stamped);
if (lines.length > 500) {
lines.removeRange(0, lines.length - 500);
}
// ignore: avoid_print
print(stamped);
_controller.add(stamped);
}
void dispose() => _controller.close();
}
/// 助手核心:组合打印后端、任务队列与 HTTP 服务(Flutter 窗口与 headless 入口共用)。
class Agent {
Agent(this.options) : log = AgentLog();
final AgentOptions options;
final AgentLog log;
late final PrintQueue queue;
late final PrintBackend backend;
late final AgentHttpServer server;
int? port;
Future<int> start() async {
final backendName = osBackendName();
backend = options.mock ? MockBackend(onLog: log.write) : createOsBackend(onLog: log.write);
queue = PrintQueue(backend, onLog: log.write);
server = AgentHttpServer(
queue: queue,
backend: backend,
mock: options.mock,
manifestBackendName: backendName,
);
final ports = options.port == null ? const [38200, 38201, 38202] : <int>[options.port!];
port = await server.start(candidatePorts: ports);
log.write('饭卡打印助手已启动 http://127.0.0.1:$port'
'(后端=$backendName,mock=${options.mock},agent=desktop-plugin)');
return port!;
}
Future<void> stop() async {
await server.stop();
queue.dispose();
log.dispose();
}
}