Fix windows tray issues

Optimize windows logic
This commit is contained in:
chen08209
2024-08-23 18:33:40 +08:00
parent c38a671d57
commit f6d9ed11d9
24 changed files with 315 additions and 322 deletions

View File

@@ -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)

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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(

View File

@@ -241,5 +241,7 @@
"tight": "Tight",
"standard": "Standard",
"loose": "Loose",
"profilesSort": "Profiles sort"
"profilesSort": "Profiles sort",
"start": "Start",
"stop": "Stop"
}

View File

@@ -241,5 +241,7 @@
"tight": "宽松",
"standard": "标准",
"loose": "紧凑",
"profilesSort": "配置排序"
"profilesSort": "配置排序",
"start": "启动",
"stop": "暂停"
}

View File

@@ -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"),

View File

@@ -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("提交"),

View File

@@ -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> {

View File

@@ -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);

View File

@@ -538,6 +538,7 @@ class Config extends ChangeNotifier {
}
}
@JsonKey(defaultValue: false)
bool get showLabel => _showLabel;
set showLabel(bool value) {

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -19,6 +19,7 @@ class GlobalState {
Timer? timer;
Timer? groupsUpdateTimer;
var isVpnService = false;
var autoRun = false;
late PackageInfo packageInfo;
Function? updateCurrentDelayDebounce;
PageController? pageController;

View File

@@ -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);
}
}

View File

@@ -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,

View File

@@ -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,

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.54+202408221
version: 0.8.55+202408251
environment:
sdk: '>=3.1.0 <4.0.0'
flutter: 3.22.3

View File

@@ -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})

View File

@@ -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;
}