import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; import 'dart:ui'; import 'package:ffi/ffi.dart'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/plugins/service.dart'; import 'package:fl_clash/state.dart'; import 'generated/clash_ffi.dart'; import 'interface.dart'; class ClashLib extends ClashHandlerInterface with AndroidClashInterface { static ClashLib? _instance; Completer _canSendCompleter = Completer(); SendPort? sendPort; final receiverPort = ReceivePort(); ClashLib._internal() { _initService(); } @override preload() { return _canSendCompleter.future; } _initService() async { await service?.destroy(); _registerMainPort(receiverPort.sendPort); receiverPort.listen((message) { if (message is SendPort) { if (_canSendCompleter.isCompleted) { sendPort = null; _canSendCompleter = Completer(); } sendPort = message; _canSendCompleter.complete(true); } else { handleResult( ActionResult.fromJson(json.decode( message, )), ); } }); await service?.init(); } _registerMainPort(SendPort sendPort) { IsolateNameServer.removePortNameMapping(mainIsolate); IsolateNameServer.registerPortWithName(sendPort, mainIsolate); } factory ClashLib() { _instance ??= ClashLib._internal(); return _instance!; } @override destroy() async { await service?.destroy(); return true; } @override reStart() { _initService(); } @override Future shutdown() async { await super.shutdown(); destroy(); return true; } @override sendMessage(String message) async { await _canSendCompleter.future; sendPort?.send(message); } // @override // Future stopTun() { // return invoke( // method: ActionMethod.stopTun, // ); // } @override Future getAndroidVpnOptions() async { final res = await invoke( method: ActionMethod.getAndroidVpnOptions, ); if (res.isEmpty) { return null; } return AndroidVpnOptions.fromJson(json.decode(res)); } @override Future updateDns(String value) { return invoke( method: ActionMethod.updateDns, data: value, ); } @override Future getRunTime() async { final runTimeString = await invoke( method: ActionMethod.getRunTime, ); if (runTimeString.isEmpty) { return null; } return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString)); } @override Future getCurrentProfileName() { return invoke( method: ActionMethod.getCurrentProfileName, ); } } class ClashLibHandler { static ClashLibHandler? _instance; late final ClashFFI clashFFI; late final DynamicLibrary lib; ClashLibHandler._internal() { lib = DynamicLibrary.open("libclash.so"); clashFFI = ClashFFI(lib); clashFFI.initNativeApiBridge( NativeApi.initializeApiDLData, ); } factory ClashLibHandler() { _instance ??= ClashLibHandler._internal(); return _instance!; } Future invokeAction(String actionParams) { final completer = Completer(); final receiver = ReceivePort(); receiver.listen((message) { if (!completer.isCompleted) { completer.complete(message); receiver.close(); } }); final actionParamsChar = actionParams.toNativeUtf8().cast(); clashFFI.invokeAction( actionParamsChar, receiver.sendPort.nativePort, ); malloc.free(actionParamsChar); return completer.future; } attachMessagePort(int messagePort) { clashFFI.attachMessagePort( messagePort, ); } updateDns(String dns) { final dnsChar = dns.toNativeUtf8().cast(); clashFFI.updateDns(dnsChar); malloc.free(dnsChar); } setState(CoreState state) { final stateChar = json.encode(state).toNativeUtf8().cast(); clashFFI.setState(stateChar); malloc.free(stateChar); } String getCurrentProfileName() { final currentProfileRaw = clashFFI.getCurrentProfileName(); final currentProfile = currentProfileRaw.cast().toDartString(); clashFFI.freeCString(currentProfileRaw); return currentProfile; } AndroidVpnOptions getAndroidVpnOptions() { final vpnOptionsRaw = clashFFI.getAndroidVpnOptions(); final vpnOptions = json.decode(vpnOptionsRaw.cast().toDartString()); clashFFI.freeCString(vpnOptionsRaw); return AndroidVpnOptions.fromJson(vpnOptions); } Traffic getTraffic() { final trafficRaw = clashFFI.getTraffic(); final trafficString = trafficRaw.cast().toDartString(); clashFFI.freeCString(trafficRaw); if (trafficString.isEmpty) { return Traffic(); } return Traffic.fromMap(json.decode(trafficString)); } Traffic getTotalTraffic(bool value) { final trafficRaw = clashFFI.getTotalTraffic(); final trafficString = trafficRaw.cast().toDartString(); clashFFI.freeCString(trafficRaw); if (trafficString.isEmpty) { return Traffic(); } return Traffic.fromMap(json.decode(trafficString)); } startListener() async { clashFFI.startListener(); return true; } stopListener() async { clashFFI.stopListener(); return true; } DateTime? getRunTime() { final runTimeRaw = clashFFI.getRunTime(); final runTimeString = runTimeRaw.cast().toDartString(); if (runTimeString.isEmpty) { return null; } return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString)); } Future> getConfig(String id) async { final path = await appPath.getProfilePath(id); final pathChar = path.toNativeUtf8().cast(); final configRaw = clashFFI.getConfig(pathChar); final configString = configRaw.cast().toDartString(); if (configString.isEmpty) { return {}; } final config = json.decode(configString); malloc.free(pathChar); clashFFI.freeCString(configRaw); return config; } Future quickStart( InitParams initParams, SetupParams setupParams, CoreState state, ) { final completer = Completer(); final receiver = ReceivePort(); receiver.listen((message) { if (!completer.isCompleted) { completer.complete(message); receiver.close(); } }); final params = json.encode(setupParams); final initValue = json.encode(initParams); final stateParams = json.encode(state); final initParamsChar = initValue.toNativeUtf8().cast(); final paramsChar = params.toNativeUtf8().cast(); final stateParamsChar = stateParams.toNativeUtf8().cast(); clashFFI.quickStart( initParamsChar, paramsChar, stateParamsChar, receiver.sendPort.nativePort, ); malloc.free(initParamsChar); malloc.free(paramsChar); malloc.free(stateParamsChar); return completer.future; } } ClashLib? get clashLib => Platform.isAndroid && !globalState.isService ? ClashLib() : null; ClashLibHandler? get clashLibHandler => Platform.isAndroid && globalState.isService ? ClashLibHandler() : null;