Phase 4 剩余三项: - 首次引导向导(setup/first_run + ui/setup_wizard):服务自检(含 C# 助手共存 提示)→ 打印机检测与 HiTi 驱动安装指引(官网链接/重新检测,mac 与 win 文案 分支,允许无打印机完成)→ 开机自启开关 + scheme 注册;侧栏底部常驻设置区 (自启开关 + 重新运行引导) - 开机自启(setup/autostart):mac 写 LaunchAgent(RunAtLoad 一次性,不用 KeepAlive——托盘退出不能被拉回);Windows 走 HKCU Run(reg 命令免 win32 依赖) - URL scheme 闭环:单实例唤醒端口 38210(setup/single_instance)——zaxprint:// 唤起或重复双击启动第二进程时通知常驻实例弹窗后自退,跨平台一致(弃用 app_links 的 mac 事件转发不确定性);Windows 启动时幂等注册 HKCU scheme; 前端 cardPrinter.ts 加 launchAgent()(zaxprint://open),开卡抽屉离线时显示 "启动打印助手"按钮,点击唤起后 1s×6 轮询自动上线(biz 前端改动待用户提交) Phase 5: - tool/package_macos.sh:构建 → ad-hoc 就地签名(--deep,勿加 --options=runtime: hardened runtime 库校验会因 ad-hoc 无 Team ID 拒载 framework,实测踩坑)→ hdiutil dmg;实测产出 22MB dmg + 签名后冒烟通过 - windows/installer.iss:Inno Setup 安装包脚本(快捷方式/桌面图标/开机自启 Tasks/zaxprint scheme 注册与卸载清理/HiTi SDK DLL 随 Release 目录打包) 依赖 +url_launcher +shared_preferences;13 测试全过、analyze 零问题。
51 lines
1.7 KiB
Dart
51 lines
1.7 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
/// 单实例 + 唤醒端口:应用只允许一个常驻实例。
|
|
///
|
|
/// zaxprint:// 唤起或用户再次双击图标时,OS 会启动第二个进程——第二实例连上
|
|
/// 唤醒端口发送 wake 后立即退出,常驻实例收到后把主窗口带到前台。
|
|
/// (比 app_links 的方案更跨平台一致:mac/win 都只依赖回环 socket。)
|
|
class SingleInstance {
|
|
static const int wakePort = 38210;
|
|
|
|
/// 尝试成为主实例。
|
|
///
|
|
/// 返回 true = 本实例是主实例(已开始监听唤醒,[onWake] 在收到唤醒时回调);
|
|
/// 返回 false = 已有常驻实例(已通知其显示窗口),调用方应当退出进程。
|
|
static Future<bool> acquire({void Function()? onWake}) async {
|
|
try {
|
|
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, wakePort);
|
|
server.listen((socket) {
|
|
socket
|
|
.cast<List<int>>()
|
|
.transform(utf8.decoder)
|
|
.listen((message) {
|
|
if (message.contains('wake')) {
|
|
onWake?.call();
|
|
}
|
|
}, onDone: () {
|
|
socket.destroy();
|
|
});
|
|
}, onError: (_) {});
|
|
return true;
|
|
} on SocketException {
|
|
// 端口被占:已有常驻实例,通知它显示窗口
|
|
try {
|
|
final socket = await Socket.connect(
|
|
InternetAddress.loopbackIPv4,
|
|
wakePort,
|
|
timeout: const Duration(seconds: 2),
|
|
);
|
|
socket.add(utf8.encode('wake'));
|
|
await socket.flush();
|
|
await socket.close();
|
|
} catch (_) {
|
|
// 唤醒端口被非本应用进程占用等异常:放弃唤醒,按第二实例退出
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|