feat: windows ico替换成圆角,macos增加tg代理
This commit is contained in:
parent
5d00bf3404
commit
7017c99f4a
11
README.md
11
README.md
@ -33,13 +33,22 @@ make windows-release
|
||||
|
||||
### 🍎 iOS
|
||||
> ios-release
|
||||
- **证书管理**: 开发环境使用automativally模式自动,生产环境需要下载hiFastVPN-iOs-Prod的profile
|
||||
- **证书管理**: 开发环境使用automaticall模式自动,生产环境需要下载hiFastVPN-iOs-Prod的profile
|
||||
- ipa发布后使用 `Transporter.app`上传到苹果后台
|
||||
- release apk:路径在 `dist/对应版本号/*.ipa`,
|
||||
#### 问题
|
||||
- ios真机调试,出现开发环境flutter运行一会就断开,日志没法看,并且生成flutter.log文件,但在xcode可以正常看到日志
|
||||
修改mac上的设置 -> 本机网络 -> android studio 开启
|
||||
|
||||
### 💻 macOS
|
||||
> macos-release
|
||||
- release apk:路径在 `dist/对应版本号/*.dmg`,完成公证和dmg封面制作
|
||||
#### 问题
|
||||
- 启动过程中遇到 Crash occurred when compiling unknown function in unoptimized JIT mode in unknown pass
|
||||
1. xcode修改配置, macOS -> signing Certificate -> 选择 sign to Run Locally;
|
||||
2. 使用automaticall
|
||||
- 启动过程中页面卡在启动页 FFISingboxService - singbox native libs path: "libcore.dylib"
|
||||
|
||||
|
||||
### 🪟 Windows
|
||||
- 环境需要Inno Setup
|
||||
|
||||
@ -1019,11 +1019,11 @@
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
@ -1251,12 +1251,12 @@
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
@ -1309,11 +1309,11 @@
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@ -22,6 +22,7 @@ import '../../utils/kr_country_util.dart';
|
||||
import '../../utils/kr_log_util.dart';
|
||||
import '../../utils/kr_secure_storage.dart';
|
||||
import '../../utils/kr_windows_dns_util.dart';
|
||||
import '../../utils/kr_macos_proxy_util.dart';
|
||||
import '../../common/app_run_data.dart';
|
||||
import '../../common/app_config.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -297,6 +298,21 @@ class KRSingBoxImp {
|
||||
KRLogUtil.kr_i('⏳ Windows 文件系统同步等待完成', tag: 'SingBox');
|
||||
}
|
||||
|
||||
// 🍎 macOS 平台:初始化时确保代理关闭(防止上次崩溃残留)
|
||||
if (Platform.isMacOS) {
|
||||
KRLogUtil.kr_i('🍎 macOS 平台,初始化时清理系统代理残留...', tag: 'SingBox');
|
||||
try {
|
||||
// 异步执行,不阻塞初始化主流程
|
||||
KRMacosProxyUtil.disableSocks5().then((_) {
|
||||
KRLogUtil.kr_i('✅ macOS 系统代理残留清理完成', tag: 'SingBox');
|
||||
}).catchError((e) {
|
||||
KRLogUtil.kr_w('⚠️ macOS 系统代理清理异常: $e', tag: 'SingBox');
|
||||
});
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ macOS 系统代理清理尝试失败: $e', tag: 'SingBox');
|
||||
}
|
||||
}
|
||||
|
||||
// 最终验证:在 setup() 之前再次确认 workingDir 和 data 目录都存在且可访问
|
||||
// libcore 的 Setup() 会调用 os.Chdir(workingPath),然后使用相对路径 "./data"
|
||||
// 如果 os.Chdir() 失败(路径不存在或权限问题),后续的相对路径访问会失败
|
||||
@ -1555,6 +1571,19 @@ class KRSingBoxImp {
|
||||
throw err;
|
||||
}).run();
|
||||
|
||||
// 🔑 macOS 平台:启用 SOCKS5 系统代理
|
||||
if (Platform.isMacOS) {
|
||||
KRLogUtil.kr_i('🍎 macOS 平台,开启系统 SOCKS5 代理...', tag: 'SingBox');
|
||||
try {
|
||||
await KRMacosProxyUtil.enableSocks5(
|
||||
host: '127.0.0.1',
|
||||
port: kr_port,
|
||||
);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ macOS 系统代理开启失败: $e', tag: 'SingBox');
|
||||
}
|
||||
}
|
||||
|
||||
// ⚠️ 关键修复:在启动成功后立即订阅统计流
|
||||
// 原因:
|
||||
// 1. 统计流需要主动订阅才能接收数据
|
||||
@ -1751,6 +1780,16 @@ class KRSingBoxImp {
|
||||
|
||||
// 不手动设置状态,由 libcore 通过 status stream 自动发送 Stopped 事件
|
||||
KRLogUtil.kr_i('✅ SingBox 停止请求已发送', tag: 'SingBox');
|
||||
|
||||
// 🍎 macOS 平台:关闭系统 SOCKS5 代理
|
||||
if (Platform.isMacOS) {
|
||||
KRLogUtil.kr_i('🍎 macOS 平台,关闭系统 SOCKS5 代理...', tag: 'SingBox');
|
||||
try {
|
||||
await KRMacosProxyUtil.disableSocks5();
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ macOS 系统代理关闭失败: $e', tag: 'SingBox');
|
||||
}
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
KRLogUtil.kr_e('停止服务时出错: $e');
|
||||
KRLogUtil.kr_e('错误堆栈: $stackTrace');
|
||||
|
||||
113
lib/app/utils/kr_macos_proxy_util.dart
Normal file
113
lib/app/utils/kr_macos_proxy_util.dart
Normal file
@ -0,0 +1,113 @@
|
||||
import 'dart:io';
|
||||
import 'kr_log_util.dart';
|
||||
|
||||
/// macOS 系统代理管理工具类
|
||||
///
|
||||
/// 使用 `networksetup` 命令管理 macOS 的 SOCKS5 代理设置
|
||||
class KRMacosProxyUtil {
|
||||
/// 私有构造函数
|
||||
KRMacosProxyUtil._();
|
||||
|
||||
/// 获取所有网络服务列表
|
||||
static Future<List<String>> _listNetworkServices() async {
|
||||
try {
|
||||
final r = await Process.run('networksetup', ['-listallnetworkservices']);
|
||||
if (r.exitCode != 0) {
|
||||
KRLogUtil.kr_e('列出网络服务失败: ${r.stderr}', tag: 'MacosProxy');
|
||||
return [];
|
||||
}
|
||||
final lines = (r.stdout as String).split('\n').map((e) => e.trim()).toList();
|
||||
// 过滤掉第一行说明文字和空行
|
||||
return lines
|
||||
.where((l) => l.isNotEmpty && !l.toLowerCase().contains('an asterisk'))
|
||||
.skip(1)
|
||||
.toList();
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('列出网络服务异常: $e', tag: 'MacosProxy');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// 为所有网络服务启用 SOCKS5 代理
|
||||
static Future<void> enableSocks5({
|
||||
required String host,
|
||||
required int port,
|
||||
}) async {
|
||||
if (!Platform.isMacOS) return;
|
||||
|
||||
final services = await _listNetworkServices();
|
||||
if (services.isEmpty) {
|
||||
KRLogUtil.kr_w('未找到可用的网络服务', tag: 'MacosProxy');
|
||||
return;
|
||||
}
|
||||
|
||||
for (final service in services) {
|
||||
try {
|
||||
// 1) 设置 SOCKS5 服务器地址和端口
|
||||
final r1 = await Process.run('networksetup', [
|
||||
'-setsocksfirewallproxy',
|
||||
service,
|
||||
host,
|
||||
port.toString(),
|
||||
]);
|
||||
|
||||
if (r1.exitCode != 0) {
|
||||
KRLogUtil.kr_w('服务 [$service] 设置 SOCKS5 代理失败: ${r1.stderr}', tag: 'MacosProxy');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2) 开启 SOCKS5 代理状态
|
||||
final r2 = await Process.run('networksetup', [
|
||||
'-setsocksfirewallproxystate',
|
||||
service,
|
||||
'on',
|
||||
]);
|
||||
|
||||
if (r2.exitCode != 0) {
|
||||
KRLogUtil.kr_w('服务 [$service] 启用 SOCKS5 代理失败: ${r2.stderr}', tag: 'MacosProxy');
|
||||
continue;
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('✅ 服务 [$service] SOCKS5 代理已启用: $host:$port', tag: 'MacosProxy');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('服务 [$service] 启用代理异常: $e', tag: 'MacosProxy');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 为所有网络服务禁用 SOCKS5 代理
|
||||
static Future<void> disableSocks5() async {
|
||||
if (!Platform.isMacOS) return;
|
||||
|
||||
final services = await _listNetworkServices();
|
||||
if (services.isEmpty) return;
|
||||
|
||||
for (final service in services) {
|
||||
try {
|
||||
final r = await Process.run('networksetup', [
|
||||
'-setsocksfirewallproxystate',
|
||||
service,
|
||||
'off',
|
||||
]);
|
||||
|
||||
if (r.exitCode == 0) {
|
||||
KRLogUtil.kr_i('✅ 服务 [$service] SOCKS5 代理已关闭', tag: 'MacosProxy');
|
||||
} else {
|
||||
KRLogUtil.kr_w('服务 [$service] 关闭 SOCKS5 代理失败: ${r.stderr}', tag: 'MacosProxy');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('服务 [$service] 关闭代理异常: $e', tag: 'MacosProxy');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取指定服务的 SOCKS5 状态 (主要用于调试)
|
||||
static Future<String> getSocks5Status(String serviceName) async {
|
||||
try {
|
||||
final r = await Process.run('networksetup', ['-getsocksfirewallproxy', serviceName]);
|
||||
return r.stdout as String;
|
||||
} catch (e) {
|
||||
return '获取状态失败: $e';
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:kaer_with_panels/utils/isolate_worker.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
@ -31,32 +32,80 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
Stream<List<SingboxOutboundGroup>>? _outboundsStream;
|
||||
|
||||
static SingboxNativeLibrary _gen() {
|
||||
String fullPath = "";
|
||||
if (Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||
fullPath = "libcore";
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
fullPath = p.join(fullPath, "libcore.dll");
|
||||
} else if (Platform.isMacOS) {
|
||||
fullPath = p.join(fullPath, "libcore.dylib");
|
||||
} else {
|
||||
fullPath = p.join(fullPath, "libcore.so");
|
||||
}
|
||||
String fullPath = _getLibraryPath();
|
||||
_logger.debug('singbox native libs path: "$fullPath"');
|
||||
final lib = DynamicLibrary.open(fullPath);
|
||||
return SingboxNativeLibrary(lib);
|
||||
}
|
||||
|
||||
static String _getLibraryPath() {
|
||||
String libName;
|
||||
if (Platform.isWindows) {
|
||||
libName = "libcore.dll";
|
||||
} else if (Platform.isMacOS) {
|
||||
libName = "libcore.dylib";
|
||||
} else {
|
||||
libName = "libcore.so";
|
||||
}
|
||||
|
||||
// 测试环境
|
||||
if (Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||
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 (File(devPath).existsSync()) {
|
||||
if (kDebugMode) {
|
||||
print('✅ [FFI] 使用开发环境路径: $devPath');
|
||||
}
|
||||
return devPath;
|
||||
}
|
||||
|
||||
// 生产环境:使用相对路径(bundle中的路径)
|
||||
if (kDebugMode) {
|
||||
print('⚠️ [FFI] 开发环境路径不存在,使用生产环境路径: $libName');
|
||||
}
|
||||
return libName;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
if (kDebugMode) {
|
||||
print('🚀 [FFI] init() 开始');
|
||||
}
|
||||
loggy.debug("initializing");
|
||||
_box.setupOnce(NativeApi.initializeApiDLData);
|
||||
|
||||
// 注意:setupOnce 会在 worker isolate 中调用(见 _ffiLoadLibrary)
|
||||
// 在主 isolate 中调用会导致阻塞,因此这里跳过
|
||||
if (kDebugMode) {
|
||||
print('⏭️ [FFI] 跳过主 isolate 中的 setupOnce(将在 worker isolate 中执行)');
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
print('📡 [FFI] 创建 ReceivePort');
|
||||
}
|
||||
_statusReceiver = ReceivePort('service status receiver');
|
||||
|
||||
if (kDebugMode) {
|
||||
print('🔄 [FFI] 设置状态流');
|
||||
}
|
||||
final source = _statusReceiver.asBroadcastStream().map((event) => jsonDecode(event as String)).map(SingboxStatus.fromEvent);
|
||||
_status = ValueConnectableStream.seeded(
|
||||
source,
|
||||
const SingboxStopped(),
|
||||
).autoConnect();
|
||||
|
||||
if (kDebugMode) {
|
||||
print('✅ [FFI] init() 完成');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -495,17 +544,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
}
|
||||
|
||||
SingboxNativeLibrary _ffiLoadLibrary() {
|
||||
String fullPath = "";
|
||||
if (Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||
fullPath = "libcore";
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
fullPath = p.join(fullPath, "libcore.dll");
|
||||
} else if (Platform.isMacOS) {
|
||||
fullPath = p.join(fullPath, "libcore.dylib");
|
||||
} else {
|
||||
fullPath = p.join(fullPath, "libcore.so");
|
||||
}
|
||||
final fullPath = FFISingboxService._getLibraryPath();
|
||||
final lib = DynamicLibrary.open(fullPath);
|
||||
final box = SingboxNativeLibrary(lib);
|
||||
box.setupOnce(NativeApi.initializeApiDLData);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 9.2 KiB |
Loading…
x
Reference in New Issue
Block a user