Files
desktop-plugin/lib/setup/driver_install.dart
T
dzc d475cac6ab feat: 向导内置 HiTi Mac 驱动一键安装(补齐计划 §3.2"驱动 dmg 放包内"设计)
- setup/driver_install.dart:检测已装(/Library/Printers/HiTi)+ 内置 dmg 定位
  (从 resolvedExecutable 反推 flutter_assets/assets/drivers/,缺失降级官网链接)
  + 静默安装(hdiutil 挂载→定位 pkg→osascript 提权 installer→finally detach;
  挂载点名带随机序号必须从 attach 输出解析)
- 向导第 2 步重做状态机:驱动未装 → "一键安装驱动(内置安装包)"按钮
  (安装中提示输管理员密码,失败显示原因);驱动已装但无队列 → 连接/添加队列
  引导;无内置 dmg 的构建回退官网链接
- assets/drivers/ 收驱动 dmg(.gitignore 不入库,打包脚本检查缺失并警告,
  当前从 card-printer-agent/dist 拷入 2.4.2 版,25MB)
- 实测:挂载/pkg 定位/detach 免密链路全通过;打包 47MB dmg 内含驱动,
  冒烟启动正常(服务已占主端口 38200);一键安装的提权弹窗待用户实测
2026-09-21 14:04:55 +08:00

79 lines
3.2 KiB
Dart

import 'dart:io';
import '../utils/command_runner.dart';
/// HiTi Mac 驱动(CUPS 过滤器)内置安装:App 资源携带驱动 dmg,
/// 向导一键挂载静默安装(osascript 提权一次),已装判断 = /Library/Printers/HiTi 存在。
/// Windows 不适用(PavoApi 直打,官方驱动可选)。
class DriverInstall {
static const installedMarker = '/Library/Printers/HiTi';
static bool get isSupported => Platform.isMacOS;
/// 驱动是否已安装。
static bool isInstalled() => Directory(installedMarker).existsSync();
/// 内置驱动 dmg 的文件系统路径;未打包携带(如开发运行/产物缺失)返回 null。
///
/// Flutter assets 在 .app 内的真实路径:
/// [app]/Contents/Frameworks/App.framework/Resources/flutter_assets/[asset]
/// 从可执行文件位置反推(exe 在 [app]/Contents/MacOS/)。
static String? bundledDmgPath() {
if (!Platform.isMacOS) return null;
final exe = Platform.resolvedExecutable;
final exeDir = File(exe).parent.path; // .../Contents/MacOS
final driversDir =
'$exeDir/../Frameworks/App.framework/Resources/flutter_assets/assets/drivers';
final dir = Directory(driversDir);
if (!dir.existsSync()) return null;
final dmgs = dir
.listSync()
.whereType<File>()
.where((f) => f.path.toLowerCase().endsWith('.dmg'))
.toList();
return dmgs.isEmpty ? null : dmgs.first.path;
}
/// 从内置 dmg 静默安装驱动(挂载 → 找 pkg → osascript 提权 installer → 卸载卷)。
/// 提权弹窗由系统显示(一次管理员密码),超时 3 分钟。
static Future<void> installFromDmg(String dmgPath, {void Function(String line)? onLog}) async {
final log = onLog ?? (_) {};
// 1. 只读挂载(挂载点带随机序号,必须从输出解析,不能拼名字)
final attachOut = await runCommand('hdiutil', ['attach', '-nobrowse', '-readonly', dmgPath],
timeoutMs: 30000);
final match = RegExp(r'/Volumes/[^\t\n]+').allMatches(attachOut);
if (match.isEmpty) {
throw StateError('驱动 dmg 挂载失败');
}
final mountPoint = match.last.group(0)!;
log('驱动卷已挂载:$mountPoint');
try {
// 2. 找 pkg
final pkg = Directory(mountPoint)
.listSync()
.whereType<File>()
.where((f) => f.path.toLowerCase().endsWith('.pkg'))
.toList();
if (pkg.isEmpty) {
throw StateError('驱动 dmg 内未找到 .pkg 安装包');
}
final pkgPath = pkg.first.path;
// 3. osascript 提权安装(AppleScript 字符串内嵌套引号用 \" 转义)
final script =
'do shell script "installer -pkg \\"$pkgPath\\" -target /" '
'with administrator privileges with prompt "安装 HiTi 打印机驱动"';
log('开始安装驱动(请在系统弹窗中输入管理员密码)…');
await runCommand('osascript', ['-e', script], timeoutMs: 180000);
log('驱动安装完成');
} finally {
// 4. 卸载卷(失败不影响安装结果)
try {
await runCommand('hdiutil', ['detach', mountPoint, '-force'], tolerateExit: true);
} catch (_) {}
}
}
}