2024-04-30 23:38:49 +08:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
2024-12-03 21:47:12 +08:00
|
|
|
import 'package:fl_clash/common/common.dart';
|
|
|
|
|
import 'package:fl_clash/enum/enum.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:fl_clash/plugins/app.dart';
|
2024-12-03 21:47:12 +08:00
|
|
|
import 'package:fl_clash/state.dart';
|
|
|
|
|
import 'package:fl_clash/widgets/input.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
|
|
|
|
|
class System {
|
|
|
|
|
static System? _instance;
|
2025-05-02 02:24:12 +08:00
|
|
|
List<String>? originDns;
|
2024-04-30 23:38:49 +08:00
|
|
|
|
|
|
|
|
System._internal();
|
|
|
|
|
|
|
|
|
|
factory System() {
|
|
|
|
|
_instance ??= System._internal();
|
|
|
|
|
return _instance!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool get isDesktop =>
|
|
|
|
|
Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
Future<int> get version async {
|
|
|
|
|
final deviceInfo = await DeviceInfoPlugin().deviceInfo;
|
|
|
|
|
return switch (Platform.operatingSystem) {
|
|
|
|
|
"macos" => (deviceInfo as MacOsDeviceInfo).majorVersion,
|
|
|
|
|
"android" => (deviceInfo as AndroidDeviceInfo).version.sdkInt,
|
|
|
|
|
"windows" => (deviceInfo as WindowsDeviceInfo).majorVersion,
|
|
|
|
|
String() => 0
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
Future<bool> checkIsAdmin() async {
|
|
|
|
|
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
|
|
|
|
|
if (Platform.isWindows) {
|
|
|
|
|
final result = await windows?.checkService();
|
|
|
|
|
return result == WindowsHelperServiceStatus.running;
|
|
|
|
|
} else if (Platform.isMacOS) {
|
|
|
|
|
final result = await Process.run('stat', ['-f', '%Su:%Sg %Sp', corePath]);
|
|
|
|
|
final output = result.stdout.trim();
|
|
|
|
|
if (output.startsWith('root:admin') && output.contains('rws')) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
} else if (Platform.isLinux) {
|
|
|
|
|
final result = await Process.run('stat', ['-c', '%U:%G %A', corePath]);
|
|
|
|
|
final output = result.stdout.trim();
|
2025-02-09 18:39:38 +08:00
|
|
|
if (output.startsWith('root:') && output.contains('rws')) {
|
2024-12-03 21:47:12 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<AuthorizeCode> authorizeCore() async {
|
2025-04-18 17:50:46 +08:00
|
|
|
if (Platform.isAndroid) {
|
2025-05-02 02:24:12 +08:00
|
|
|
return AuthorizeCode.error;
|
2025-04-18 17:50:46 +08:00
|
|
|
}
|
2024-12-03 21:47:12 +08:00
|
|
|
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
|
|
|
|
|
final isAdmin = await checkIsAdmin();
|
|
|
|
|
if (isAdmin) {
|
|
|
|
|
return AuthorizeCode.none;
|
|
|
|
|
}
|
2025-04-18 17:50:46 +08:00
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
if (Platform.isWindows) {
|
|
|
|
|
final result = await windows?.registerService();
|
|
|
|
|
if (result == true) {
|
|
|
|
|
return AuthorizeCode.success;
|
|
|
|
|
}
|
|
|
|
|
return AuthorizeCode.error;
|
2025-04-18 17:50:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Platform.isMacOS) {
|
2024-12-03 21:47:12 +08:00
|
|
|
final shell = 'chown root:admin $corePath; chmod +sx $corePath';
|
|
|
|
|
final arguments = [
|
|
|
|
|
"-e",
|
|
|
|
|
'do shell script "$shell" with administrator privileges',
|
|
|
|
|
];
|
|
|
|
|
final result = await Process.run("osascript", arguments);
|
|
|
|
|
if (result.exitCode != 0) {
|
|
|
|
|
return AuthorizeCode.error;
|
|
|
|
|
}
|
|
|
|
|
return AuthorizeCode.success;
|
|
|
|
|
} else if (Platform.isLinux) {
|
|
|
|
|
final shell = Platform.environment['SHELL'] ?? 'bash';
|
|
|
|
|
final password = await globalState.showCommonDialog<String>(
|
|
|
|
|
child: InputDialog(
|
|
|
|
|
title: appLocalizations.pleaseInputAdminPassword,
|
|
|
|
|
value: '',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
final arguments = [
|
|
|
|
|
"-c",
|
|
|
|
|
'echo "$password" | sudo -S chown root:root "$corePath" && echo "$password" | sudo -S chmod +sx "$corePath"'
|
|
|
|
|
];
|
|
|
|
|
final result = await Process.run(shell, arguments);
|
|
|
|
|
if (result.exitCode != 0) {
|
|
|
|
|
return AuthorizeCode.error;
|
|
|
|
|
}
|
|
|
|
|
return AuthorizeCode.success;
|
|
|
|
|
}
|
|
|
|
|
return AuthorizeCode.error;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-02 02:24:12 +08:00
|
|
|
Future<String?> getMacOSDefaultServiceName() async {
|
|
|
|
|
if (!Platform.isMacOS) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
final result = await Process.run('route', ['-n', 'get', 'default']);
|
|
|
|
|
final output = result.stdout.toString();
|
|
|
|
|
final deviceLine = output
|
|
|
|
|
.split('\n')
|
|
|
|
|
.firstWhere((s) => s.contains('interface:'), orElse: () => "");
|
|
|
|
|
final lineSplits = deviceLine.trim().split(' ');
|
|
|
|
|
if (lineSplits.length != 2) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
final device = lineSplits[1];
|
|
|
|
|
final serviceResult = await Process.run(
|
|
|
|
|
'networksetup',
|
|
|
|
|
['-listnetworkserviceorder'],
|
|
|
|
|
);
|
|
|
|
|
final serviceResultOutput = serviceResult.stdout.toString();
|
|
|
|
|
final currentService = serviceResultOutput.split('\n\n').firstWhere(
|
|
|
|
|
(s) => s.contains("Device: $device"),
|
|
|
|
|
orElse: () => "",
|
|
|
|
|
);
|
|
|
|
|
if (currentService.isEmpty) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
final currentServiceNameLine = currentService.split("\n").firstWhere(
|
|
|
|
|
(line) => RegExp(r'^\(\d+\).*').hasMatch(line),
|
|
|
|
|
orElse: () => "");
|
|
|
|
|
final currentServiceNameLineSplits =
|
|
|
|
|
currentServiceNameLine.trim().split(' ');
|
|
|
|
|
if (currentServiceNameLineSplits.length < 2) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return currentServiceNameLineSplits[1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<List<String>?> getMacOSOriginDns() async {
|
|
|
|
|
if (!Platform.isMacOS) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
final deviceServiceName = await getMacOSDefaultServiceName();
|
|
|
|
|
if (deviceServiceName == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
final result = await Process.run(
|
|
|
|
|
'networksetup',
|
|
|
|
|
['-getdnsservers', deviceServiceName],
|
|
|
|
|
);
|
|
|
|
|
final output = result.stdout.toString().trim();
|
|
|
|
|
if (output.startsWith("There aren't any DNS Servers set on")) {
|
|
|
|
|
originDns = [];
|
|
|
|
|
} else {
|
|
|
|
|
originDns = output.split("\n");
|
|
|
|
|
}
|
|
|
|
|
return originDns;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setMacOSDns(bool restore) async {
|
|
|
|
|
if (!Platform.isMacOS) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final serviceName = await getMacOSDefaultServiceName();
|
|
|
|
|
if (serviceName == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
List<String>? nextDns;
|
|
|
|
|
if (restore) {
|
|
|
|
|
nextDns = originDns;
|
|
|
|
|
} else {
|
|
|
|
|
final originDns = await system.getMacOSOriginDns();
|
|
|
|
|
if (originDns == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final needAddDns = "223.5.5.5";
|
|
|
|
|
if (originDns.contains(needAddDns)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
nextDns = List.from(originDns)..add(needAddDns);
|
|
|
|
|
}
|
|
|
|
|
if (nextDns == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await Process.run(
|
|
|
|
|
'networksetup',
|
|
|
|
|
[
|
|
|
|
|
'-setdnsservers',
|
|
|
|
|
serviceName,
|
|
|
|
|
if (nextDns.isNotEmpty) ...nextDns,
|
|
|
|
|
if (nextDns.isEmpty) "Empty",
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
back() async {
|
|
|
|
|
await app?.moveTaskToBack();
|
|
|
|
|
await window?.hide();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exit() async {
|
|
|
|
|
if (Platform.isAndroid) {
|
|
|
|
|
await SystemNavigator.pop();
|
|
|
|
|
}
|
|
|
|
|
await window?.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final system = System();
|