import 'dart:io'; import '../utils/command_runner.dart'; /// HiTi 打印驱动内置安装:App 资源携带驱动安装包,向导一键安装。 /// /// - macOS:CUPS 过滤器 pkg(dmg 内置),已装判断 = /Library/Printers/HiTi 存在, /// 安装 = 挂载 dmg → osascript 提权 installer → 卸载卷 /// - Windows:HiTi 官方驱动 exe(assets/drivers/*.exe 内置),UAC 提权运行; /// 已装判断 = 我们自己的注册表标记(HKCU\Software\zax\desktop-plugin, /// 系统级驱动安装状态无统一可查标志,安装成功后写标记最可靠) class DriverInstall { static const installedMarker = '/Library/Printers/HiTi'; static const _winMarkKey = r'HKCU\Software\zax\desktop-plugin'; static const _winMarkValue = 'HiTiDriverInstalled'; 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:异步查询安装标记。 static Future queryInstalled() async { if (!Platform.isWindows) return isInstalled(); final out = await runCommand( 'reg', ['query', _winMarkKey, '/v', _winMarkValue], tolerateExit: true, ); return out.contains('0x1'); } /// 内置驱动安装包的文件系统路径;未打包携带返回 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 弹窗中确认,安装向导可能需要点几次"下一步")…'); // -Verb RunAs 触发 UAC;-Wait 等安装程序退出(用户取消 UAC 时 PowerShell 报错) 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. 写安装标记(我们自己的注册表键;0x1 = 已装) await runCommand('reg', ['add', _winMarkKey, '/v', _winMarkValue, '/t', 'REG_DWORD', '/d', '1', '/f']); log('已记录驱动安装状态'); } 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; } }