fix: 修改样式

This commit is contained in:
2026-01-22 16:58:54 -08:00
parent 343891b702
commit 73480ce417
22 changed files with 450 additions and 219 deletions
+144 -87
View File
@@ -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.dylibfallback 到默认路径: $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:")) {