import 'dart:io'; import '../utils/command_runner.dart'; /// HiTi 打印驱动内置安装:App 资源携带驱动安装包,向导一键安装。 /// /// - macOS:CUPS 过滤器 pkg(dmg 内置),已装判断 = /Library/Printers/HiTi 存在, /// 安装 = 挂载 dmg → osascript 提权 installer → 卸载卷 /// - Windows:HiTi 官方驱动整目录 zip 内置,已装判断 = pnputil 枚举驱动商店 /// (实时校验,安装器壳提前退出不会误判),安装 = 解压 → UAC 提权 Setup64 → /// pnputil 校验通过才算成功 class DriverInstall { static const installedMarker = '/Library/Printers/HiTi'; static bool get isSupported => Platform.isMacOS || Platform.isWindows; /// 驱动是否已安装(macOS:驱动目录;Windows:安装标记,同步场景请用 queryInstalled)。 static bool isInstalled() { if (Platform.isMacOS) return Directory(installedMarker).existsSync(); return false; // Windows 走异步 queryInstalled(注册表查询需要子进程) } /// Windows:pnputil 实时校验驱动商店(安装器是自解压壳、可能假成功, /// 注册表标记会被失败的安装误写,故以系统驱动商店为唯一真源)。 static Future queryInstalled() async { if (!Platform.isWindows) return isInstalled(); final drivers = await runCommand('pnputil', ['/enum-drivers'], tolerateExit: true, timeoutMs: 60000); final lower = drivers.toLowerCase(); return lower.contains('cs2xxhcd') || lower.contains('htitahcd') || lower.contains('hiti'); } /// 内置驱动安装包的文件系统路径;未打包携带返回 null。 /// /// Flutter assets 在包内的真实路径(两平台结构不同): /// - macOS:[exe]/../Frameworks/App.framework/Resources/flutter_assets /// - Windows:[exe]/data/flutter_assets static String? bundledInstallerPath() { if (Platform.isMacOS) { final exeDir = File(Platform.resolvedExecutable).parent.path; return _firstFile( '$exeDir/../Frameworks/App.framework/Resources/flutter_assets/assets/drivers', '.dmg'); } if (Platform.isWindows) { final exeDir = File(Platform.resolvedExecutable).parent.path; return _firstFile('$exeDir/data/flutter_assets/assets/drivers', '.zip'); } return null; } static String? _firstFile(String dirPath, String ext) { final dir = Directory(dirPath); if (!dir.existsSync()) return null; final files = dir .listSync() .whereType() .where((f) => f.path.toLowerCase().endsWith(ext)) .toList(); return files.isEmpty ? null : files.first.path; } /// 静默安装(macOS)/提权安装(Windows,UAC 确认一次)。 static Future install(String installerPath, {void Function(String line)? onLog}) async { final log = onLog ?? (_) {}; if (Platform.isMacOS) { await _installMac(installerPath, log); return; } if (Platform.isWindows) { await _installWindows(installerPath, log); return; } throw UnsupportedError('当前平台不支持驱动安装'); } // ---------------- macOS:dmg → pkg → osascript 提权 ---------------- static Future _installMac(String dmgPath, void Function(String) log) async { // 挂载点带随机序号,必须从 attach 输出解析,不能拼名字 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 { final pkg = Directory(mountPoint) .listSync() .whereType() .where((f) => f.path.toLowerCase().endsWith('.pkg')) .toList(); if (pkg.isEmpty) { throw StateError('驱动 dmg 内未找到 .pkg 安装包'); } final pkgPath = pkg.first.path; 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 { try { await runCommand('hdiutil', ['detach', mountPoint, '-force'], tolerateExit: true); } catch (_) {} } } // ---------------- Windows:解压 zip → UAC 提权 Setup64 → 写安装标记 ---------------- static Future _installWindows(String zipPath, void Function(String) log) async { // 1. 解压到临时目录(驱动是"安装器 + 数据文件"的多文件结构,内置为 zip) final extractDir = '${Directory.systemTemp.path}/zax-hiti-driver-${DateTime.now().millisecondsSinceEpoch}'; log('解压驱动安装包…'); await runCommand('powershell', [ '-Command', 'Expand-Archive -Path "$zipPath" -DestinationPath "$extractDir" -Force' ], timeoutMs: 120000); try { // 2. 递归找安装器(优先 64 位 Setup64.exe,兜底 autorun.exe) final setup = _findFileDeep(extractDir, 'setup64.exe') ?? _findFileDeep(extractDir, 'setup.exe') ?? _findFileDeep(extractDir, 'autorun.exe'); if (setup == null) { throw StateError('驱动包内未找到安装程序(Setup64.exe)'); } log('开始安装 HiTi 打印机驱动(请在 UAC 弹窗中确认,安装向导可能需要点几次"下一步")…'); // 注意:HiTi 的 Setup64 是自解压壳(InstallShield 类),-Wait 等的是壳进程, // 内层安装向导可能仍在运行——安装是否成功必须以后续的驱动校验为准 final script = 'Start-Process -FilePath "$setup" -Verb RunAs -Wait'; await runCommand('powershell', ['-Command', script], timeoutMs: 600000); log('安装程序已退出,正在校验驱动是否装入系统…'); } finally { // 3. 清理临时目录(失败不影响安装结果) try { await Directory(extractDir).delete(recursive: true); } catch (_) {} } // 4. 校验:驱动是否真的进了驱动商店(pnputil 枚举,匹配 htitahcd/CS2xx) await Future.delayed(const Duration(seconds: 3)); // 给安装收尾一点时间 final drivers = await runCommand('pnputil', ['/enum-drivers'], tolerateExit: true, timeoutMs: 60000); final lower = drivers.toLowerCase(); final installed = lower.contains('cs2xxhcd') || lower.contains('htitahcd') || lower.contains('hiti'); if (!installed) { throw StateError( '驱动校验未通过:系统中未发现 HiTi 驱动(安装器可能未真正完成,请重试,' '或手动运行驱动包 CS2xx 目录内的 Setup64.exe 完成安装)'); } log('校验通过:HiTi 驱动已装入系统'); log('驱动安装完成——请拔掉打印机 USB 重新插上(驱动绑定需要设备重新枚举),然后重新检测'); } static String? _findFileDeep(String root, String lowerName) { final hits = Directory(root) .listSync(recursive: true, followLinks: false) .whereType() .where((f) => f.uri.pathSegments.last.toLowerCase() == lowerName) .toList(); return hits.isEmpty ? null : hits.first.path; } }