import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; import 'package:ffi/ffi.dart'; class KRWindowsProcessUtil { /// 🔍 调试标志:设为 true 可以追踪所有命令执行(用于排查黑窗问题) static const bool _debugCommandExecution = true; // ← 开启调试 static Future runHidden(String executable, List arguments) async { if (!Platform.isWindows) { return Process.run(executable, arguments); } final result = await _runHiddenWindows(executable, arguments); return result; } static Future startHidden(String executable, List arguments) async { if (!Platform.isWindows) { final process = await Process.start(executable, arguments); return process.pid; } return _startHiddenWindows(executable, arguments); } static String _buildCommandLine(String executable, List arguments) { final parts = [_quoteArgument(executable)]; for (final arg in arguments) { parts.add(_quoteArgument(arg)); } return parts.join(' '); } static bool _shouldSearchPath(String executable) { if (executable.isEmpty) { return true; } return !(executable.contains('\\') || executable.contains('/') || executable.contains(':')); } static String _quoteArgument(String value) { if (value.isEmpty) { return '""'; } final needsQuotes = value.contains(' ') || value.contains('\t') || value.contains('"'); if (!needsQuotes) { return value; } final buffer = StringBuffer('"'); var backslashes = 0; for (var i = 0; i < value.length; i++) { final char = value[i]; if (char == '\\') { backslashes++; continue; } if (char == '"') { buffer.write('\\' * (backslashes * 2 + 1)); buffer.write('"'); backslashes = 0; continue; } if (backslashes > 0) { buffer.write('\\' * backslashes); backslashes = 0; } buffer.write(char); } if (backslashes > 0) { buffer.write('\\' * (backslashes * 2)); } buffer.write('"'); return buffer.toString(); } static Future _runHiddenWindows(String executable, List arguments) async { final stdoutPipe = _createPipe(); final stderrPipe = _createPipe(); final startupInfo = calloc(); final processInfo = calloc(); final commandLine = _buildCommandLine(executable, arguments).toNativeUtf16(); final applicationName = _shouldSearchPath(executable) ? nullptr : executable.toNativeUtf16(); final stdInput = _getStdInputHandle(); startupInfo.ref ..cb = sizeOf() ..dwFlags = STARTF_USESTDHANDLES ..hStdInput = stdInput ..hStdOutput = stdoutPipe.write ..hStdError = stderrPipe.write; final created = _CreateProcessW( applicationName, commandLine, nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, startupInfo, processInfo, ); calloc.free(commandLine); if (applicationName != nullptr) { calloc.free(applicationName); } if (created == 0) { _closeHandle(stdoutPipe.read); _closeHandle(stdoutPipe.write); _closeHandle(stderrPipe.read); _closeHandle(stderrPipe.write); calloc.free(startupInfo); calloc.free(processInfo); throw Exception('CreateProcessW failed: ${_GetLastError()}'); } _closeHandle(stdoutPipe.write); _closeHandle(stderrPipe.write); final output = await _collectOutput(processInfo.ref.hProcess, stdoutPipe.read, stderrPipe.read); final exitCode = _getExitCode(processInfo.ref.hProcess); _closeHandle(stdoutPipe.read); _closeHandle(stderrPipe.read); _closeHandle(processInfo.ref.hThread); _closeHandle(processInfo.ref.hProcess); final pid = processInfo.ref.dwProcessId; calloc.free(startupInfo); calloc.free(processInfo); return ProcessResult(pid, exitCode, output.stdout, output.stderr); } static Future _startHiddenWindows(String executable, List arguments) async { final startupInfo = calloc(); final processInfo = calloc(); final commandLine = _buildCommandLine(executable, arguments).toNativeUtf16(); final applicationName = _shouldSearchPath(executable) ? nullptr : executable.toNativeUtf16(); startupInfo.ref.cb = sizeOf(); final created = _CreateProcessW( applicationName, commandLine, nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, startupInfo, processInfo, ); calloc.free(commandLine); if (applicationName != nullptr) { calloc.free(applicationName); } if (created == 0) { calloc.free(startupInfo); calloc.free(processInfo); throw Exception('CreateProcessW failed: ${_GetLastError()}'); } final pid = processInfo.ref.dwProcessId; _closeHandle(processInfo.ref.hThread); _closeHandle(processInfo.ref.hProcess); calloc.free(startupInfo); calloc.free(processInfo); return pid; } static _Pipe _createPipe() { final readHandle = calloc>(); final writeHandle = calloc>(); final securityAttributes = calloc(); securityAttributes.ref ..nLength = sizeOf() ..bInheritHandle = TRUE ..lpSecurityDescriptor = nullptr; final created = _CreatePipe(readHandle, writeHandle, securityAttributes, 0); calloc.free(securityAttributes); if (created == 0) { calloc.free(readHandle); calloc.free(writeHandle); throw Exception('CreatePipe failed: ${_GetLastError()}'); } final readValue = readHandle.value; final writeValue = writeHandle.value; calloc.free(readHandle); calloc.free(writeHandle); final infoResult = _SetHandleInformation(readValue, HANDLE_FLAG_INHERIT, 0); if (infoResult == 0) { _closeHandle(readValue); _closeHandle(writeValue); throw Exception('SetHandleInformation failed: ${_GetLastError()}'); } return _Pipe(readValue, writeValue); } static Pointer _getStdInputHandle() { final handle = _GetStdHandle(STD_INPUT_HANDLE); if (handle == INVALID_HANDLE_VALUE || handle == 0) { return nullptr; } return Pointer.fromAddress(handle); } static Future<_ProcessOutput> _collectOutput( Pointer process, Pointer stdoutHandle, Pointer stderrHandle, ) async { final stdoutBuilder = BytesBuilder(); final stderrBuilder = BytesBuilder(); while (true) { final stdoutRead = _drainPipe(stdoutHandle, stdoutBuilder); final stderrRead = _drainPipe(stderrHandle, stderrBuilder); final waitResult = _WaitForSingleObject(process, 0); if (waitResult == WAIT_OBJECT_0) { break; } if (waitResult == WAIT_FAILED) { throw Exception('WaitForSingleObject failed: ${_GetLastError()}'); } if (!stdoutRead && !stderrRead) { await Future.delayed(const Duration(milliseconds: 10)); } else { await Future.delayed(Duration.zero); } } while (_drainPipe(stdoutHandle, stdoutBuilder) || _drainPipe(stderrHandle, stderrBuilder)) { await Future.delayed(Duration.zero); } return _ProcessOutput( _decodeOutput(stdoutBuilder), _decodeOutput(stderrBuilder), ); } static bool _drainPipe(Pointer handle, BytesBuilder builder) { final buffer = calloc(4096); final bytesRead = calloc(); final available = calloc(); var didRead = false; while (true) { final peekOk = _PeekNamedPipe(handle, nullptr, 0, nullptr, available, nullptr); if (peekOk == 0 || available.value == 0) { break; } final toRead = available.value < 4096 ? available.value : 4096; final ok = _ReadFile(handle, buffer.cast(), toRead, bytesRead, nullptr); final read = ok == 0 ? 0 : bytesRead.value; if (read == 0) { break; } builder.add(buffer.asTypedList(read)); didRead = true; } calloc.free(buffer); calloc.free(bytesRead); calloc.free(available); return didRead; } static String _decodeOutput(BytesBuilder builder) { if (builder.length == 0) { return ''; } final bytes = builder.toBytes(); try { return systemEncoding.decode(bytes); } catch (_) { return utf8.decode(bytes, allowMalformed: true); } } static int _getExitCode(Pointer process) { final exitCode = calloc(); final ok = _GetExitCodeProcess(process, exitCode); final code = ok == 0 ? -1 : exitCode.value; calloc.free(exitCode); return code; } static void _closeHandle(Pointer handle) { if (handle == nullptr) { return; } _CloseHandle(handle); } // 🔧 WinINet API helpers for proxy settings /// 查询当前系统代理设置 static String? queryWindowsProxyServer() { if (!Platform.isWindows) return null; try { final bufferSize = calloc(); bufferSize.value = sizeOf(); final proxyInfo = calloc(); final result = _InternetQueryOptionW(nullptr, INTERNET_OPTION_PROXY, proxyInfo.cast(), bufferSize); if (result == 0) { calloc.free(bufferSize); calloc.free(proxyInfo); return null; } final proxyServer = proxyInfo.ref.lpszProxy.toDartString(); calloc.free(bufferSize); calloc.free(proxyInfo); return proxyServer.isEmpty ? null : proxyServer; } catch (e) { return null; } } /// 设置系统代理 static bool setWindowsProxyServer(String? server) { if (!Platform.isWindows) return false; try { final proxyInfo = calloc(); if (server != null && server.isNotEmpty) { // 设置代理模式 proxyInfo.ref.dwAccessType = INTERNET_OPEN_TYPE_PROXY; proxyInfo.ref.lpszProxy = server.toNativeUtf16(); proxyInfo.ref.lpszProxyBypass = ''.toNativeUtf16(); } else { // 禁用代理 proxyInfo.ref.dwAccessType = INTERNET_OPEN_TYPE_DIRECT; proxyInfo.ref.lpszProxy = nullptr; proxyInfo.ref.lpszProxyBypass = nullptr; } final result = _InternetSetOptionW( nullptr, INTERNET_OPTION_PROXY, proxyInfo.cast(), sizeOf(), ); if (server != null && server.isNotEmpty) { calloc.free(proxyInfo.ref.lpszProxy); calloc.free(proxyInfo.ref.lpszProxyBypass); } calloc.free(proxyInfo); return result != 0; } catch (e) { return false; } } /// 禁用系统代理 static bool disableWindowsProxy() { return setWindowsProxyServer(null); } } class _Pipe { final Pointer read; final Pointer write; _Pipe(this.read, this.write); } class _ProcessOutput { final String stdout; final String stderr; _ProcessOutput(this.stdout, this.stderr); } const int TRUE = 1; const int FALSE = 0; const int STARTF_USESTDHANDLES = 0x00000100; const int CREATE_NO_WINDOW = 0x08000000; const int HANDLE_FLAG_INHERIT = 0x00000001; const int WAIT_OBJECT_0 = 0x00000000; const int WAIT_FAILED = 0xFFFFFFFF; const int STD_INPUT_HANDLE = -10; const int INVALID_HANDLE_VALUE = -1; final class SECURITY_ATTRIBUTES extends Struct { @Uint32() external int nLength; external Pointer lpSecurityDescriptor; @Int32() external int bInheritHandle; } final class STARTUPINFO extends Struct { @Uint32() external int cb; external Pointer lpReserved; external Pointer lpDesktop; external Pointer lpTitle; @Uint32() external int dwX; @Uint32() external int dwY; @Uint32() external int dwXSize; @Uint32() external int dwYSize; @Uint32() external int dwXCountChars; @Uint32() external int dwYCountChars; @Uint32() external int dwFillAttribute; @Uint32() external int dwFlags; @Uint16() external int wShowWindow; @Uint16() external int cbReserved2; external Pointer lpReserved2; external Pointer hStdInput; external Pointer hStdOutput; external Pointer hStdError; } final class PROCESS_INFORMATION extends Struct { external Pointer hProcess; external Pointer hThread; @Uint32() external int dwProcessId; @Uint32() external int dwThreadId; } final DynamicLibrary _kernel32 = DynamicLibrary.open('kernel32.dll'); final _CreatePipe = _kernel32.lookupFunction< Int32 Function(Pointer>, Pointer>, Pointer, Uint32), int Function(Pointer>, Pointer>, Pointer, int)>( 'CreatePipe', ); final _SetHandleInformation = _kernel32.lookupFunction< Int32 Function(Pointer, Uint32, Uint32), int Function(Pointer, int, int)>( 'SetHandleInformation', ); final _CreateProcessW = _kernel32.lookupFunction< Int32 Function( Pointer, Pointer, Pointer, Pointer, Int32, Uint32, Pointer, Pointer, Pointer, Pointer, ), int Function( Pointer, Pointer, Pointer, Pointer, int, int, Pointer, Pointer, Pointer, Pointer, )>( 'CreateProcessW', ); final _PeekNamedPipe = _kernel32.lookupFunction< Int32 Function(Pointer, Pointer, Uint32, Pointer, Pointer, Pointer), int Function(Pointer, Pointer, int, Pointer, Pointer, Pointer)>( 'PeekNamedPipe', ); final _ReadFile = _kernel32.lookupFunction< Int32 Function(Pointer, Pointer, Uint32, Pointer, Pointer), int Function(Pointer, Pointer, int, Pointer, Pointer)>( 'ReadFile', ); final _CloseHandle = _kernel32.lookupFunction< Int32 Function(Pointer), int Function(Pointer)>( 'CloseHandle', ); final _WaitForSingleObject = _kernel32.lookupFunction< Uint32 Function(Pointer, Uint32), int Function(Pointer, int)>( 'WaitForSingleObject', ); final _GetStdHandle = _kernel32.lookupFunction< IntPtr Function(Int32), int Function(int)>( 'GetStdHandle', ); final _GetExitCodeProcess = _kernel32.lookupFunction< Int32 Function(Pointer, Pointer), int Function(Pointer, Pointer)>( 'GetExitCodeProcess', ); final _GetLastError = _kernel32.lookupFunction< Uint32 Function(), int Function()>( 'GetLastError', ); // 🔧 WinINet API for proxy settings - 用于替代 reg 命令,消除黑屏 final DynamicLibrary _wininet = DynamicLibrary.open('wininet.dll'); const int INTERNET_OPTION_PROXY = 38; const int INTERNET_OPEN_TYPE_PROXY = 3; const int INTERNET_OPEN_TYPE_DIRECT = 1; final class INTERNET_PROXY_INFO extends Struct { @Int32() external int dwAccessType; external Pointer lpszProxy; external Pointer lpszProxyBypass; } /// WinINet InternetSetOption API - 用于设置系统代理 final _InternetSetOptionW = _wininet.lookupFunction< Int32 Function(Pointer, Uint32, Pointer, Uint32), int Function(Pointer, int, Pointer, int)>( 'InternetSetOptionW', ); /// WinINet InternetQueryOption API - 用于查询系统代理 final _InternetQueryOptionW = _wininet.lookupFunction< Int32 Function(Pointer, Uint32, Pointer, Pointer), int Function(Pointer, int, Pointer, Pointer)>( 'InternetQueryOptionW', );