Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6d9ed11d9 | ||
|
|
c38a671d57 | ||
|
|
75af47aead | ||
|
|
8dafe3b0ec | ||
|
|
813198a21d | ||
|
|
68dd262fef | ||
|
|
5ef020db73 | ||
|
|
e3c9035903 | ||
|
|
7fc54c5295 | ||
|
|
00a78b5fb4 |
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/metacubex/mihomo/common/batch"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/sniffer"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
cp "github.com/metacubex/mihomo/constant/provider"
|
||||
@@ -422,7 +423,9 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
|
||||
func patchConfig(general *config.General) {
|
||||
log.Infoln("[Apply] patch")
|
||||
route.ReStartServer(general.ExternalController)
|
||||
tunnel.SetSniffing(general.Sniffing)
|
||||
if sniffer.Dispatcher != nil {
|
||||
tunnel.SetSniffing(general.Sniffing)
|
||||
}
|
||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:fl_clash/models/models.dart' hide Process;
|
||||
import 'package:launch_at_startup/launch_at_startup.dart';
|
||||
|
||||
import 'constant.dart';
|
||||
import 'system.dart';
|
||||
import 'windows.dart';
|
||||
|
||||
class AutoLaunch {
|
||||
static AutoLaunch? _instance;
|
||||
@@ -34,6 +36,9 @@ class AutoLaunch {
|
||||
}
|
||||
|
||||
Future<bool> enable() async {
|
||||
if (Platform.isWindows) {
|
||||
await windowsDisable();
|
||||
}
|
||||
return await launchAtStartup.enable();
|
||||
}
|
||||
|
||||
@@ -51,45 +56,47 @@ class AutoLaunch {
|
||||
return res.exitCode == 0;
|
||||
}
|
||||
|
||||
windowsEnable() async {
|
||||
await Process.run(
|
||||
'schtasks',
|
||||
[
|
||||
'/Create',
|
||||
'/SC',
|
||||
'ONLOGON',
|
||||
'/TN',
|
||||
appName,
|
||||
'/TR',
|
||||
Platform.resolvedExecutable,
|
||||
'/RL',
|
||||
'HIGHEST',
|
||||
'/F'
|
||||
],
|
||||
runInShell: true,
|
||||
);
|
||||
Future<bool> windowsEnable() async {
|
||||
await disable();
|
||||
return windows?.runas(
|
||||
'schtasks',
|
||||
[
|
||||
'/Create',
|
||||
'/SC',
|
||||
'ONLOGON',
|
||||
'/TN',
|
||||
appName,
|
||||
'/TR',
|
||||
Platform.resolvedExecutable,
|
||||
'/RL',
|
||||
'HIGHEST',
|
||||
'/F'
|
||||
].join(" "),
|
||||
) ??
|
||||
false;
|
||||
}
|
||||
|
||||
Future<bool> disable() async {
|
||||
return await launchAtStartup.disable();
|
||||
}
|
||||
|
||||
updateStatus(bool value) async {
|
||||
final currentEnable =
|
||||
Platform.isWindows ? await windowsIsEnable : await isEnable;
|
||||
if (value == currentEnable) {
|
||||
return;
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
if (value) {
|
||||
enable();
|
||||
windowsEnable();
|
||||
updateStatus(AutoLaunchState state) async {
|
||||
final isOpenTun = state.isOpenTun;
|
||||
final isAutoLaunch = state.isAutoLaunch;
|
||||
if (Platform.isWindows && isOpenTun) {
|
||||
if (await windowsIsEnable == isAutoLaunch) return;
|
||||
if (isAutoLaunch) {
|
||||
final isEnable = await windowsEnable();
|
||||
if (!isEnable) {
|
||||
enable();
|
||||
}
|
||||
} else {
|
||||
windowsDisable();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (value == true) {
|
||||
if (await isEnable == isAutoLaunch) return;
|
||||
if (isAutoLaunch == true) {
|
||||
enable();
|
||||
} else {
|
||||
disable();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
@@ -14,12 +15,16 @@ class Picker {
|
||||
return filePickerResult?.files.first;
|
||||
}
|
||||
|
||||
Future<String?> saveFile(String fileName,Uint8List bytes) async {
|
||||
Future<String?> saveFile(String fileName, Uint8List bytes) async {
|
||||
final path = await FilePicker.platform.saveFile(
|
||||
fileName: fileName,
|
||||
initialDirectory: await appPath.getDownloadDirPath(),
|
||||
bytes: bytes,
|
||||
bytes: Platform.isAndroid ? bytes : null,
|
||||
);
|
||||
if (!Platform.isAndroid && path != null) {
|
||||
final file = await File(path).create(recursive: true);
|
||||
await file.writeAsBytes(bytes);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,12 @@ class System {
|
||||
bool get isDesktop =>
|
||||
Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
||||
|
||||
get isAdmin async {
|
||||
if (!Platform.isWindows) return false;
|
||||
final result = await Process.run('net', ['session'], runInShell: true);
|
||||
return result.exitCode == 0;
|
||||
}
|
||||
|
||||
back() async {
|
||||
await app?.moveTaskToBack();
|
||||
await window?.hide();
|
||||
|
||||
@@ -15,7 +15,7 @@ class Windows {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
void runAsAdministrator(String command, String arguments) async {
|
||||
bool runas(String command, String arguments) {
|
||||
final commandPtr = command.toNativeUtf16();
|
||||
final argumentsPtr = arguments.toNativeUtf16();
|
||||
final operationPtr = 'runas'.toNativeUtf16();
|
||||
@@ -50,8 +50,9 @@ class Windows {
|
||||
calloc.free(operationPtr);
|
||||
|
||||
if (result <= 32) {
|
||||
throw Exception('Failed to launch $command with UAC');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class AppController {
|
||||
late Function updateClashConfigDebounce;
|
||||
late Function updateGroupDebounce;
|
||||
late Function addCheckIpNumDebounce;
|
||||
late Function applyProfileDebounce;
|
||||
|
||||
AppController(this.context) {
|
||||
appState = context.read<AppState>();
|
||||
@@ -34,6 +35,9 @@ class AppController {
|
||||
updateClashConfigDebounce = debounce<Function()>(() async {
|
||||
await updateClashConfig();
|
||||
});
|
||||
applyProfileDebounce = debounce<Function()>(() async {
|
||||
await applyProfile(isPrue: true);
|
||||
});
|
||||
addCheckIpNumDebounce = debounce(() {
|
||||
appState.checkIpNum++;
|
||||
});
|
||||
@@ -55,8 +59,7 @@ class AppController {
|
||||
updateRunTime,
|
||||
updateTraffic,
|
||||
];
|
||||
if (Platform.isAndroid) return;
|
||||
await applyProfile(isPrue: true);
|
||||
applyProfileDebounce();
|
||||
} else {
|
||||
await globalState.handleStop();
|
||||
clashCore.resetTraffic();
|
||||
@@ -367,6 +370,10 @@ class AppController {
|
||||
);
|
||||
}
|
||||
|
||||
showSnackBar(String message) {
|
||||
globalState.showSnackBar(context, message: message);
|
||||
}
|
||||
|
||||
addProfileFormURL(String url) async {
|
||||
if (globalState.navigatorKey.currentState?.canPop() ?? false) {
|
||||
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
|
||||
|
||||
@@ -52,7 +52,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
try {
|
||||
await appController.updateProfile(profile);
|
||||
if (profile.id == appController.config.currentProfile?.id) {
|
||||
appController.applyProfile(isPrue: true);
|
||||
appController.applyProfileDebounce();
|
||||
}
|
||||
} catch (e) {
|
||||
messages.add("${profile.label ?? profile.id}: $e \n");
|
||||
@@ -225,7 +225,7 @@ class ProfileItem extends StatelessWidget {
|
||||
);
|
||||
await appController.updateProfile(profile);
|
||||
if (profile.id == appController.config.currentProfile?.id) {
|
||||
appController.applyProfile(isPrue: true);
|
||||
appController.applyProfileDebounce();
|
||||
}
|
||||
} catch (e) {
|
||||
config.setProfile(
|
||||
|
||||
@@ -241,5 +241,7 @@
|
||||
"tight": "Tight",
|
||||
"standard": "Standard",
|
||||
"loose": "Loose",
|
||||
"profilesSort": "Profiles sort"
|
||||
"profilesSort": "Profiles sort",
|
||||
"start": "Start",
|
||||
"stop": "Stop"
|
||||
}
|
||||
@@ -241,5 +241,7 @@
|
||||
"tight": "宽松",
|
||||
"standard": "标准",
|
||||
"loose": "紧凑",
|
||||
"profilesSort": "配置排序"
|
||||
"profilesSort": "配置排序",
|
||||
"start": "启动",
|
||||
"stop": "暂停"
|
||||
}
|
||||
@@ -316,7 +316,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("Source"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
|
||||
"start": MessageLookupByLibrary.simpleMessage("Start"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."),
|
||||
"stop": MessageLookupByLibrary.simpleMessage("Stop"),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("Style"),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
|
||||
|
||||
@@ -253,7 +253,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"sort": MessageLookupByLibrary.simpleMessage("排序"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("来源"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("标准"),
|
||||
"start": MessageLookupByLibrary.simpleMessage("启动"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
|
||||
"stop": MessageLookupByLibrary.simpleMessage("暂停"),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("风格"),
|
||||
"submit": MessageLookupByLibrary.simpleMessage("提交"),
|
||||
|
||||
@@ -2479,6 +2479,26 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Start`
|
||||
String get start {
|
||||
return Intl.message(
|
||||
'Start',
|
||||
name: 'start',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Stop`
|
||||
String get stop {
|
||||
return Intl.message(
|
||||
'Stop',
|
||||
name: 'stop',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -18,6 +18,7 @@ Future<void> main() async {
|
||||
clashCore.initMessage();
|
||||
globalState.packageInfo = await PackageInfo.fromPlatform();
|
||||
final config = await preferences.getConfig() ?? Config();
|
||||
globalState.autoRun = config.autoRun;
|
||||
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
|
||||
await android?.init();
|
||||
await window?.init(config.windowProps);
|
||||
|
||||
@@ -538,6 +538,7 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool get showLabel => _showLabel;
|
||||
|
||||
set showLabel(bool value) {
|
||||
|
||||
@@ -52,7 +52,8 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
||||
WindowProps.fromJson(json['windowProps'] as Map<String, dynamic>?)
|
||||
..vpnProps = VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?)
|
||||
..desktopProps =
|
||||
DesktopProps.fromJson(json['desktopProps'] as Map<String, dynamic>?);
|
||||
DesktopProps.fromJson(json['desktopProps'] as Map<String, dynamic>?)
|
||||
..showLabel = json['showLabel'] as bool? ?? false;
|
||||
|
||||
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'profiles': instance.profiles,
|
||||
@@ -83,6 +84,7 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'windowProps': instance.windowProps,
|
||||
'vpnProps': instance.vpnProps,
|
||||
'desktopProps': instance.desktopProps,
|
||||
'showLabel': instance.showLabel,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
|
||||
@@ -960,7 +960,9 @@ abstract class _ApplicationSelectorState implements ApplicationSelectorState {
|
||||
mixin _$TrayContainerSelectorState {
|
||||
Mode get mode => throw _privateConstructorUsedError;
|
||||
bool get autoLaunch => throw _privateConstructorUsedError;
|
||||
bool get isRun => throw _privateConstructorUsedError;
|
||||
bool get systemProxy => throw _privateConstructorUsedError;
|
||||
bool get tunEnable => throw _privateConstructorUsedError;
|
||||
bool get isStart => throw _privateConstructorUsedError;
|
||||
String? get locale => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@@ -975,7 +977,13 @@ abstract class $TrayContainerSelectorStateCopyWith<$Res> {
|
||||
_$TrayContainerSelectorStateCopyWithImpl<$Res,
|
||||
TrayContainerSelectorState>;
|
||||
@useResult
|
||||
$Res call({Mode mode, bool autoLaunch, bool isRun, String? locale});
|
||||
$Res call(
|
||||
{Mode mode,
|
||||
bool autoLaunch,
|
||||
bool systemProxy,
|
||||
bool tunEnable,
|
||||
bool isStart,
|
||||
String? locale});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -994,7 +1002,9 @@ class _$TrayContainerSelectorStateCopyWithImpl<$Res,
|
||||
$Res call({
|
||||
Object? mode = null,
|
||||
Object? autoLaunch = null,
|
||||
Object? isRun = null,
|
||||
Object? systemProxy = null,
|
||||
Object? tunEnable = null,
|
||||
Object? isStart = null,
|
||||
Object? locale = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
@@ -1006,9 +1016,17 @@ class _$TrayContainerSelectorStateCopyWithImpl<$Res,
|
||||
? _value.autoLaunch
|
||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isRun: null == isRun
|
||||
? _value.isRun
|
||||
: isRun // ignore: cast_nullable_to_non_nullable
|
||||
systemProxy: null == systemProxy
|
||||
? _value.systemProxy
|
||||
: systemProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
tunEnable: null == tunEnable
|
||||
? _value.tunEnable
|
||||
: tunEnable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isStart: null == isStart
|
||||
? _value.isStart
|
||||
: isStart // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
locale: freezed == locale
|
||||
? _value.locale
|
||||
@@ -1027,7 +1045,13 @@ abstract class _$$TrayContainerSelectorStateImplCopyWith<$Res>
|
||||
__$$TrayContainerSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({Mode mode, bool autoLaunch, bool isRun, String? locale});
|
||||
$Res call(
|
||||
{Mode mode,
|
||||
bool autoLaunch,
|
||||
bool systemProxy,
|
||||
bool tunEnable,
|
||||
bool isStart,
|
||||
String? locale});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -1045,7 +1069,9 @@ class __$$TrayContainerSelectorStateImplCopyWithImpl<$Res>
|
||||
$Res call({
|
||||
Object? mode = null,
|
||||
Object? autoLaunch = null,
|
||||
Object? isRun = null,
|
||||
Object? systemProxy = null,
|
||||
Object? tunEnable = null,
|
||||
Object? isStart = null,
|
||||
Object? locale = freezed,
|
||||
}) {
|
||||
return _then(_$TrayContainerSelectorStateImpl(
|
||||
@@ -1057,9 +1083,17 @@ class __$$TrayContainerSelectorStateImplCopyWithImpl<$Res>
|
||||
? _value.autoLaunch
|
||||
: autoLaunch // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isRun: null == isRun
|
||||
? _value.isRun
|
||||
: isRun // ignore: cast_nullable_to_non_nullable
|
||||
systemProxy: null == systemProxy
|
||||
? _value.systemProxy
|
||||
: systemProxy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
tunEnable: null == tunEnable
|
||||
? _value.tunEnable
|
||||
: tunEnable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isStart: null == isStart
|
||||
? _value.isStart
|
||||
: isStart // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
locale: freezed == locale
|
||||
? _value.locale
|
||||
@@ -1075,7 +1109,9 @@ class _$TrayContainerSelectorStateImpl implements _TrayContainerSelectorState {
|
||||
const _$TrayContainerSelectorStateImpl(
|
||||
{required this.mode,
|
||||
required this.autoLaunch,
|
||||
required this.isRun,
|
||||
required this.systemProxy,
|
||||
required this.tunEnable,
|
||||
required this.isStart,
|
||||
required this.locale});
|
||||
|
||||
@override
|
||||
@@ -1083,13 +1119,17 @@ class _$TrayContainerSelectorStateImpl implements _TrayContainerSelectorState {
|
||||
@override
|
||||
final bool autoLaunch;
|
||||
@override
|
||||
final bool isRun;
|
||||
final bool systemProxy;
|
||||
@override
|
||||
final bool tunEnable;
|
||||
@override
|
||||
final bool isStart;
|
||||
@override
|
||||
final String? locale;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TrayContainerSelectorState(mode: $mode, autoLaunch: $autoLaunch, isRun: $isRun, locale: $locale)';
|
||||
return 'TrayContainerSelectorState(mode: $mode, autoLaunch: $autoLaunch, systemProxy: $systemProxy, tunEnable: $tunEnable, isStart: $isStart, locale: $locale)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1100,12 +1140,17 @@ class _$TrayContainerSelectorStateImpl implements _TrayContainerSelectorState {
|
||||
(identical(other.mode, mode) || other.mode == mode) &&
|
||||
(identical(other.autoLaunch, autoLaunch) ||
|
||||
other.autoLaunch == autoLaunch) &&
|
||||
(identical(other.isRun, isRun) || other.isRun == isRun) &&
|
||||
(identical(other.systemProxy, systemProxy) ||
|
||||
other.systemProxy == systemProxy) &&
|
||||
(identical(other.tunEnable, tunEnable) ||
|
||||
other.tunEnable == tunEnable) &&
|
||||
(identical(other.isStart, isStart) || other.isStart == isStart) &&
|
||||
(identical(other.locale, locale) || other.locale == locale));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, mode, autoLaunch, isRun, locale);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, mode, autoLaunch, systemProxy, tunEnable, isStart, locale);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -1120,7 +1165,9 @@ abstract class _TrayContainerSelectorState
|
||||
const factory _TrayContainerSelectorState(
|
||||
{required final Mode mode,
|
||||
required final bool autoLaunch,
|
||||
required final bool isRun,
|
||||
required final bool systemProxy,
|
||||
required final bool tunEnable,
|
||||
required final bool isStart,
|
||||
required final String? locale}) = _$TrayContainerSelectorStateImpl;
|
||||
|
||||
@override
|
||||
@@ -1128,7 +1175,11 @@ abstract class _TrayContainerSelectorState
|
||||
@override
|
||||
bool get autoLaunch;
|
||||
@override
|
||||
bool get isRun;
|
||||
bool get systemProxy;
|
||||
@override
|
||||
bool get tunEnable;
|
||||
@override
|
||||
bool get isStart;
|
||||
@override
|
||||
String? get locale;
|
||||
@override
|
||||
|
||||
@@ -64,7 +64,9 @@ class TrayContainerSelectorState with _$TrayContainerSelectorState {
|
||||
const factory TrayContainerSelectorState({
|
||||
required Mode mode,
|
||||
required bool autoLaunch,
|
||||
required bool isRun,
|
||||
required bool systemProxy,
|
||||
required bool tunEnable,
|
||||
required bool isStart,
|
||||
required String? locale,
|
||||
}) = _TrayContainerSelectorState;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class GlobalState {
|
||||
Timer? timer;
|
||||
Timer? groupsUpdateTimer;
|
||||
var isVpnService = false;
|
||||
var autoRun = false;
|
||||
late PackageInfo packageInfo;
|
||||
Function? updateCurrentDelayDebounce;
|
||||
PageController? pageController;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -76,8 +77,12 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
);
|
||||
}
|
||||
|
||||
_changeProfileHandle() {
|
||||
_changeProfile() async {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (globalState.autoRun) {
|
||||
globalState.autoRun = false;
|
||||
return;
|
||||
}
|
||||
final appController = globalState.appController;
|
||||
appController.appState.delayMap = {};
|
||||
await appController.applyProfile();
|
||||
@@ -88,7 +93,7 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
return Selector<Config, String?>(
|
||||
selector: (_, config) => config.currentProfileId,
|
||||
builder: (__, state, child) {
|
||||
_changeProfileHandle();
|
||||
_changeProfile();
|
||||
return child!;
|
||||
},
|
||||
child: child,
|
||||
@@ -129,6 +134,9 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
@override
|
||||
void onLog(Log log) {
|
||||
globalState.appController.appState.addLog(log);
|
||||
if (log.logLevel == LogLevel.error) {
|
||||
globalState.appController.showSnackBar(log.payload ?? '');
|
||||
}
|
||||
debugPrint("$log");
|
||||
super.onLog(log);
|
||||
}
|
||||
@@ -150,11 +158,4 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
appController.addCheckIpNumDebounce();
|
||||
super.onLoaded(providerName);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onStarted(String runTime) async {
|
||||
super.onStarted(runTime);
|
||||
final appController = globalState.appController;
|
||||
await appController.applyProfile(isPrue: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@ class _TrayContainerState extends State<TrayContainer> with TrayListener {
|
||||
|
||||
_updateOtherTray() async {
|
||||
if (isTrayInit == false) {
|
||||
await trayManager.setToolTip(
|
||||
appName,
|
||||
);
|
||||
await trayManager.setIcon(
|
||||
other.getTrayIconPath(),
|
||||
);
|
||||
await trayManager.setToolTip(
|
||||
appName,
|
||||
);
|
||||
isTrayInit = true;
|
||||
}
|
||||
}
|
||||
@@ -52,92 +52,121 @@ class _TrayContainerState extends State<TrayContainer> with TrayListener {
|
||||
);
|
||||
}
|
||||
|
||||
updateMenu(TrayContainerSelectorState state) async {
|
||||
if (!Platform.isLinux) {
|
||||
_updateOtherTray();
|
||||
}
|
||||
List<MenuItem> menuItems = [];
|
||||
final showMenuItem = MenuItem(
|
||||
label: appLocalizations.show,
|
||||
onClick: (_) {
|
||||
window?.show();
|
||||
},
|
||||
);
|
||||
menuItems.add(showMenuItem);
|
||||
menuItems.add(MenuItem.separator());
|
||||
// for (final group in state.groups) {
|
||||
// List<MenuItem> subMenuItems = [];
|
||||
// final isCurrentGroup = group.name == state.currentGroupName;
|
||||
// for (final proxy in group.all) {
|
||||
// final isCurrentProxy = proxy.name == state.currentProxyName;
|
||||
// subMenuItems.add(
|
||||
// MenuItem.checkbox(
|
||||
// label: proxy.name,
|
||||
// checked: isCurrentGroup && isCurrentProxy,
|
||||
// onClick: (_) {
|
||||
// final config = globalState.appController.config;
|
||||
// config.currentProfile?.groupName = group.name;
|
||||
// config.currentProfile?.proxyName = proxy.name;
|
||||
// config.update();
|
||||
// globalState.appController.changeProxy();
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
// menuItems.add(
|
||||
// MenuItem.submenu(
|
||||
// label: group.name,
|
||||
// submenu: Menu(
|
||||
// items: subMenuItems,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// if (state.groups.isNotEmpty) {
|
||||
// menuItems.add(MenuItem.separator());
|
||||
// }
|
||||
for (final mode in Mode.values) {
|
||||
menuItems.add(
|
||||
MenuItem.checkbox(
|
||||
label: Intl.message(mode.name),
|
||||
onClick: (_) {
|
||||
globalState.appController.clashConfig.mode = mode;
|
||||
},
|
||||
checked: mode == state.mode,
|
||||
),
|
||||
updateMenu(TrayContainerSelectorState state) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!Platform.isLinux) {
|
||||
_updateOtherTray();
|
||||
}
|
||||
List<MenuItem> menuItems = [];
|
||||
final showMenuItem = MenuItem(
|
||||
label: appLocalizations.show,
|
||||
onClick: (_) {
|
||||
window?.show();
|
||||
},
|
||||
);
|
||||
}
|
||||
menuItems.add(MenuItem.separator());
|
||||
final proxyMenuItem = MenuItem.checkbox(
|
||||
label: appLocalizations.systemProxy,
|
||||
onClick: (_) async {
|
||||
globalState.appController.updateStatus(!state.isRun);
|
||||
},
|
||||
checked: state.isRun,
|
||||
);
|
||||
menuItems.add(proxyMenuItem);
|
||||
final autoStartMenuItem = MenuItem.checkbox(
|
||||
label: appLocalizations.autoLaunch,
|
||||
onClick: (_) async {
|
||||
globalState.appController.config.autoLaunch =
|
||||
!globalState.appController.config.autoLaunch;
|
||||
},
|
||||
checked: state.autoLaunch,
|
||||
);
|
||||
menuItems.add(autoStartMenuItem);
|
||||
menuItems.add(MenuItem.separator());
|
||||
final exitMenuItem = MenuItem(
|
||||
label: appLocalizations.exit,
|
||||
onClick: (_) async {
|
||||
await globalState.appController.handleExit();
|
||||
},
|
||||
);
|
||||
menuItems.add(exitMenuItem);
|
||||
final menu = Menu();
|
||||
menu.items = menuItems;
|
||||
trayManager.setContextMenu(menu);
|
||||
if (Platform.isLinux) {
|
||||
_updateLinuxTray();
|
||||
}
|
||||
menuItems.add(showMenuItem);
|
||||
final startMenuItem = MenuItem.checkbox(
|
||||
label: state.isStart ? appLocalizations.stop : appLocalizations.start,
|
||||
onClick: (_) async {
|
||||
globalState.appController.updateStatus(!state.isStart);
|
||||
},
|
||||
checked: false,
|
||||
);
|
||||
menuItems.add(startMenuItem);
|
||||
menuItems.add(MenuItem.separator());
|
||||
// for (final group in state.groups) {
|
||||
// List<MenuItem> subMenuItems = [];
|
||||
// final isCurrentGroup = group.name == state.currentGroupName;
|
||||
// for (final proxy in group.all) {
|
||||
// final isCurrentProxy = proxy.name == state.currentProxyName;
|
||||
// subMenuItems.add(
|
||||
// MenuItem.checkbox(
|
||||
// label: proxy.name,
|
||||
// checked: isCurrentGroup && isCurrentProxy,
|
||||
// onClick: (_) {
|
||||
// final config = globalState.appController.config;
|
||||
// config.currentProfile?.groupName = group.name;
|
||||
// config.currentProfile?.proxyName = proxy.name;
|
||||
// config.update();
|
||||
// globalState.appController.changeProxy();
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
// menuItems.add(
|
||||
// MenuItem.submenu(
|
||||
// label: group.name,
|
||||
// submenu: Menu(
|
||||
// items: subMenuItems,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// if (state.groups.isNotEmpty) {
|
||||
// menuItems.add(MenuItem.separator());
|
||||
// }
|
||||
for (final mode in Mode.values) {
|
||||
menuItems.add(
|
||||
MenuItem.checkbox(
|
||||
label: Intl.message(mode.name),
|
||||
onClick: (_) {
|
||||
globalState.appController.clashConfig.mode = mode;
|
||||
},
|
||||
checked: mode == state.mode,
|
||||
),
|
||||
);
|
||||
}
|
||||
menuItems.add(MenuItem.separator());
|
||||
|
||||
if (state.isStart) {
|
||||
menuItems.add(
|
||||
MenuItem.checkbox(
|
||||
label: appLocalizations.tun,
|
||||
onClick: (_) {
|
||||
final clashConfig = globalState.appController.clashConfig;
|
||||
clashConfig.tun =
|
||||
clashConfig.tun.copyWith(enable: !state.tunEnable);
|
||||
},
|
||||
checked: state.tunEnable,
|
||||
),
|
||||
);
|
||||
menuItems.add(
|
||||
MenuItem.checkbox(
|
||||
label: appLocalizations.systemProxy,
|
||||
onClick: (_) {
|
||||
final config = globalState.appController.config;
|
||||
config.desktopProps =
|
||||
config.desktopProps.copyWith(systemProxy: !state.systemProxy);
|
||||
},
|
||||
checked: state.systemProxy,
|
||||
),
|
||||
);
|
||||
menuItems.add(MenuItem.separator());
|
||||
}
|
||||
|
||||
final autoStartMenuItem = MenuItem.checkbox(
|
||||
label: appLocalizations.autoLaunch,
|
||||
onClick: (_) async {
|
||||
globalState.appController.config.autoLaunch =
|
||||
!globalState.appController.config.autoLaunch;
|
||||
},
|
||||
checked: state.autoLaunch,
|
||||
);
|
||||
menuItems.add(autoStartMenuItem);
|
||||
menuItems.add(MenuItem.separator());
|
||||
final exitMenuItem = MenuItem(
|
||||
label: appLocalizations.exit,
|
||||
onClick: (_) async {
|
||||
await globalState.appController.handleExit();
|
||||
},
|
||||
);
|
||||
menuItems.add(exitMenuItem);
|
||||
final menu = Menu();
|
||||
menu.items = menuItems;
|
||||
trayManager.setContextMenu(menu);
|
||||
if (Platform.isLinux) {
|
||||
_updateLinuxTray();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -147,13 +176,13 @@ class _TrayContainerState extends State<TrayContainer> with TrayListener {
|
||||
TrayContainerSelectorState(
|
||||
mode: clashConfig.mode,
|
||||
autoLaunch: config.autoLaunch,
|
||||
isRun: appState.runTime != null,
|
||||
isStart: appState.isStart,
|
||||
locale: config.locale,
|
||||
systemProxy: config.desktopProps.systemProxy,
|
||||
tunEnable: clashConfig.tun.enable,
|
||||
),
|
||||
builder: (_, state, child) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
updateMenu(state);
|
||||
});
|
||||
updateMenu(state);
|
||||
return child!;
|
||||
},
|
||||
child: widget.child,
|
||||
|
||||
@@ -20,11 +20,17 @@ class WindowContainer extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _WindowContainerState extends State<WindowContainer> with WindowListener {
|
||||
Function? updateLaunchDebounce;
|
||||
|
||||
_autoLaunchContainer(Widget child) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.autoLaunch,
|
||||
return Selector2<Config, ClashConfig, AutoLaunchState>(
|
||||
selector: (_, config, clashConfig) => AutoLaunchState(
|
||||
isAutoLaunch: config.autoLaunch, isOpenTun: clashConfig.tun.enable),
|
||||
builder: (_, state, child) {
|
||||
autoLaunch?.updateStatus(state);
|
||||
updateLaunchDebounce ??= debounce((AutoLaunchState state) {
|
||||
autoLaunch?.updateStatus(state);
|
||||
});
|
||||
updateLaunchDebounce!([state]);
|
||||
return child!;
|
||||
},
|
||||
child: child,
|
||||
|
||||
@@ -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.54+202408221
|
||||
version: 0.8.55+202408251
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
flutter: 3.22.3
|
||||
|
||||
@@ -18,12 +18,6 @@ add_executable(${BINARY_NAME} WIN32
|
||||
"runner.exe.manifest"
|
||||
)
|
||||
|
||||
SET_TARGET_PROPERTIES(${BINARY_NAME} PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:WINDOWS")
|
||||
|
||||
# add_executable(service
|
||||
# "service.cpp"
|
||||
# )
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
#include <string>
|
||||
|
||||
#define SERVICE_NAME _T("MyService")
|
||||
|
||||
SERVICE_STATUS g_ServiceStatus = {0};
|
||||
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
|
||||
HANDLE g_ServiceStopEvent = INVALID_HANDLE_VALUE;
|
||||
|
||||
VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv);
|
||||
VOID WINAPI ServiceCtrlHandler(DWORD);
|
||||
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam);
|
||||
|
||||
int _tmain(int argc, TCHAR *argv[])
|
||||
{
|
||||
SERVICE_TABLE_ENTRY ServiceTable[] =
|
||||
{
|
||||
{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
|
||||
{
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv)
|
||||
{
|
||||
DWORD Status = E_FAIL;
|
||||
|
||||
g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrlHandler);
|
||||
|
||||
if (g_StatusHandle == NULL)
|
||||
{
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
ZeroMemory(&g_ServiceStatus, sizeof(g_ServiceStatus));
|
||||
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwServiceSpecificExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 0;
|
||||
|
||||
if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE)
|
||||
{
|
||||
OutputDebugString(_T("My Service: ServiceMain: SetServiceStatus returned error"));
|
||||
}
|
||||
|
||||
g_ServiceStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (g_ServiceStopEvent == NULL)
|
||||
{
|
||||
g_ServiceStatus.dwControlsAccepted = 0;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = GetLastError();
|
||||
g_ServiceStatus.dwCheckPoint = 1;
|
||||
|
||||
if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE)
|
||||
{
|
||||
OutputDebugString(_T("My Service: ServiceMain: SetServiceStatus returned error"));
|
||||
}
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
||||
g_ServiceStatus.dwCheckPoint = 0;
|
||||
g_ServiceStatus.dwWaitHint = 0;
|
||||
|
||||
if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE)
|
||||
{
|
||||
OutputDebugString(_T("My Service: ServiceMain: SetServiceStatus returned error"));
|
||||
}
|
||||
|
||||
HANDLE hThread = CreateThread(NULL, 0, ServiceWorkerThread, NULL, 0, NULL);
|
||||
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
|
||||
CloseHandle(g_ServiceStopEvent);
|
||||
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwCheckPoint = 3;
|
||||
|
||||
if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE)
|
||||
{
|
||||
OutputDebugString(_T("My Service: ServiceMain: SetServiceStatus returned error"));
|
||||
}
|
||||
|
||||
EXIT:
|
||||
return;
|
||||
}
|
||||
|
||||
VOID WINAPI ServiceCtrlHandler(DWORD CtrlCode)
|
||||
{
|
||||
switch(CtrlCode)
|
||||
{
|
||||
case SERVICE_CONTROL_STOP:
|
||||
if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
|
||||
break;
|
||||
|
||||
g_ServiceStatus.dwControlsAccepted = 0;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 4;
|
||||
|
||||
if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE)
|
||||
{
|
||||
OutputDebugString(_T("My Service: ServiceCtrlHandler: SetServiceStatus returned error"));
|
||||
}
|
||||
|
||||
SetEvent(g_ServiceStopEvent);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
{
|
||||
while(WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0)
|
||||
{
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
// 启动 "C:\path\to\your\executable.exe"
|
||||
if(!CreateProcess(NULL, _T("C:\\path\\to\\your\\executable.exe"), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
|
||||
{
|
||||
OutputDebugString(_T("CreateProcess failed"));
|
||||
}
|
||||
|
||||
// 等待进程结束
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
|
||||
// 关闭进程和线程句柄
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
// 每隔一段时间检查一次,这里设置为60秒
|
||||
Sleep(60000);
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user