603 lines
16 KiB
Dart
603 lines
16 KiB
Dart
import 'dart:convert';
|
||
import 'dart:ffi';
|
||
import 'dart:io';
|
||
import 'dart:typed_data';
|
||
|
||
import 'package:ffi/ffi.dart';
|
||
import 'kr_file_logger.dart';
|
||
|
||
class KRWindowsProcessUtil {
|
||
/// 🔍 调试标志:设为 true 可以追踪所有命令执行(用于排查黑窗问题)
|
||
static const bool _debugCommandExecution = true; // ← 开启调试
|
||
|
||
static Future<ProcessResult> runHidden(String executable, List<String> arguments) async {
|
||
final timestamp = DateTime.now().toString();
|
||
// 🔧 使用非阻塞日志(不 await),避免影响执行时序
|
||
KRFileLogger.log('[黑屏调试] [KRWindowsProcessUtil] [$timestamp] 🔧 runHidden 调用: $executable ${arguments.join(" ")}');
|
||
|
||
if (!Platform.isWindows) {
|
||
KRFileLogger.log('[黑屏调试] [KRWindowsProcessUtil] [$timestamp] ⚠️ 非 Windows,使用 Process.run(可能黑窗)');
|
||
return Process.run(executable, arguments);
|
||
}
|
||
|
||
KRFileLogger.log('[黑屏调试] [KRWindowsProcessUtil] [$timestamp] ✅ Windows,使用 CreateProcessW(无黑窗)');
|
||
final result = await _runHiddenWindows(executable, arguments);
|
||
KRFileLogger.log('[黑屏调试] [KRWindowsProcessUtil] [$timestamp] 📤 执行完成: exitCode=${result.exitCode}');
|
||
return result;
|
||
}
|
||
|
||
static Future<int> startHidden(String executable, List<String> 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<String> arguments) {
|
||
final parts = <String>[_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<ProcessResult> _runHiddenWindows(String executable, List<String> arguments) async {
|
||
final stdoutPipe = _createPipe();
|
||
final stderrPipe = _createPipe();
|
||
|
||
final startupInfo = calloc<STARTUPINFO>();
|
||
final processInfo = calloc<PROCESS_INFORMATION>();
|
||
final commandLine = _buildCommandLine(executable, arguments).toNativeUtf16();
|
||
final applicationName = _shouldSearchPath(executable) ? nullptr : executable.toNativeUtf16();
|
||
final stdInput = _getStdInputHandle();
|
||
|
||
startupInfo.ref
|
||
..cb = sizeOf<STARTUPINFO>()
|
||
..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<int> _startHiddenWindows(String executable, List<String> arguments) async {
|
||
final startupInfo = calloc<STARTUPINFO>();
|
||
final processInfo = calloc<PROCESS_INFORMATION>();
|
||
final commandLine = _buildCommandLine(executable, arguments).toNativeUtf16();
|
||
final applicationName = _shouldSearchPath(executable) ? nullptr : executable.toNativeUtf16();
|
||
|
||
startupInfo.ref.cb = sizeOf<STARTUPINFO>();
|
||
|
||
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<Pointer<Void>>();
|
||
final writeHandle = calloc<Pointer<Void>>();
|
||
final securityAttributes = calloc<SECURITY_ATTRIBUTES>();
|
||
securityAttributes.ref
|
||
..nLength = sizeOf<SECURITY_ATTRIBUTES>()
|
||
..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<Void> _getStdInputHandle() {
|
||
final handle = _GetStdHandle(STD_INPUT_HANDLE);
|
||
if (handle == INVALID_HANDLE_VALUE || handle == 0) {
|
||
return nullptr;
|
||
}
|
||
return Pointer<Void>.fromAddress(handle);
|
||
}
|
||
|
||
static Future<_ProcessOutput> _collectOutput(
|
||
Pointer<Void> process,
|
||
Pointer<Void> stdoutHandle,
|
||
Pointer<Void> 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<void>.delayed(Duration.zero);
|
||
}
|
||
}
|
||
|
||
while (_drainPipe(stdoutHandle, stdoutBuilder) || _drainPipe(stderrHandle, stderrBuilder)) {
|
||
await Future<void>.delayed(Duration.zero);
|
||
}
|
||
|
||
return _ProcessOutput(
|
||
_decodeOutput(stdoutBuilder),
|
||
_decodeOutput(stderrBuilder),
|
||
);
|
||
}
|
||
|
||
static bool _drainPipe(Pointer<Void> handle, BytesBuilder builder) {
|
||
final buffer = calloc<Uint8>(4096);
|
||
final bytesRead = calloc<Uint32>();
|
||
final available = calloc<Uint32>();
|
||
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<Void>(), 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<Void> process) {
|
||
final exitCode = calloc<Uint32>();
|
||
final ok = _GetExitCodeProcess(process, exitCode);
|
||
final code = ok == 0 ? -1 : exitCode.value;
|
||
calloc.free(exitCode);
|
||
return code;
|
||
}
|
||
|
||
static void _closeHandle(Pointer<Void> 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<Uint32>();
|
||
bufferSize.value = sizeOf<INTERNET_PROXY_INFO>();
|
||
|
||
final proxyInfo = calloc<INTERNET_PROXY_INFO>();
|
||
|
||
final result = _InternetQueryOptionW(nullptr, INTERNET_OPTION_PROXY, proxyInfo.cast<Void>(), 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<INTERNET_PROXY_INFO>();
|
||
|
||
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<Void>(),
|
||
sizeOf<INTERNET_PROXY_INFO>(),
|
||
);
|
||
|
||
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<Void> read;
|
||
final Pointer<Void> 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<Void> lpSecurityDescriptor;
|
||
|
||
@Int32()
|
||
external int bInheritHandle;
|
||
}
|
||
|
||
final class STARTUPINFO extends Struct {
|
||
@Uint32()
|
||
external int cb;
|
||
|
||
external Pointer<Utf16> lpReserved;
|
||
|
||
external Pointer<Utf16> lpDesktop;
|
||
|
||
external Pointer<Utf16> 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<Uint8> lpReserved2;
|
||
|
||
external Pointer<Void> hStdInput;
|
||
|
||
external Pointer<Void> hStdOutput;
|
||
|
||
external Pointer<Void> hStdError;
|
||
}
|
||
|
||
final class PROCESS_INFORMATION extends Struct {
|
||
external Pointer<Void> hProcess;
|
||
|
||
external Pointer<Void> hThread;
|
||
|
||
@Uint32()
|
||
external int dwProcessId;
|
||
|
||
@Uint32()
|
||
external int dwThreadId;
|
||
}
|
||
|
||
final DynamicLibrary _kernel32 = DynamicLibrary.open('kernel32.dll');
|
||
|
||
final _CreatePipe = _kernel32.lookupFunction<
|
||
Int32 Function(Pointer<Pointer<Void>>, Pointer<Pointer<Void>>, Pointer<SECURITY_ATTRIBUTES>, Uint32),
|
||
int Function(Pointer<Pointer<Void>>, Pointer<Pointer<Void>>, Pointer<SECURITY_ATTRIBUTES>, int)>(
|
||
'CreatePipe',
|
||
);
|
||
|
||
final _SetHandleInformation = _kernel32.lookupFunction<
|
||
Int32 Function(Pointer<Void>, Uint32, Uint32),
|
||
int Function(Pointer<Void>, int, int)>(
|
||
'SetHandleInformation',
|
||
);
|
||
|
||
final _CreateProcessW = _kernel32.lookupFunction<
|
||
Int32 Function(
|
||
Pointer<Utf16>,
|
||
Pointer<Utf16>,
|
||
Pointer<SECURITY_ATTRIBUTES>,
|
||
Pointer<SECURITY_ATTRIBUTES>,
|
||
Int32,
|
||
Uint32,
|
||
Pointer<Void>,
|
||
Pointer<Utf16>,
|
||
Pointer<STARTUPINFO>,
|
||
Pointer<PROCESS_INFORMATION>,
|
||
),
|
||
int Function(
|
||
Pointer<Utf16>,
|
||
Pointer<Utf16>,
|
||
Pointer<SECURITY_ATTRIBUTES>,
|
||
Pointer<SECURITY_ATTRIBUTES>,
|
||
int,
|
||
int,
|
||
Pointer<Void>,
|
||
Pointer<Utf16>,
|
||
Pointer<STARTUPINFO>,
|
||
Pointer<PROCESS_INFORMATION>,
|
||
)>(
|
||
'CreateProcessW',
|
||
);
|
||
|
||
final _PeekNamedPipe = _kernel32.lookupFunction<
|
||
Int32 Function(Pointer<Void>, Pointer<Void>, Uint32, Pointer<Uint32>, Pointer<Uint32>, Pointer<Uint32>),
|
||
int Function(Pointer<Void>, Pointer<Void>, int, Pointer<Uint32>, Pointer<Uint32>, Pointer<Uint32>)>(
|
||
'PeekNamedPipe',
|
||
);
|
||
|
||
final _ReadFile = _kernel32.lookupFunction<
|
||
Int32 Function(Pointer<Void>, Pointer<Void>, Uint32, Pointer<Uint32>, Pointer<Void>),
|
||
int Function(Pointer<Void>, Pointer<Void>, int, Pointer<Uint32>, Pointer<Void>)>(
|
||
'ReadFile',
|
||
);
|
||
|
||
final _CloseHandle = _kernel32.lookupFunction<
|
||
Int32 Function(Pointer<Void>),
|
||
int Function(Pointer<Void>)>(
|
||
'CloseHandle',
|
||
);
|
||
|
||
final _WaitForSingleObject = _kernel32.lookupFunction<
|
||
Uint32 Function(Pointer<Void>, Uint32),
|
||
int Function(Pointer<Void>, int)>(
|
||
'WaitForSingleObject',
|
||
);
|
||
|
||
final _GetStdHandle = _kernel32.lookupFunction<
|
||
IntPtr Function(Int32),
|
||
int Function(int)>(
|
||
'GetStdHandle',
|
||
);
|
||
|
||
final _GetExitCodeProcess = _kernel32.lookupFunction<
|
||
Int32 Function(Pointer<Void>, Pointer<Uint32>),
|
||
int Function(Pointer<Void>, Pointer<Uint32>)>(
|
||
'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<Utf16> lpszProxy;
|
||
|
||
external Pointer<Utf16> lpszProxyBypass;
|
||
}
|
||
|
||
/// WinINet InternetSetOption API - 用于设置系统代理
|
||
final _InternetSetOptionW = _wininet.lookupFunction<
|
||
Int32 Function(Pointer<Void>, Uint32, Pointer<Void>, Uint32),
|
||
int Function(Pointer<Void>, int, Pointer<Void>, int)>(
|
||
'InternetSetOptionW',
|
||
);
|
||
|
||
/// WinINet InternetQueryOption API - 用于查询系统代理
|
||
final _InternetQueryOptionW = _wininet.lookupFunction<
|
||
Int32 Function(Pointer<Void>, Uint32, Pointer<Void>, Pointer<Uint32>),
|
||
int Function(Pointer<Void>, int, Pointer<Void>, Pointer<Uint32>)>(
|
||
'InternetQueryOptionW',
|
||
);
|