Compare commits

...

2 Commits

Author SHA1 Message Date
chen08209
67d819e327 Add windows server mode start process verify 2025-04-27 01:08:29 +08:00
chen08209
0a98515457 Optimize startTun performance 2025-04-26 21:29:56 +08:00
31 changed files with 272 additions and 119 deletions

View File

@@ -34,6 +34,10 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
if (options.ipv4Address.isNotEmpty()) {
val cidr = options.ipv4Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
Log.d(
"addAddress",
"address: ${cidr.address} prefixLength:${cidr.prefixLength}"
)
val routeAddress = options.getIpv4RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
@@ -50,26 +54,39 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
} else {
addRoute("0.0.0.0", 0)
}
} else {
addRoute("0.0.0.0", 0)
}
if (options.ipv6Address.isNotEmpty()) {
val cidr = options.ipv6Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
val routeAddress = options.getIpv6RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
routeAddress.forEach { i ->
Log.d(
"addRoute6",
"address: ${i.address} prefixLength:${i.prefixLength}"
)
addRoute(i.address, i.prefixLength)
try {
if (options.ipv6Address.isNotEmpty()) {
val cidr = options.ipv6Address.toCIDR()
Log.d(
"addAddress6",
"address: ${cidr.address} prefixLength:${cidr.prefixLength}"
)
addAddress(cidr.address, cidr.prefixLength)
val routeAddress = options.getIpv6RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
routeAddress.forEach { i ->
Log.d(
"addRoute6",
"address: ${i.address} prefixLength:${i.prefixLength}"
)
addRoute(i.address, i.prefixLength)
}
} catch (_: Exception) {
addRoute("::", 0)
}
} catch (_: Exception) {
} else {
addRoute("::", 0)
}
} else {
addRoute("::", 0)
}
}catch (_:Exception){
Log.d(
"addAddress6",
"IPv6 is not supported."
)
}
addDnsServer(options.dnsServerAddress)
setMtu(9000)

View File

@@ -98,13 +98,13 @@ func handleStopTun() {
}
}
func handleStartTun(fd int, callback unsafe.Pointer) bool {
func handleStartTun(fd int, callback unsafe.Pointer) {
handleStopTun()
tunLock.Lock()
defer tunLock.Unlock()
now := time.Now()
runTime = &now
if fd != 0 {
tunLock.Lock()
defer tunLock.Unlock()
tunHandler = &TunHandler{
callback: callback,
limit: semaphore.NewWeighted(4),
@@ -113,13 +113,11 @@ func handleStartTun(fd int, callback unsafe.Pointer) bool {
tunListener, _ := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
tunHandler.listener = tunListener
} else {
removeTunHook()
return false
}
tunHandler.listener = tunListener
}
return true
}
func handleGetRunTime() string {
@@ -228,7 +226,10 @@ func quickStart(initParamsChar *C.char, paramsChar *C.char, stateParamsChar *C.c
//export startTUN
func startTUN(fd C.int, callback unsafe.Pointer) bool {
return handleStartTun(int(fd), callback)
go func() {
handleStartTun(int(fd), callback)
}()
return true
}
//export getRunTime
@@ -238,7 +239,9 @@ func getRunTime() *C.char {
//export stopTun
func stopTun() {
handleStopTun()
go func() {
handleStopTun()
}()
}
//export getCurrentProfileName

View File

@@ -245,7 +245,6 @@ abstract class ClashHandlerInterface with ClashInterface {
);
}
@override
Future<bool> crash() {
return invoke<bool>(

View File

@@ -71,9 +71,9 @@ class ClashService extends ClashHandlerInterface {
}
}, (error, stack) {
commonPrint.log(error.toString());
if(error is SocketException){
if (error is SocketException) {
globalState.showNotifier(error.toString());
globalState.appController.restartCore();
// globalState.appController.restartCore();
}
});
}

View File

@@ -16,7 +16,8 @@ const browserUa =
const packageName = "com.follow.clash";
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
const helperPort = 47890;
const helperTag = "2024125";
const maxTextScale = 1.4;
const minTextScale = 0.8;
final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16.ap,
horizontal: 16.ap,

View File

@@ -130,7 +130,7 @@ class Request {
if (response.statusCode != HttpStatus.ok) {
return false;
}
return (response.data as String) == helperTag;
return (response.data as String) == globalState.helperToken;
} catch (_) {
return false;
}

View File

@@ -396,9 +396,9 @@ class AppController {
handleExit() async {
try {
await updateStatus(false);
await proxy?.stopProxy();
await clashCore.shutdown();
await clashService?.destroy();
await proxy?.stopProxy();
await savePreferences();
} finally {
system.exit();
@@ -487,10 +487,10 @@ class AppController {
Future<void> _initCore() async {
final isInit = await clashCore.isInit;
if (!isInit) {
await clashCore.init();
await clashCore.setState(
globalState.getCoreState(),
);
await clashCore.init();
}
await applyProfile();
}

View File

@@ -293,6 +293,7 @@ enum WindowsHelperServiceStatus {
enum DebounceTag {
updateClashConfig,
updateStatus,
updateGroups,
addCheckIpNum,
applyProfile,

View File

@@ -301,8 +301,11 @@ class RouteAddressItem extends ConsumerWidget {
title: appLocalizations.routeAddress,
widget: Consumer(
builder: (_, ref, __) {
final routeAddress = ref.watch(patchClashConfigProvider
.select((state) => state.tun.routeAddress));
final routeAddress = ref.watch(
patchClashConfigProvider.select(
(state) => state.tun.routeAddress,
),
);
return ListInputPage(
title: appLocalizations.routeAddress,
items: routeAddress,
@@ -371,7 +374,9 @@ class NetworkListView extends ConsumerWidget {
return;
}
ref.read(vpnSettingProvider.notifier).updateState(
(state) => defaultVpnProps,
(state) => defaultVpnProps.copyWith(
accessControl: state.accessControl,
),
);
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(

View File

@@ -1,4 +1,5 @@
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/providers/providers.dart';
import 'package:fl_clash/state.dart';
@@ -35,11 +36,15 @@ class _StartButtonState extends State<StartButton>
}
handleSwitchStart() {
if (isStart == globalState.appState.isStart) {
isStart = !isStart;
updateController();
globalState.appController.updateStatus(isStart);
}
isStart = !isStart;
updateController();
debouncer.call(
DebounceTag.updateStatus,
() {
globalState.appController.updateStatus(isStart);
},
duration: moreDuration,
);
}
updateController() {
@@ -126,9 +131,11 @@ class _StartButtonState extends State<StartButton>
final text = utils.getTimeText(runTime);
return Text(
text,
style: Theme.of(context).textTheme.titleMedium?.toSoftBold.copyWith(
color: context.colorScheme.onPrimaryContainer
),
style: Theme.of(context)
.textTheme
.titleMedium
?.toSoftBold
.copyWith(color: context.colorScheme.onPrimaryContainer),
);
},
),

View File

@@ -123,8 +123,13 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
@override
Widget build(BuildContext context) {
final proxiesType =
ref.watch(proxiesStyleSettingProvider.select((state) => state.type));
final proxiesType = ref.watch(
proxiesStyleSettingProvider.select(
(state) => state.type,
),
);
ref.watch(themeSettingProvider.select((state) => state.textScale));
return switch (proxiesType) {
ProxiesType.tab => ProxiesTabFragment(
key: _proxiesTabKey,

View File

@@ -147,22 +147,21 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
FutureBuilder<FileInfo>(
future: _getGeoFileLastModified(geoItem.fileName),
builder: (_, snapshot) {
final height = globalState.measure.bodyMediumHeight;
return SizedBox(
height: 24,
child: FadeThroughBox(
key: Key("fade_box_${geoItem.label}"),
child: snapshot.data == null
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: Text(
snapshot.data!.desc,
height: height,
child: snapshot.data == null
? SizedBox(
width: height,
height: height,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
)
: Text(
snapshot.data!.desc,
style: context.textTheme.bodyMedium,
),
);
},
),

View File

@@ -526,8 +526,8 @@ class _TextScaleFactorItem extends ConsumerWidget {
data: _SliderDefaultsM3(context),
child: Slider(
padding: EdgeInsets.zero,
min: 0.8,
max: 1.2,
min: minTextScale,
max: maxTextScale,
value: textScale.scale,
onChanged: (value) {
ref.read(themeSettingProvider.notifier).updateState(

View File

@@ -27,6 +27,7 @@ Future<void> main() async {
await android?.init();
await window?.init(version);
globalState.isPre = const String.fromEnvironment("APP_ENV") != 'stable';
globalState.helperToken = const String.fromEnvironment("HELPER_TOKEN");
HttpOverrides.global = FlClashHttpOverrides();
runApp(ProviderScope(
child: const Application(),

View File

@@ -23,9 +23,9 @@ class ThemeManager extends ConsumerWidget {
final double textScaleFactor = max(
min(
textScale.enable ? textScale.scale : defaultTextScaleFactor,
1.2,
maxTextScale,
),
0.8,
minTextScale,
);
globalState.measure = Measure.of(context, textScaleFactor);

View File

@@ -147,6 +147,7 @@ class Tun with _$Tun {
const factory Tun({
@Default(false) bool enable,
@Default(appName) String device,
@JsonKey(name: "auto-route") @Default(false) bool autoRoute,
@Default(TunStack.gvisor) TunStack stack,
@JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack,
@JsonKey(name: "route-address") @Default([]) List<String> routeAddress,

View File

@@ -660,6 +660,8 @@ Tun _$TunFromJson(Map<String, dynamic> json) {
mixin _$Tun {
bool get enable => throw _privateConstructorUsedError;
String get device => throw _privateConstructorUsedError;
@JsonKey(name: "auto-route")
bool get autoRoute => throw _privateConstructorUsedError;
TunStack get stack => throw _privateConstructorUsedError;
@JsonKey(name: "dns-hijack")
List<String> get dnsHijack => throw _privateConstructorUsedError;
@@ -683,6 +685,7 @@ abstract class $TunCopyWith<$Res> {
$Res call(
{bool enable,
String device,
@JsonKey(name: "auto-route") bool autoRoute,
TunStack stack,
@JsonKey(name: "dns-hijack") List<String> dnsHijack,
@JsonKey(name: "route-address") List<String> routeAddress});
@@ -704,6 +707,7 @@ class _$TunCopyWithImpl<$Res, $Val extends Tun> implements $TunCopyWith<$Res> {
$Res call({
Object? enable = null,
Object? device = null,
Object? autoRoute = null,
Object? stack = null,
Object? dnsHijack = null,
Object? routeAddress = null,
@@ -717,6 +721,10 @@ class _$TunCopyWithImpl<$Res, $Val extends Tun> implements $TunCopyWith<$Res> {
? _value.device
: device // ignore: cast_nullable_to_non_nullable
as String,
autoRoute: null == autoRoute
? _value.autoRoute
: autoRoute // ignore: cast_nullable_to_non_nullable
as bool,
stack: null == stack
? _value.stack
: stack // ignore: cast_nullable_to_non_nullable
@@ -742,6 +750,7 @@ abstract class _$$TunImplCopyWith<$Res> implements $TunCopyWith<$Res> {
$Res call(
{bool enable,
String device,
@JsonKey(name: "auto-route") bool autoRoute,
TunStack stack,
@JsonKey(name: "dns-hijack") List<String> dnsHijack,
@JsonKey(name: "route-address") List<String> routeAddress});
@@ -760,6 +769,7 @@ class __$$TunImplCopyWithImpl<$Res> extends _$TunCopyWithImpl<$Res, _$TunImpl>
$Res call({
Object? enable = null,
Object? device = null,
Object? autoRoute = null,
Object? stack = null,
Object? dnsHijack = null,
Object? routeAddress = null,
@@ -773,6 +783,10 @@ class __$$TunImplCopyWithImpl<$Res> extends _$TunCopyWithImpl<$Res, _$TunImpl>
? _value.device
: device // ignore: cast_nullable_to_non_nullable
as String,
autoRoute: null == autoRoute
? _value.autoRoute
: autoRoute // ignore: cast_nullable_to_non_nullable
as bool,
stack: null == stack
? _value.stack
: stack // ignore: cast_nullable_to_non_nullable
@@ -795,6 +809,7 @@ class _$TunImpl implements _Tun {
const _$TunImpl(
{this.enable = false,
this.device = appName,
@JsonKey(name: "auto-route") this.autoRoute = false,
this.stack = TunStack.gvisor,
@JsonKey(name: "dns-hijack")
final List<String> dnsHijack = const ["any:53"],
@@ -813,6 +828,9 @@ class _$TunImpl implements _Tun {
@JsonKey()
final String device;
@override
@JsonKey(name: "auto-route")
final bool autoRoute;
@override
@JsonKey()
final TunStack stack;
final List<String> _dnsHijack;
@@ -835,7 +853,7 @@ class _$TunImpl implements _Tun {
@override
String toString() {
return 'Tun(enable: $enable, device: $device, stack: $stack, dnsHijack: $dnsHijack, routeAddress: $routeAddress)';
return 'Tun(enable: $enable, device: $device, autoRoute: $autoRoute, stack: $stack, dnsHijack: $dnsHijack, routeAddress: $routeAddress)';
}
@override
@@ -845,6 +863,8 @@ class _$TunImpl implements _Tun {
other is _$TunImpl &&
(identical(other.enable, enable) || other.enable == enable) &&
(identical(other.device, device) || other.device == device) &&
(identical(other.autoRoute, autoRoute) ||
other.autoRoute == autoRoute) &&
(identical(other.stack, stack) || other.stack == stack) &&
const DeepCollectionEquality()
.equals(other._dnsHijack, _dnsHijack) &&
@@ -858,6 +878,7 @@ class _$TunImpl implements _Tun {
runtimeType,
enable,
device,
autoRoute,
stack,
const DeepCollectionEquality().hash(_dnsHijack),
const DeepCollectionEquality().hash(_routeAddress));
@@ -882,6 +903,7 @@ abstract class _Tun implements Tun {
const factory _Tun(
{final bool enable,
final String device,
@JsonKey(name: "auto-route") final bool autoRoute,
final TunStack stack,
@JsonKey(name: "dns-hijack") final List<String> dnsHijack,
@JsonKey(name: "route-address") final List<String> routeAddress}) =
@@ -894,6 +916,9 @@ abstract class _Tun implements Tun {
@override
String get device;
@override
@JsonKey(name: "auto-route")
bool get autoRoute;
@override
TunStack get stack;
@override
@JsonKey(name: "dns-hijack")

View File

@@ -66,6 +66,7 @@ Map<String, dynamic> _$$RuleProviderImplToJson(_$RuleProviderImpl instance) =>
_$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
enable: json['enable'] as bool? ?? false,
device: json['device'] as String? ?? appName,
autoRoute: json['auto-route'] as bool? ?? false,
stack: $enumDecodeNullable(_$TunStackEnumMap, json['stack']) ??
TunStack.gvisor,
dnsHijack: (json['dns-hijack'] as List<dynamic>?)
@@ -81,6 +82,7 @@ _$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
Map<String, dynamic> _$$TunImplToJson(_$TunImpl instance) => <String, dynamic>{
'enable': instance.enable,
'device': instance.device,
'auto-route': instance.autoRoute,
'stack': _$TunStackEnumMap[instance.stack]!,
'dns-hijack': instance.dnsHijack,
'route-address': instance.routeAddress,

View File

@@ -3522,6 +3522,7 @@ mixin _$ClashConfigState {
bool get overrideDns => throw _privateConstructorUsedError;
ClashConfig get clashConfig => throw _privateConstructorUsedError;
OverrideData get overrideData => throw _privateConstructorUsedError;
RouteMode get routeMode => throw _privateConstructorUsedError;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@@ -3537,7 +3538,10 @@ abstract class $ClashConfigStateCopyWith<$Res> {
_$ClashConfigStateCopyWithImpl<$Res, ClashConfigState>;
@useResult
$Res call(
{bool overrideDns, ClashConfig clashConfig, OverrideData overrideData});
{bool overrideDns,
ClashConfig clashConfig,
OverrideData overrideData,
RouteMode routeMode});
$ClashConfigCopyWith<$Res> get clashConfig;
$OverrideDataCopyWith<$Res> get overrideData;
@@ -3561,6 +3565,7 @@ class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState>
Object? overrideDns = null,
Object? clashConfig = null,
Object? overrideData = null,
Object? routeMode = null,
}) {
return _then(_value.copyWith(
overrideDns: null == overrideDns
@@ -3575,6 +3580,10 @@ class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState>
? _value.overrideData
: overrideData // ignore: cast_nullable_to_non_nullable
as OverrideData,
routeMode: null == routeMode
? _value.routeMode
: routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
) as $Val);
}
@@ -3608,7 +3617,10 @@ abstract class _$$ClashConfigStateImplCopyWith<$Res>
@override
@useResult
$Res call(
{bool overrideDns, ClashConfig clashConfig, OverrideData overrideData});
{bool overrideDns,
ClashConfig clashConfig,
OverrideData overrideData,
RouteMode routeMode});
@override
$ClashConfigCopyWith<$Res> get clashConfig;
@@ -3632,6 +3644,7 @@ class __$$ClashConfigStateImplCopyWithImpl<$Res>
Object? overrideDns = null,
Object? clashConfig = null,
Object? overrideData = null,
Object? routeMode = null,
}) {
return _then(_$ClashConfigStateImpl(
overrideDns: null == overrideDns
@@ -3646,6 +3659,10 @@ class __$$ClashConfigStateImplCopyWithImpl<$Res>
? _value.overrideData
: overrideData // ignore: cast_nullable_to_non_nullable
as OverrideData,
routeMode: null == routeMode
? _value.routeMode
: routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
));
}
}
@@ -3656,7 +3673,8 @@ class _$ClashConfigStateImpl implements _ClashConfigState {
const _$ClashConfigStateImpl(
{required this.overrideDns,
required this.clashConfig,
required this.overrideData});
required this.overrideData,
required this.routeMode});
@override
final bool overrideDns;
@@ -3664,10 +3682,12 @@ class _$ClashConfigStateImpl implements _ClashConfigState {
final ClashConfig clashConfig;
@override
final OverrideData overrideData;
@override
final RouteMode routeMode;
@override
String toString() {
return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig, overrideData: $overrideData)';
return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig, overrideData: $overrideData, routeMode: $routeMode)';
}
@override
@@ -3680,12 +3700,14 @@ class _$ClashConfigStateImpl implements _ClashConfigState {
(identical(other.clashConfig, clashConfig) ||
other.clashConfig == clashConfig) &&
(identical(other.overrideData, overrideData) ||
other.overrideData == overrideData));
other.overrideData == overrideData) &&
(identical(other.routeMode, routeMode) ||
other.routeMode == routeMode));
}
@override
int get hashCode =>
Object.hash(runtimeType, overrideDns, clashConfig, overrideData);
int get hashCode => Object.hash(
runtimeType, overrideDns, clashConfig, overrideData, routeMode);
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@@ -3701,7 +3723,8 @@ abstract class _ClashConfigState implements ClashConfigState {
const factory _ClashConfigState(
{required final bool overrideDns,
required final ClashConfig clashConfig,
required final OverrideData overrideData}) = _$ClashConfigStateImpl;
required final OverrideData overrideData,
required final RouteMode routeMode}) = _$ClashConfigStateImpl;
@override
bool get overrideDns;
@@ -3709,6 +3732,8 @@ abstract class _ClashConfigState implements ClashConfigState {
ClashConfig get clashConfig;
@override
OverrideData get overrideData;
@override
RouteMode get routeMode;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.

View File

@@ -228,6 +228,7 @@ class ClashConfigState with _$ClashConfigState {
required bool overrideDns,
required ClashConfig clashConfig,
required OverrideData overrideData,
required RouteMode routeMode,
}) = _ClashConfigState;
}

View File

@@ -127,6 +127,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
);
},
popup: CommonPopupMenu(
minWidth: 180,
items: [
PopupMenuItemData(
icon: Icons.search,
@@ -189,7 +190,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
shortcutsActivatorsBuilder: DefaultCodeShortcutsActivatorsBuilder(),
controller: _controller,
style: CodeEditorStyle(
fontSize: 14,
fontSize: 14.ap,
fontFamily: FontFamily.jetBrainsMono.value,
codeTheme: CodeHighlightTheme(
languages: {

View File

@@ -78,7 +78,7 @@ final coreStateProvider = AutoDisposeProvider<CoreState>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef CoreStateRef = AutoDisposeProviderRef<CoreState>;
String _$clashConfigStateHash() => r'848f6b2f734d99fb11ec05f73d614be415e9658f';
String _$clashConfigStateHash() => r'fbbcd7221b0b9b18db523e59c9021e8e56e119ca';
/// See also [clashConfigState].
@ProviderFor(clashConfigState)

View File

@@ -75,13 +75,15 @@ CoreState coreState(Ref ref) {
ClashConfigState clashConfigState(Ref ref) {
final clashConfig = ref.watch(patchClashConfigProvider);
final overrideDns = ref.watch(overrideDnsProvider);
final overrideData = ref.watch(currentProfileProvider.select(
(state) => state?.overrideData,
));
final overrideData =
ref.watch(currentProfileProvider.select((state) => state?.overrideData));
final routeMode =
ref.watch(networkSettingProvider.select((state) => state.routeMode));
return ClashConfigState(
overrideDns: overrideDns,
clashConfig: clashConfig,
overrideData: overrideData ?? OverrideData(),
routeMode: routeMode,
);
}

View File

@@ -30,6 +30,7 @@ class GlobalState {
late Config config;
late AppState appState;
bool isPre = true;
String? helperToken;
late PackageInfo packageInfo;
Function? updateCurrentDelayDebounce;
late Measure measure;
@@ -258,14 +259,17 @@ class GlobalState {
getUpdateConfigParams([bool? isPatch]) {
final currentProfile = config.currentProfile;
final clashConfig = config.patchClashConfig;
final routeAddress =
config.networkProps.routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: clashConfig.tun.routeAddress;
return UpdateConfigParams(
profileId: config.currentProfileId ?? "",
config: clashConfig.copyWith(
globalUa: ua,
tun: clashConfig.tun.copyWith(
routeAddress: config.networkProps.routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: clashConfig.tun.routeAddress,
autoRoute: routeAddress.isEmpty ? true : false,
routeAddress: routeAddress,
),
rule: currentProfile?.overrideData.runningRule ?? [],
),

View File

@@ -279,7 +279,7 @@ packages:
source: hosted
version: "0.3.4+2"
crypto:
dependency: transitive
dependency: "direct dev"
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"

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.83+202504252
version: 0.8.83+202504254
environment:
sdk: '>=3.1.0 <4.0.0'
@@ -66,6 +66,7 @@ dev_dependencies:
riverpod_generator: ^2.6.3
custom_lint: ^0.7.0
riverpod_lint: ^2.6.3
crypto: ^3.0.3
flutter:
uses-material-design: true

View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@@ -284,6 +284,7 @@ dependencies = [
"anyhow",
"once_cell",
"serde",
"sha2",
"tokio",
"warp",
"windows-service",
@@ -822,6 +823,17 @@ dependencies = [
"digest",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"

View File

@@ -14,10 +14,11 @@ anyhow = "1.0.93"
warp = "0.3.7"
serde = { version = "1.0.215", features = ["derive"] }
once_cell = "1.20.2"
sha2 = "0.10.8"
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
opt-level = "s"
opt-level = "s"

4
services/helper/build.rs Normal file
View File

@@ -0,0 +1,4 @@
fn main() {
let version = std::env::var("TOKEN").unwrap_or_default();
println!("cargo:rustc-env=TOKEN={}", version);
}

View File

@@ -1,11 +1,13 @@
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::VecDeque;
use std::{io, thread};
use std::io::BufRead;
use std::fs::File;
use std::io::{BufRead, Error, Read};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::{io, thread};
use warp::{Filter, Reply};
use serde::{Deserialize, Serialize};
use once_cell::sync::Lazy;
const LISTEN_PORT: u16 = 47890;
@@ -15,10 +17,31 @@ pub struct StartParams {
pub arg: String,
}
static LOGS: Lazy<Arc<Mutex<VecDeque<String>>>> = Lazy::new(|| Arc::new(Mutex::new(VecDeque::with_capacity(100))));
static PROCESS: Lazy<Arc<Mutex<Option<std::process::Child>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
fn sha256_file(path: &str) -> Result<String, Error> {
let mut file = File::open(path)?;
let mut hasher = Sha256::new();
let mut buffer = [0; 4096];
loop {
let bytes_read = file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
Ok(format!("{:x}", hasher.finalize()))
}
static LOGS: Lazy<Arc<Mutex<VecDeque<String>>>> =
Lazy::new(|| Arc::new(Mutex::new(VecDeque::with_capacity(100))));
static PROCESS: Lazy<Arc<Mutex<Option<std::process::Child>>>> =
Lazy::new(|| Arc::new(Mutex::new(None)));
fn start(start_params: StartParams) -> impl Reply {
if sha256_file(start_params.path.as_str()).unwrap_or("".to_string()) != env!("TOKEN") {
return "Only FlClashCore is allowed to run.".to_string();
}
stop();
let mut process = PROCESS.lock().unwrap();
match Command::new(&start_params.path)
@@ -73,38 +96,29 @@ fn log_message(message: String) {
fn get_logs() -> impl Reply {
let log_buffer = LOGS.lock().unwrap();
let value = log_buffer.iter().cloned().collect::<Vec<String>>().join("\n");
let value = log_buffer
.iter()
.cloned()
.collect::<Vec<String>>()
.join("\n");
warp::reply::with_header(value, "Content-Type", "text/plain")
}
pub async fn run_service() -> anyhow::Result<()> {
let api_ping = warp::get()
.and(warp::path("ping"))
.map(|| "2024125");
let api_ping = warp::get().and(warp::path("ping")).map(|| env!("TOKEN"));
let api_start = warp::post()
.and(warp::path("start"))
.and(warp::body::json())
.map(|start_params: StartParams| {
start(start_params)
});
.map(|start_params: StartParams| start(start_params));
let api_stop = warp::post()
.and(warp::path("stop"))
.map(|| stop());
let api_stop = warp::post().and(warp::path("stop")).map(|| stop());
let api_logs = warp::get()
.and(warp::path("logs"))
.map(|| get_logs());
let api_logs = warp::get().and(warp::path("logs")).map(|| get_logs());
warp::serve(
api_ping
.or(api_start)
.or(api_stop)
.or(api_logs)
)
warp::serve(api_ping.or(api_start).or(api_stop).or(api_logs))
.run(([127, 0, 0, 1], LISTEN_PORT))
.await;
Ok(())
}
}

View File

@@ -5,6 +5,7 @@ import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:path/path.dart';
import 'package:crypto/crypto.dart';
enum Target {
windows,
@@ -195,7 +196,16 @@ class Build {
if (exitCode != 0 && name != null) throw "$name error";
}
static buildCore({
static Future<String?> calcSha256(String filePath) async {
final file = File(filePath);
if (!await file.exists()) {
return null;
}
final stream = file.openRead();
return sha256.convert(await stream.reduce((a, b) => a + b)).toString();
}
static Future<List<String>> buildCore({
required Mode mode,
required Target target,
Arch? arch,
@@ -209,6 +219,8 @@ class Build {
},
).toList();
final List<String> corePaths = [];
for (final item in items) {
final outFileDir = join(
outDir,
@@ -228,6 +240,7 @@ class Build {
outFileDir,
fileName,
);
corePaths.add(outPath);
final Map<String, String> env = {};
env["GOOS"] = item.target.os;
@@ -258,9 +271,11 @@ class Build {
workingDirectory: _coreDir,
);
}
return corePaths;
}
static buildHelper(Target target) async {
static buildHelper(Target target, String token) async {
await exec(
[
"cargo",
@@ -269,6 +284,9 @@ class Build {
"--features",
"windows-service",
],
environment: {
"TOKEN": token,
},
name: "build helper",
workingDirectory: _servicesDir,
);
@@ -278,13 +296,15 @@ class Build {
"release",
"helper${target.executableExtensionName}",
);
final targetPath = join(outDir, target.name,
"FlClashHelperService${target.executableExtensionName}");
final targetPath = join(
outDir,
target.name,
"FlClashHelperService${target.executableExtensionName}",
);
await File(outPath).copy(targetPath);
}
static List<String> getExecutable(String command) {
print(command);
return command.split(" ");
}
@@ -466,26 +486,28 @@ class BuildCommand extends Command {
throw "Invalid arch parameter";
}
await Build.buildCore(
final corePaths = await Build.buildCore(
target: target,
arch: arch,
mode: mode,
);
if (target == Target.windows) {
await Build.buildHelper(target);
}
if (out != "app") {
return;
}
switch (target) {
case Target.windows:
final token = await Build.calcSha256(corePaths.first);
if (token == null) {
throw "Core not exists";
}
Build.buildHelper(target, token);
_buildDistributor(
target: target,
targets: "exe,zip",
args: " --description $archName",
args:
" --description $archName --build-dart-define=HELPER_TOKEN=$token",
env: env,
);
return;