From 9aec1e75e47f28b354bb011cb51d539f4e883188 Mon Sep 17 00:00:00 2001 From: chen08209 Date: Fri, 23 Aug 2024 18:33:40 +0800 Subject: [PATCH] Fix windows tray issues Optimize windows logic --- core/common.go | 5 +- lib/common/launch.dart | 63 +++--- lib/common/picker.dart | 9 +- lib/common/system.dart | 6 + lib/common/windows.dart | 5 +- lib/controller.dart | 11 +- lib/fragments/profiles/profiles.dart | 4 +- lib/l10n/arb/intl_en.arb | 4 +- lib/l10n/arb/intl_zh_CN.arb | 4 +- lib/l10n/intl/messages_en.dart | 2 + lib/l10n/intl/messages_zh_CN.dart | 2 + lib/l10n/l10n.dart | 20 ++ lib/main.dart | 1 + lib/models/config.dart | 1 + lib/models/generated/config.g.dart | 4 +- lib/models/generated/selector.freezed.dart | 87 +++++++-- lib/models/selector.dart | 4 +- lib/state.dart | 1 + lib/widgets/clash_container.dart | 19 +- lib/widgets/tray_container.dart | 213 ++++++++++++--------- lib/widgets/window_container.dart | 12 +- pubspec.yaml | 2 +- windows/runner/CMakeLists.txt | 6 - windows/runner/service.cpp | 152 --------------- 24 files changed, 315 insertions(+), 322 deletions(-) delete mode 100644 windows/runner/service.cpp diff --git a/core/common.go b/core/common.go index 6b0798f..5065a94 100644 --- a/core/common.go +++ b/core/common.go @@ -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) diff --git a/lib/common/launch.dart b/lib/common/launch.dart index 05daf34..2d05e47 100644 --- a/lib/common/launch.dart +++ b/lib/common/launch.dart @@ -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 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 windowsEnable() async { + await disable(); + return windows?.runas( + 'schtasks', + [ + '/Create', + '/SC', + 'ONLOGON', + '/TN', + appName, + '/TR', + Platform.resolvedExecutable, + '/RL', + 'HIGHEST', + '/F' + ].join(" "), + ) ?? + false; } Future 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(); diff --git a/lib/common/picker.dart b/lib/common/picker.dart index fa98382..c894bfd 100644 --- a/lib/common/picker.dart +++ b/lib/common/picker.dart @@ -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 saveFile(String fileName,Uint8List bytes) async { + Future 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; } diff --git a/lib/common/system.dart b/lib/common/system.dart index c7e4174..9d61071 100644 --- a/lib/common/system.dart +++ b/lib/common/system.dart @@ -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(); diff --git a/lib/common/windows.dart b/lib/common/windows.dart index b7a0b14..4b7ccd9 100644 --- a/lib/common/windows.dart +++ b/lib/common/windows.dart @@ -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; } } diff --git a/lib/controller.dart b/lib/controller.dart index f4d013d..04acc51 100644 --- a/lib/controller.dart +++ b/lib/controller.dart @@ -26,6 +26,7 @@ class AppController { late Function updateClashConfigDebounce; late Function updateGroupDebounce; late Function addCheckIpNumDebounce; + late Function applyProfileDebounce; AppController(this.context) { appState = context.read(); @@ -34,6 +35,9 @@ class AppController { updateClashConfigDebounce = debounce(() async { await updateClashConfig(); }); + applyProfileDebounce = debounce(() 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); diff --git a/lib/fragments/profiles/profiles.dart b/lib/fragments/profiles/profiles.dart index 61abac9..7d61c8d 100644 --- a/lib/fragments/profiles/profiles.dart +++ b/lib/fragments/profiles/profiles.dart @@ -52,7 +52,7 @@ class _ProfilesFragmentState extends State { 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( diff --git a/lib/l10n/arb/intl_en.arb b/lib/l10n/arb/intl_en.arb index 3ff46e3..e08b7b1 100644 --- a/lib/l10n/arb/intl_en.arb +++ b/lib/l10n/arb/intl_en.arb @@ -241,5 +241,7 @@ "tight": "Tight", "standard": "Standard", "loose": "Loose", - "profilesSort": "Profiles sort" + "profilesSort": "Profiles sort", + "start": "Start", + "stop": "Stop" } \ No newline at end of file diff --git a/lib/l10n/arb/intl_zh_CN.arb b/lib/l10n/arb/intl_zh_CN.arb index e59e8b6..ba571fb 100644 --- a/lib/l10n/arb/intl_zh_CN.arb +++ b/lib/l10n/arb/intl_zh_CN.arb @@ -241,5 +241,7 @@ "tight": "宽松", "standard": "标准", "loose": "紧凑", - "profilesSort": "配置排序" + "profilesSort": "配置排序", + "start": "启动", + "stop": "暂停" } \ No newline at end of file diff --git a/lib/l10n/intl/messages_en.dart b/lib/l10n/intl/messages_en.dart index f1096fb..6d74d4e 100644 --- a/lib/l10n/intl/messages_en.dart +++ b/lib/l10n/intl/messages_en.dart @@ -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"), diff --git a/lib/l10n/intl/messages_zh_CN.dart b/lib/l10n/intl/messages_zh_CN.dart index 73461d7..1c5f4f9 100644 --- a/lib/l10n/intl/messages_zh_CN.dart +++ b/lib/l10n/intl/messages_zh_CN.dart @@ -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("提交"), diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index d42b732..f730617 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -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 { diff --git a/lib/main.dart b/lib/main.dart index 4c2b166..69b07b7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,6 +18,7 @@ Future 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); diff --git a/lib/models/config.dart b/lib/models/config.dart index 1a84587..c90f1d6 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -538,6 +538,7 @@ class Config extends ChangeNotifier { } } + @JsonKey(defaultValue: false) bool get showLabel => _showLabel; set showLabel(bool value) { diff --git a/lib/models/generated/config.g.dart b/lib/models/generated/config.g.dart index ca3b413..1f532d3 100644 --- a/lib/models/generated/config.g.dart +++ b/lib/models/generated/config.g.dart @@ -52,7 +52,8 @@ Config _$ConfigFromJson(Map json) => Config() WindowProps.fromJson(json['windowProps'] as Map?) ..vpnProps = VpnProps.fromJson(json['vpnProps'] as Map?) ..desktopProps = - DesktopProps.fromJson(json['desktopProps'] as Map?); + DesktopProps.fromJson(json['desktopProps'] as Map?) + ..showLabel = json['showLabel'] as bool? ?? false; Map _$ConfigToJson(Config instance) => { 'profiles': instance.profiles, @@ -83,6 +84,7 @@ Map _$ConfigToJson(Config instance) => { 'windowProps': instance.windowProps, 'vpnProps': instance.vpnProps, 'desktopProps': instance.desktopProps, + 'showLabel': instance.showLabel, }; const _$ThemeModeEnumMap = { diff --git a/lib/models/generated/selector.freezed.dart b/lib/models/generated/selector.freezed.dart index 181082e..d98ec52 100644 --- a/lib/models/generated/selector.freezed.dart +++ b/lib/models/generated/selector.freezed.dart @@ -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 diff --git a/lib/models/selector.dart b/lib/models/selector.dart index b73d862..d2ff749 100644 --- a/lib/models/selector.dart +++ b/lib/models/selector.dart @@ -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; } diff --git a/lib/state.dart b/lib/state.dart index 7999ac6..c42c5b5 100644 --- a/lib/state.dart +++ b/lib/state.dart @@ -19,6 +19,7 @@ class GlobalState { Timer? timer; Timer? groupsUpdateTimer; var isVpnService = false; + var autoRun = false; late PackageInfo packageInfo; Function? updateCurrentDelayDebounce; PageController? pageController; diff --git a/lib/widgets/clash_container.dart b/lib/widgets/clash_container.dart index 9c74179..51ed096 100644 --- a/lib/widgets/clash_container.dart +++ b/lib/widgets/clash_container.dart @@ -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 ); } - _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 return Selector( selector: (_, config) => config.currentProfileId, builder: (__, state, child) { - _changeProfileHandle(); + _changeProfile(); return child!; }, child: child, @@ -129,6 +134,9 @@ class _ClashContainerState extends State @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 appController.addCheckIpNumDebounce(); super.onLoaded(providerName); } - - @override - Future onStarted(String runTime) async { - super.onStarted(runTime); - final appController = globalState.appController; - await appController.applyProfile(isPrue: true); - } } diff --git a/lib/widgets/tray_container.dart b/lib/widgets/tray_container.dart index 1da8705..d8d34bc 100644 --- a/lib/widgets/tray_container.dart +++ b/lib/widgets/tray_container.dart @@ -32,12 +32,12 @@ class _TrayContainerState extends State 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 with TrayListener { ); } - updateMenu(TrayContainerSelectorState state) async { - if (!Platform.isLinux) { - _updateOtherTray(); - } - List menuItems = []; - final showMenuItem = MenuItem( - label: appLocalizations.show, - onClick: (_) { - window?.show(); - }, - ); - menuItems.add(showMenuItem); - menuItems.add(MenuItem.separator()); - // for (final group in state.groups) { - // List 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 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 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 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, diff --git a/lib/widgets/window_container.dart b/lib/widgets/window_container.dart index c9d71d9..be93adb 100644 --- a/lib/widgets/window_container.dart +++ b/lib/widgets/window_container.dart @@ -20,11 +20,17 @@ class WindowContainer extends StatefulWidget { } class _WindowContainerState extends State with WindowListener { + Function? updateLaunchDebounce; + _autoLaunchContainer(Widget child) { - return Selector( - selector: (_, config) => config.autoLaunch, + return Selector2( + 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, diff --git a/pubspec.yaml b/pubspec.yaml index 501282a..2386994 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt index 8e9f68d..09fcbc0 100644 --- a/windows/runner/CMakeLists.txt +++ b/windows/runner/CMakeLists.txt @@ -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}) diff --git a/windows/runner/service.cpp b/windows/runner/service.cpp deleted file mode 100644 index 610e452..0000000 --- a/windows/runner/service.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include -#include -#include - -#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; -} \ No newline at end of file