Compare commits

..

4 Commits

Author SHA1 Message Date
chen08209
6dcb466fd3 Fix android proxy error 2024-07-01 20:57:24 +08:00
chen08209
acbcec358b Fix submit error 2024-07-01 19:51:11 +08:00
chen08209
a923549ddf Add windows tun
Optimize android proxy

Optimize change profile

Update application ua

Optimize delay test
2024-07-01 19:41:57 +08:00
chen08209
07bd21580b Fix android repeated request notification issues 2024-06-28 21:16:47 +08:00
24 changed files with 207 additions and 82 deletions

View File

@@ -21,11 +21,25 @@ jobs:
os: macos-13
steps:
- name: Setup Mingw64
if: startsWith(matrix.platform,'windows')
uses: msys2/setup-msys2@v2
with:
msystem: mingw64
install: mingw-w64-x86_64-gcc
update: true
- name: Set Mingw64 Env
if: startsWith(matrix.platform,'windows')
run: |
echo "${{ runner.temp }}\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Check Matrix
run: |
echo "Running on ${{ matrix.os }}"
echo "Arch: ${{ runner.arch }}"
gcc --version
echo "Running on ${{ matrix.os }}"
echo "Arch: ${{ runner.arch }}"
gcc --version
- name: Checkout
uses: actions/checkout@v4
@@ -52,10 +66,10 @@ jobs:
if: startsWith(matrix.platform,'android')
run: |
echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
- name: Setup Go
uses: actions/setup-go@v5

View File

@@ -22,8 +22,10 @@
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config"
android:extractNativeLibs="true"
android:label="FlClash">
android:label="FlClash"
tools:targetApi="n">
<activity
android:name="com.follow.clash.MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

View File

@@ -44,6 +44,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
private var props: Props? = null
private lateinit var title: String
private lateinit var content: String
var isBlockNotification: Boolean = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
@@ -135,7 +136,6 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
GlobalState.runTime = null;
}
@SuppressLint("ForegroundServiceType")
private fun startForeground() {
if (GlobalState.runState.value != RunState.START) return
flClashVpnService?.startForeground(title, content)
@@ -152,13 +152,14 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
if (permission == PackageManager.PERMISSION_GRANTED) {
startForeground()
} else {
activity?.let {
ActivityCompat.requestPermissions(
it,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
NOTIFICATION_PERMISSION_REQUEST_CODE
)
}
if (isBlockNotification) return
if (activity == null) return
ActivityCompat.requestPermissions(
activity!!,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
NOTIFICATION_PERMISSION_REQUEST_CODE
)
}
} else {
startForeground()
@@ -192,11 +193,14 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
grantResults: IntArray
): Boolean {
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startForeground()
isBlockNotification = true
if (grantResults.isNotEmpty()) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startForeground()
}
}
}
return true;
return false;
}

View File

@@ -7,4 +7,8 @@
<certificates src="user" />
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">127.0.0.1</domain>
</domain-config>
</network-security-config>

View File

@@ -340,8 +340,8 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
targetConfig.Mode = patchConfig.Mode
targetConfig.Tun.Enable = patchConfig.Tun.Enable
targetConfig.Tun.Device = patchConfig.Tun.Device
//targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
//targetConfig.Tun.Stack = patchConfig.Tun.Stack
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
targetConfig.Tun.Stack = patchConfig.Tun.Stack
targetConfig.GeodataLoader = patchConfig.GeodataLoader
targetConfig.Profile.StoreSelected = false
if targetConfig.DNS.Enable == false {
@@ -396,7 +396,6 @@ func applyConfig(isPatch bool) {
if isPatch {
patchConfig(cfg.General)
} else {
executor.Shutdown()
runtime.GC()
hub.UltraApplyConfig(cfg, true)
}

View File

@@ -150,7 +150,7 @@ func getProxies() *C.char {
}
//export changeProxy
func changeProxy(s *C.char) bool {
func changeProxy(s *C.char) {
paramsString := C.GoString(s)
go func() {
var params = &ChangeProxyParams{}
@@ -158,22 +158,24 @@ func changeProxy(s *C.char) bool {
if err != nil {
log.Infoln("Unmarshal ChangeProxyParams %v", err)
}
groupName := *params.GroupName
proxyName := *params.ProxyName
proxies := tunnel.ProxiesWithProviders()
proxy := proxies[*params.GroupName]
if proxy == nil {
group := proxies[groupName]
if group == nil {
return
}
log.Infoln("change proxy %s", proxy.Name())
adapterProxy := proxy.(*adapter.Proxy)
adapterProxy := group.(*adapter.Proxy)
selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector)
if !ok {
return
}
if err := selector.Set(*params.ProxyName); err != nil {
return
err = selector.Set(proxyName)
if err == nil {
log.Infoln("[Selector] %s selected %s", groupName, proxyName)
}
}()
return true
}
//export getTraffic

View File

@@ -18,6 +18,21 @@ import (
var tunLock sync.Mutex
var tun *t.Tun
type FdMap struct {
m sync.Map
}
func (cm *FdMap) Store(key int) {
cm.m.Store(key, struct{}{})
}
func (cm *FdMap) Load(key int) bool {
_, ok := cm.m.Load(key)
return ok
}
var fdMap FdMap
//export startTUN
func startTUN(fd C.int) {
go func() {
@@ -63,16 +78,38 @@ func stopTun() {
var errBlocked = errors.New("blocked")
//export setFdMap
func setFdMap(fd C.long) {
fdInt := int(fd)
go func() {
fdMap.Store(fdInt)
}()
}
func init() {
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
if platform.ShouldBlockConnection() {
return errBlocked
}
return conn.Control(func(fd uintptr) {
fdInt := int(fd)
//timeout := time.After(100 * time.Millisecond)
if tun != nil {
tun.MarkSocket(int(fd))
time.Sleep(time.Millisecond * 100)
tun.MarkSocket(fdInt)
time.Sleep(100 * time.Millisecond)
}
//for {
// select {
// case <-timeout:
// return
// default:
// exists := fdMap.Load(fdInt)
// if exists {
// return
// }
// time.Sleep(20 * time.Millisecond)
// }
//}
})
}
}

View File

@@ -117,6 +117,7 @@ class ClashCore {
.map(
(name) => proxies[name],
)
.where((proxy) => proxy != null)
.toList();
return group;
}).toList();
@@ -185,7 +186,8 @@ class ClashCore {
receiver.close();
}
});
final delayParamsChar = json.encode(delayParams).toNativeUtf8().cast<Char>();
final delayParamsChar =
json.encode(delayParams).toNativeUtf8().cast<Char>();
clashFFI.asyncTestDelay(
delayParamsChar,
receiver.sendPort.nativePort,
@@ -254,11 +256,16 @@ class ClashCore {
}
void setProcessMap(ProcessMapItem processMapItem) {
final processMapItemChar = json.encode(processMapItem).toNativeUtf8().cast<Char>();
final processMapItemChar =
json.encode(processMapItem).toNativeUtf8().cast<Char>();
clashFFI.setProcessMap(processMapItemChar);
malloc.free(processMapItemChar);
}
void setFdMap(int fd) {
clashFFI.setFdMap(fd);
}
// DateTime? getRunTime() {
// final runTimeString = clashFFI.getRunTime().cast<Utf8>().toDartString();
// if (runTimeString.isEmpty) return null;

View File

@@ -5484,6 +5484,18 @@ class ClashFFI {
late final _stopTunPtr =
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopTun');
late final _stopTun = _stopTunPtr.asFunction<void Function()>();
void setFdMap(
int fd,
) {
return _setFdMap(
fd,
);
}
late final _setFdMapPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Long)>>('setFdMap');
late final _setFdMap = _setFdMapPtr.asFunction<void Function(int)>();
}
typedef va_list = ffi.Pointer<ffi.Char>;

View File

@@ -1,22 +1,35 @@
import 'dart:async';
import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart';
class AppPackage{
import 'common.dart';
class AppPackage {
static AppPackage? _instance;
Completer<PackageInfo> packageInfoCompleter = Completer();
AppPackage._internal() {
PackageInfo.fromPlatform().then(
(value) => packageInfoCompleter.complete(value),
PackageInfo.fromPlatform().then(
(value) => packageInfoCompleter.complete(value),
);
}
Future<String> getUa() async {
final packageInfo = await packageInfoCompleter.future;
final uas = [
"$appName/v${packageInfo.version}",
"clash-verge/v1.6.6",
"Platform/${Platform.operatingSystem}",
];
return uas.join(" ");
}
factory AppPackage() {
_instance ??= AppPackage._internal();
return _instance!;
}
}
final appPackage = AppPackage();
final appPackage = AppPackage();

View File

@@ -2,21 +2,20 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/ip.dart';
import 'package:fl_clash/state.dart';
import 'constant.dart';
import 'other.dart';
import 'package.dart';
class Request {
late final Dio _dio;
int? _port;
bool _isStart = false;
Request() {
_dio = Dio(
BaseOptions(
headers: {"User-Agent": coreName},
),
);
_dio = Dio();
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
_syncProxy();
@@ -45,10 +44,14 @@ class Request {
}
Future<Response> getFileResponseForUrl(String url) async {
final ua = await appPackage.getUa();
final response = await _dio
.get(
url,
options: Options(
headers: {
"User-Agent": ua,
},
responseType: ResponseType.bytes,
),
)

View File

@@ -10,5 +10,5 @@ extension TextStyleExtension on TextStyle {
TextStyle get toBold => copyWith(fontWeight: FontWeight.bold);
TextStyle get toMinus => copyWith(fontSize: fontSize! - 1);
TextStyle get toMinus => copyWith(fontSize: fontSize! - 2);
}

View File

@@ -337,7 +337,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
Widget _buildMoreSection() {
final items = [
if (false)
if (system.isDesktop)
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.tun.enable,
builder: (_, tunEnable, __) {
@@ -361,7 +361,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
];
if(items.isEmpty) return Container();
return Section(
title: appLocalizations.general,
title: appLocalizations.more,
child: Column(
children: [
for (final item in items) ...[

View File

@@ -53,7 +53,7 @@ class _IntranetIPState extends State<IntranetIP> {
},
child: Container(
padding: const EdgeInsets.all(16).copyWith(top: 0),
height: globalState.appController.measure.titleLargeHeight + 24 - 1,
height: globalState.appController.measure.titleLargeHeight + 24 - 2,
child: ValueListenableBuilder(
valueListenable: ipNotifier,
builder: (_, value, __) {

View File

@@ -137,7 +137,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
Container(
height: globalState.appController.measure.titleLargeHeight +
24 -
1,
2,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(16).copyWith(top: 0),
child: FadeBox(

View File

@@ -3,6 +3,7 @@ import 'package:fl_clash/fragments/profiles/edit_profile.dart';
import 'package:fl_clash/fragments/profiles/view_profile.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
@@ -28,6 +29,7 @@ class ProfilesFragment extends StatefulWidget {
class _ProfilesFragmentState extends State<ProfilesFragment> {
final hasPadding = ValueNotifier<bool>(false);
Function? applyConfigDebounce;
List<GlobalObjectKey<_ProfileItemState>> profileItemKeys = [];
@@ -86,12 +88,30 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
hasPadding.dispose();
}
_changeProfile(String? id) async {
final appController = globalState.appController;
final config = appController.config;
if (id == config.currentProfileId) return;
config.currentProfileId = id;
applyConfigDebounce ??= debounce<Function()>(() async {
await appController.applyProfile();
appController.appState.delayMap = {};
appController.saveConfigPreferences();
});
applyConfigDebounce!();
}
@override
Widget build(BuildContext context) {
return Selector<AppState, bool>(
@@ -154,8 +174,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
key: profileItemKeys[i],
profile: state.profiles[i],
groupValue: state.currentProfileId,
onChanged:
globalState.appController.changeProfile,
onChanged: _changeProfile,
),
),
],

View File

@@ -831,7 +831,12 @@ class ProxyCard extends StatelessWidget {
groupName,
proxy.name,
);
appController.changeProxy();
clashCore.changeProxy(
ChangeProxyParams(
groupName: groupName,
proxyName: proxy.name,
),
);
}
@override

View File

@@ -49,11 +49,15 @@ Future<void> vpnService() async {
isCompatible: config.isCompatible,
selectedMap: config.currentSelectedMap,
);
clashMessage.addListener(ClashMessageListenerWithVpn(onTun: (String fd) {
proxyManager.setProtect(
int.parse(fd),
);
}));
clashMessage.addListener(
ClashMessageListenerWithVpn(
onTun: (String fd) async {
final fdInt = int.parse(fd);
await proxyManager.setProtect(fdInt);
clashCore.setFdMap(fdInt);
},
),
);
await globalState.init(
appState: appState,

View File

@@ -123,9 +123,10 @@ class AppState with ChangeNotifier {
final index = groups.indexWhere((element) => element.name == proxyName);
if (index == -1) return proxyName;
final group = groups[index];
return getRealProxyName(selectedMap.containsKey(proxyName)
? selectedMap[proxyName]
: group.now);
return getRealProxyName((selectedMap.containsKey(proxyName)
? selectedMap[proxyName]
: group.now)) ??
proxyName;
}
String? get showProxyName {

View File

@@ -1,5 +1,7 @@
// ignore_for_file: invalid_annotation_target
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/constant.dart';
import 'package:flutter/material.dart';
@@ -235,7 +237,12 @@ class ClashConfig extends ChangeNotifier {
}
}
Tun get tun => _tun;
Tun get tun {
if (Platform.isAndroid) {
return _tun.copyWith(enable: false);
}
return _tun;
}
set tun(Tun value) {
if (_tun != value) {
@@ -282,9 +289,4 @@ class ClashConfig extends ChangeNotifier {
factory ClashConfig.fromJson(Map<String, dynamic> json) {
return _$ClashConfigFromJson(json);
}
@override
String toString() {
return 'ClashConfig{_mixedPort: $_mixedPort, _allowLan: $_allowLan, _mode: $_mode, _logLevel: $_logLevel, _tun: $_tun, _dns: $_dns, _rules: $_rules}';
}
}

View File

@@ -43,13 +43,14 @@ class GlobalState {
}) async {
final profilePath = await appPath.getProfilePath(config.currentProfileId);
await config.currentProfile?.checkAndUpdate();
debugPrint("update config");
final res = await clashCore.updateConfig(UpdateConfigParams(
profilePath: profilePath,
config: clashConfig,
isPatch: isPatch,
isCompatible: config.isCompatible,
));
final res = await clashCore.updateConfig(
UpdateConfigParams(
profilePath: profilePath,
config: clashConfig,
isPatch: isPatch,
isCompatible: config.isCompatible,
),
);
if (res.isNotEmpty) throw res;
}
@@ -263,7 +264,7 @@ class GlobalState {
}
}
int getColumns(ViewMode viewMode,int currentColumns){
int getColumns(ViewMode viewMode, int currentColumns) {
final targetColumnsArray = switch (viewMode) {
ViewMode.mobile => [2, 1],
ViewMode.laptop => [3, 2],

View File

@@ -51,8 +51,10 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
}
@override
void onTun(String fd) {
proxyManager.setProtect(int.parse(fd));
Future<void> onTun(String fd) async {
final fdInt = int.parse(fd);
await proxyManager.setProtect(fdInt);
clashCore.setFdMap(fdInt);
super.onTun(fd);
}
@@ -73,10 +75,4 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
globalState.appController.appState.addRequest(connection);
super.onRequest(connection);
}
@override
void onRun(String runTime) async {
// proxy?.updateStartTime();
super.onRun(runTime);
}
}

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.31
version: 0.8.33+202407012
environment:
sdk: '>=3.1.0 <4.0.0'