fix: 修改样式
This commit is contained in:
@@ -53,20 +53,21 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
return p.join("libcore", libName);
|
||||
}
|
||||
|
||||
// 🔧 修复:开发环境使用绝对路径
|
||||
// 尝试开发环境路径(相对于当前工作目录)
|
||||
final devPath = p.join("libcore", "bin", libName);
|
||||
if (kDebugMode) {
|
||||
print('🔍 [FFI] 检查开发环境路径: $devPath');
|
||||
print('🔍 [FFI] 当前工作目录: ${Directory.current.path}');
|
||||
print('🔍 [FFI] 文件是否存在: ${File(devPath).existsSync()}');
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
// 当前 app binary 路径
|
||||
final execPath = File(Platform.resolvedExecutable).absolute.path;
|
||||
|
||||
if (File(devPath).existsSync()) {
|
||||
if (kDebugMode) {
|
||||
print('✅ [FFI] 使用开发环境路径: $devPath');
|
||||
// App binary 在 Contents/MacOS, libcore 在 Contents/Frameworks
|
||||
final bundleDir = p.dirname(p.dirname(execPath)); // 上两级到 Contents
|
||||
final frameworksPath = p.join(bundleDir, 'Frameworks', libName);
|
||||
|
||||
if (File(frameworksPath).existsSync()) {
|
||||
if (kDebugMode) print('✅ [FFI] macOS bundle 相对路径: $frameworksPath');
|
||||
return frameworksPath;
|
||||
} else {
|
||||
print('⚠️ [FFI] 未找到 libcore.dylib,fallback 到默认路径: $libName');
|
||||
return libName;
|
||||
}
|
||||
return devPath;
|
||||
}
|
||||
|
||||
// 生产环境:使用相对路径(bundle中的路径)
|
||||
@@ -82,22 +83,28 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
print('🚀 [FFI] init() 开始');
|
||||
}
|
||||
loggy.debug("initializing");
|
||||
final box = FFISingboxService._box;
|
||||
|
||||
box.setupOnce(NativeApi.initializeApiDLData);
|
||||
// 注意:setupOnce 会在 worker isolate 中调用(见 _ffiLoadLibrary)
|
||||
// 在主 isolate 中调用会导致阻塞,因此这里跳过
|
||||
if (kDebugMode) {
|
||||
print('⏭️ [FFI] 跳过主 isolate 中的 setupOnce(将在 worker isolate 中执行)');
|
||||
}
|
||||
|
||||
print('📡 [FFI] 主 Isolate setupOnce 执行完毕,Port 已创建');
|
||||
if (kDebugMode) {
|
||||
print('📡 [FFI] 创建 ReceivePort');
|
||||
}
|
||||
_statusReceiver = ReceivePort('service status receiver');
|
||||
print('📡 [FFI] Dart port created: ${_statusReceiver.sendPort.nativePort}');
|
||||
|
||||
if (kDebugMode) {
|
||||
print('🔄 [FFI] 设置状态流');
|
||||
}
|
||||
final source = _statusReceiver.asBroadcastStream().map((event) => jsonDecode(event as String)).map(SingboxStatus.fromEvent);
|
||||
final source = _statusReceiver
|
||||
.asBroadcastStream()
|
||||
.map((event) => jsonDecode(event as String))
|
||||
.map(SingboxStatus.fromEvent);
|
||||
_status = ValueConnectableStream.seeded(
|
||||
source,
|
||||
const SingboxStopped(),
|
||||
@@ -125,7 +132,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
_logger.debug('[黑屏调试] setup() 开始调用 libcore.dll - $startTime');
|
||||
|
||||
final err = await IsolateWorker().execute(
|
||||
() => _ffiSetup(baseDir, workingDir, tempDir, port, debugFlag),
|
||||
() => _ffiSetup(baseDir, workingDir, tempDir, port, debugFlag),
|
||||
allowSyncFallback: false,
|
||||
);
|
||||
|
||||
@@ -137,9 +144,15 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
_logger.error('[黑屏调试] setup() 错误: $err');
|
||||
return left(err);
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('✅ [FFI][ORDER] setup() finished OK');
|
||||
}
|
||||
return right(unit);
|
||||
} catch (e) {
|
||||
_logger.error('[黑屏调试] setup() 异常: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ [FFI][ORDER] setup() threw: $e');
|
||||
}
|
||||
return left(e.toString());
|
||||
}
|
||||
});
|
||||
@@ -147,15 +160,15 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> validateConfigByPath(
|
||||
String path,
|
||||
String tempPath,
|
||||
bool debug,
|
||||
) {
|
||||
String path,
|
||||
String tempPath,
|
||||
bool debug,
|
||||
) {
|
||||
final debugFlag = debug ? 1 : 0;
|
||||
return TaskEither(() async {
|
||||
try {
|
||||
final err = await IsolateWorker().execute(
|
||||
() => _ffiValidateConfig(path, tempPath, debugFlag),
|
||||
() => _ffiValidateConfig(path, tempPath, debugFlag),
|
||||
allowSyncFallback: false,
|
||||
);
|
||||
if (err != null && err.isNotEmpty) {
|
||||
@@ -177,7 +190,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
_logger.debug('[黑屏调试] changeOptions 开始调用 libcore.dll - $startTime');
|
||||
|
||||
final err = await IsolateWorker().execute(
|
||||
() => _ffiChangeOptions(json),
|
||||
() => _ffiChangeOptions(json),
|
||||
allowSyncFallback: false,
|
||||
);
|
||||
|
||||
@@ -199,12 +212,12 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
|
||||
@override
|
||||
TaskEither<String, String> generateFullConfigByPath(
|
||||
String path,
|
||||
) {
|
||||
String path,
|
||||
) {
|
||||
return TaskEither(() async {
|
||||
try {
|
||||
final result = await IsolateWorker().execute(
|
||||
() => _ffiGenerateFullConfig(path),
|
||||
() => _ffiGenerateFullConfig(path),
|
||||
allowSyncFallback: false,
|
||||
);
|
||||
final ok = result.isNotEmpty && result[0] == true;
|
||||
@@ -221,18 +234,21 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> start(
|
||||
String configPath,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
) {
|
||||
String configPath,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
) {
|
||||
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");
|
||||
if (kDebugMode) {
|
||||
print('🚦 [FFI][ORDER] start() entered (after setup)');
|
||||
}
|
||||
return TaskEither(() async {
|
||||
try {
|
||||
final startTime = DateTime.now();
|
||||
_logger.debug('[黑屏调试] start() 开始调用 libcore.dll - $startTime');
|
||||
|
||||
final err = await IsolateWorker().execute(
|
||||
() => _ffiStart(configPath, disableMemoryLimit),
|
||||
() => _ffiStart(configPath, disableMemoryLimit),
|
||||
allowSyncFallback: false,
|
||||
);
|
||||
|
||||
@@ -244,9 +260,15 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
_logger.error('[黑屏调试] start() 错误: $err');
|
||||
return left(err);
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('✅ [FFI][ORDER] start() finished OK');
|
||||
}
|
||||
return right(unit);
|
||||
} catch (e) {
|
||||
_logger.error('[黑屏调试] start() 异常: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ [FFI][ORDER] start() threw: $e');
|
||||
}
|
||||
return left(e.toString());
|
||||
}
|
||||
});
|
||||
@@ -282,15 +304,15 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> restart(
|
||||
String configPath,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
) {
|
||||
String configPath,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
) {
|
||||
loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]");
|
||||
return TaskEither(() async {
|
||||
try {
|
||||
final err = await IsolateWorker().execute(
|
||||
() => _ffiRestart(configPath, disableMemoryLimit),
|
||||
() => _ffiRestart(configPath, disableMemoryLimit),
|
||||
allowSyncFallback: false,
|
||||
);
|
||||
if (err != null && err.isNotEmpty) {
|
||||
@@ -328,7 +350,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
_serviceStatsStream = null;
|
||||
},
|
||||
).map(
|
||||
(event) {
|
||||
(event) {
|
||||
if (event case String _) {
|
||||
if (event.startsWith('error:')) {
|
||||
loggy.error("[service stats client] error received: $event");
|
||||
@@ -343,12 +365,18 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
},
|
||||
);
|
||||
|
||||
final err = _box.startCommandClient(1, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'📡 [FFI][ORDER] watchStats startCommandClient port=${receiver.sendPort.nativePort}');
|
||||
}
|
||||
final err = _box
|
||||
.startCommandClient(1, receiver.sendPort.nativePort)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
loggy.error("error starting status command: $err");
|
||||
throw err;
|
||||
}
|
||||
|
||||
return _serviceStatsStream = statusStream;
|
||||
}
|
||||
|
||||
@@ -368,7 +396,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
}
|
||||
},
|
||||
).map(
|
||||
(event) {
|
||||
(event) {
|
||||
if (event case String _) {
|
||||
if (event.startsWith('error:')) {
|
||||
logger.error("error received: $event");
|
||||
@@ -385,7 +413,14 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
);
|
||||
|
||||
try {
|
||||
final err = _box.startCommandClient(5, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'📡 [FFI][ORDER] watchGroups startCommandClient port=${receiver.sendPort.nativePort}');
|
||||
}
|
||||
final err = _box
|
||||
.startCommandClient(5, receiver.sendPort.nativePort)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
logger.error("error starting group command: $err");
|
||||
throw err;
|
||||
@@ -412,7 +447,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
}
|
||||
},
|
||||
).map(
|
||||
(event) {
|
||||
(event) {
|
||||
if (event case String _) {
|
||||
if (event.startsWith('error:')) {
|
||||
logger.error(event);
|
||||
@@ -429,7 +464,14 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
);
|
||||
|
||||
try {
|
||||
final err = _box.startCommandClient(13, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'📡 [FFI][ORDER] watchActiveGroups startCommandClient port=${receiver.sendPort.nativePort}');
|
||||
}
|
||||
final err = _box
|
||||
.startCommandClient(13, receiver.sendPort.nativePort)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
logger.error("error starting: $err");
|
||||
throw err;
|
||||
@@ -447,7 +489,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
return TaskEither(() async {
|
||||
try {
|
||||
final err = await IsolateWorker().execute(
|
||||
() => _ffiSelectOutbound(groupTag, outboundTag),
|
||||
() => _ffiSelectOutbound(groupTag, outboundTag),
|
||||
allowSyncFallback: false,
|
||||
);
|
||||
if (err != null && err.isNotEmpty) {
|
||||
@@ -465,7 +507,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
return TaskEither(() async {
|
||||
try {
|
||||
final err = await IsolateWorker().execute(
|
||||
() => _ffiUrlTest(groupTag),
|
||||
() => _ffiUrlTest(groupTag),
|
||||
allowSyncFallback: false,
|
||||
);
|
||||
if (err != null && err.isNotEmpty) {
|
||||
@@ -484,7 +526,9 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
@override
|
||||
Stream<List<String>> watchLogs(String path) async* {
|
||||
yield await _readLogFile(File(path));
|
||||
yield* Watcher(path, pollingDelay: const Duration(seconds: 1)).events.asyncMap((event) async {
|
||||
yield* Watcher(path, pollingDelay: const Duration(seconds: 1))
|
||||
.events
|
||||
.asyncMap((event) async {
|
||||
if (event.type == ChangeType.MODIFY) {
|
||||
await _readLogFile(File(path));
|
||||
}
|
||||
@@ -502,7 +546,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
|
||||
Future<List<String>> _readLogFile(File file) async {
|
||||
if (_logFilePosition == 0 && file.lengthSync() == 0) return [];
|
||||
final content = await file.openRead(_logFilePosition).transform(utf8.decoder).join();
|
||||
final content =
|
||||
await file.openRead(_logFilePosition).transform(utf8.decoder).join();
|
||||
_logFilePosition = file.lengthSync();
|
||||
final lines = const LineSplitter().convert(content);
|
||||
if (lines.length > 300) {
|
||||
@@ -527,7 +572,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
return TaskEither(() async {
|
||||
try {
|
||||
final result = await IsolateWorker().execute(
|
||||
() => _ffiGenerateWarpConfig(licenseKey, previousAccountId, previousAccessToken),
|
||||
() => _ffiGenerateWarpConfig(
|
||||
licenseKey, previousAccountId, previousAccessToken),
|
||||
allowSyncFallback: false,
|
||||
);
|
||||
final ok = result.isNotEmpty && result[0] == true;
|
||||
@@ -552,38 +598,45 @@ SingboxNativeLibrary _ffiLoadLibrary() {
|
||||
}
|
||||
|
||||
String? _ffiSetup(
|
||||
String baseDir,
|
||||
String workingDir,
|
||||
String tempDir,
|
||||
int statusPort,
|
||||
int debugFlag,
|
||||
) {
|
||||
String baseDir,
|
||||
String workingDir,
|
||||
String tempDir,
|
||||
int statusPort,
|
||||
int debugFlag,
|
||||
) {
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'🧭 [FFI][ORDER] _ffiSetup inputs statusPort=$statusPort base="$baseDir" working="$workingDir" temp="$tempDir" debug="$debugFlag"');
|
||||
}
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box
|
||||
.setup(
|
||||
baseDir.toNativeUtf8().cast(),
|
||||
workingDir.toNativeUtf8().cast(),
|
||||
tempDir.toNativeUtf8().cast(),
|
||||
statusPort,
|
||||
debugFlag,
|
||||
)
|
||||
baseDir.toNativeUtf8().cast(),
|
||||
workingDir.toNativeUtf8().cast(),
|
||||
tempDir.toNativeUtf8().cast(),
|
||||
statusPort,
|
||||
debugFlag,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (kDebugMode) {
|
||||
print('🧭 [FFI][ORDER] _ffiSetup returned err="${err.isEmpty ? '' : err}"');
|
||||
}
|
||||
return err.isEmpty ? null : err;
|
||||
}
|
||||
|
||||
String? _ffiValidateConfig(
|
||||
String path,
|
||||
String tempPath,
|
||||
int debugFlag,
|
||||
) {
|
||||
String path,
|
||||
String tempPath,
|
||||
int debugFlag,
|
||||
) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box
|
||||
.parse(
|
||||
path.toNativeUtf8().cast(),
|
||||
tempPath.toNativeUtf8().cast(),
|
||||
debugFlag,
|
||||
)
|
||||
path.toNativeUtf8().cast(),
|
||||
tempPath.toNativeUtf8().cast(),
|
||||
debugFlag,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
@@ -591,7 +644,10 @@ String? _ffiValidateConfig(
|
||||
|
||||
String? _ffiChangeOptions(String optionsJson) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box.changeHiddifyOptions(optionsJson.toNativeUtf8().cast()).cast<Utf8>().toDartString();
|
||||
final err = box
|
||||
.changeHiddifyOptions(optionsJson.toNativeUtf8().cast())
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
}
|
||||
|
||||
@@ -599,8 +655,8 @@ List<Object?> _ffiGenerateFullConfig(String path) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final response = box
|
||||
.generateConfig(
|
||||
path.toNativeUtf8().cast(),
|
||||
)
|
||||
path.toNativeUtf8().cast(),
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (response.startsWith("error")) {
|
||||
@@ -613,9 +669,9 @@ String? _ffiStart(String configPath, bool disableMemoryLimit) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box
|
||||
.start(
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
@@ -631,9 +687,9 @@ String? _ffiRestart(String configPath, bool disableMemoryLimit) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box
|
||||
.restart(
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
@@ -643,9 +699,9 @@ String? _ffiSelectOutbound(String groupTag, String outboundTag) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box
|
||||
.selectOutbound(
|
||||
groupTag.toNativeUtf8().cast(),
|
||||
outboundTag.toNativeUtf8().cast(),
|
||||
)
|
||||
groupTag.toNativeUtf8().cast(),
|
||||
outboundTag.toNativeUtf8().cast(),
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
@@ -653,22 +709,23 @@ String? _ffiSelectOutbound(String groupTag, String outboundTag) {
|
||||
|
||||
String? _ffiUrlTest(String groupTag) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
|
||||
final err =
|
||||
box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
}
|
||||
|
||||
List<Object?> _ffiGenerateWarpConfig(
|
||||
String licenseKey,
|
||||
String previousAccountId,
|
||||
String previousAccessToken,
|
||||
) {
|
||||
String licenseKey,
|
||||
String previousAccountId,
|
||||
String previousAccessToken,
|
||||
) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final response = box
|
||||
.generateWarpConfig(
|
||||
licenseKey.toNativeUtf8().cast(),
|
||||
previousAccountId.toNativeUtf8().cast(),
|
||||
previousAccessToken.toNativeUtf8().cast(),
|
||||
)
|
||||
licenseKey.toNativeUtf8().cast(),
|
||||
previousAccountId.toNativeUtf8().cast(),
|
||||
previousAccessToken.toNativeUtf8().cast(),
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (response.startsWith("error:")) {
|
||||
|
||||
Reference in New Issue
Block a user