alroyso 6 months ago
parent
commit
74885932ad

+ 129 - 91
download.py

@@ -7,12 +7,13 @@ from zipfile import ZipFile
 import stat
 import argparse
 import subprocess
+import tempfile
 
 # 定义常量
 MIHOMO_BASE_URL = 'https://github.com/MetaCubeX/mihomo/releases/download'
-CORE_SERVICE_BASE_URL = 'https://github.com/alroyso/core-service/releases/download/test_2023.11.22_09-33'
+CORE_SERVICE_BASE_URL = 'https://github.com/alroyso/core-service/releases/download'
 MIHOMO_VERSION = 'v1.18.8'  # 根据实际版本修改
-CORE_SERVICE_VERSION = 'test_2023.11.22_09-33'
+CORE_SERVICE_VERSION = 'v1.0.2'
 ASSETS_DIR = './assets/bin'
 SYSTEM = platform.system().lower()
 
@@ -30,40 +31,75 @@ def get_download_urls():
     """
     download_urls = []
 
-    # mihomo 下载目录和 URL
-    mihomo_dir = os.path.join(ASSETS_DIR, 'mihomo')
-    if not os.path.exists(mihomo_dir):
-        os.makedirs(mihomo_dir)
-
-    # core-service 下载目录和 URL
-    core_service_dir = os.path.join(ASSETS_DIR, 'core-service')
-    if not os.path.exists(core_service_dir):
-        os.makedirs(core_service_dir)
-
     # 获取 ClashName 的 platform 和 arch
     platform_name, arch_name = get_platform_arch()
 
-    # 下载 macOS 的 arm64 和 x86_64 架构
+    # 下载 macOS、Linux、Windows 的 arm64 和 x86_64 架构
     if SYSTEM == 'darwin':
+        # macOS
         download_urls.append((
             f'mihomo-darwin-arm64-{MIHOMO_VERSION}.gz',
             f'{MIHOMO_BASE_URL}/{MIHOMO_VERSION}/mihomo-darwin-arm64-{MIHOMO_VERSION}.gz',
-            mihomo_dir, 'arm64'
+            'arm64'
         ))
         download_urls.append((
             f'mihomo-darwin-amd64-{MIHOMO_VERSION}.gz',
             f'{MIHOMO_BASE_URL}/{MIHOMO_VERSION}/mihomo-darwin-amd64-{MIHOMO_VERSION}.gz',
-            mihomo_dir, 'x86_64'
+            'x86_64'
         ))
         download_urls.append((
             f'ccore-service-darwin-arm64-{CORE_SERVICE_VERSION}.gz',
-            f'{CORE_SERVICE_BASE_URL}/ccore-service-darwin-arm64-{CORE_SERVICE_VERSION}.gz',
-            core_service_dir, 'arm64'
+            f'{CORE_SERVICE_BASE_URL}/{CORE_SERVICE_VERSION}/ccore-service-darwin-arm64-{CORE_SERVICE_VERSION}.gz',
+            'arm64'
         ))
         download_urls.append((
             f'ccore-service-darwin-amd64-{CORE_SERVICE_VERSION}.gz',
-            f'{CORE_SERVICE_BASE_URL}/ccore-service-darwin-amd64-{CORE_SERVICE_VERSION}.gz',
-            core_service_dir, 'x86_64'
+            f'{CORE_SERVICE_BASE_URL}/{CORE_SERVICE_VERSION}/ccore-service-darwin-amd64-{CORE_SERVICE_VERSION}.gz',
+            'x86_64'
+        ))
+    elif SYSTEM == 'linux':
+        # Linux
+        download_urls.append((
+            f'mihomo-linux-arm64-{MIHOMO_VERSION}.gz',
+            f'{MIHOMO_BASE_URL}/{MIHOMO_VERSION}/mihomo-linux-arm64-{MIHOMO_VERSION}.gz',
+            'arm64'
+        ))
+        download_urls.append((
+            f'mihomo-linux-amd64-{MIHOMO_VERSION}.gz',
+            f'{MIHOMO_BASE_URL}/{MIHOMO_VERSION}/mihomo-linux-amd64-{MIHOMO_VERSION}.gz',
+            'x86_64'
+        ))
+        download_urls.append((
+            f'ccore-service-linux-arm64-{CORE_SERVICE_VERSION}.gz',
+            f'{CORE_SERVICE_BASE_URL}/{CORE_SERVICE_VERSION}/ccore-service-linux-arm64-{CORE_SERVICE_VERSION}.gz',
+            'arm64'
+        ))
+        download_urls.append((
+            f'ccore-service-linux-amd64-{CORE_SERVICE_VERSION}.gz',
+            f'{CORE_SERVICE_BASE_URL}/{CORE_SERVICE_VERSION}/ccore-service-linux-amd64-{CORE_SERVICE_VERSION}.gz',
+            'x86_64'
+        ))
+    elif SYSTEM == 'windows':
+        # Windows
+        download_urls.append((
+            f'mihomo-windows-arm64-{MIHOMO_VERSION}.zip',
+            f'{MIHOMO_BASE_URL}/{MIHOMO_VERSION}/mihomo-windows-arm64-{MIHOMO_VERSION}.zip',
+            'arm64'
+        ))
+        download_urls.append((
+            f'mihomo-windows-amd64-{MIHOMO_VERSION}.zip',
+            f'{MIHOMO_BASE_URL}/{MIHOMO_VERSION}/mihomo-windows-amd64-{MIHOMO_VERSION}.zip',
+            'x86_64'
+        ))
+        download_urls.append((
+            f'ccore-service-windows-arm64-{CORE_SERVICE_VERSION}.zip',
+            f'{CORE_SERVICE_BASE_URL}/{CORE_SERVICE_VERSION}/ccore-service-windows-arm64-{CORE_SERVICE_VERSION}.zip',
+            'arm64'
+        ))
+        download_urls.append((
+            f'ccore-service-windows-amd64-{CORE_SERVICE_VERSION}.zip',
+            f'{CORE_SERVICE_BASE_URL}/{CORE_SERVICE_VERSION}/ccore-service-windows-amd64-{CORE_SERVICE_VERSION}.zip',
+            'x86_64'
         ))
 
     return download_urls
@@ -72,124 +108,126 @@ def download_file(url, output_path):
     """
     下载文件
     """
-    print(f"Downloading {url} ...")
+    print(f"正在下载 {url} ...")
     response = requests.get(url, stream=True)
     if response.status_code == 200:
         with open(output_path, 'wb') as file:
             shutil.copyfileobj(response.raw, file)
-        print(f"Download complete: {output_path}")
+        print(f"下载完成: {output_path}")
     else:
-        print(f"Failed to download {url}, status code: {response.status_code}")
+        print(f"下载失败 {url}, 状态码: {response.status_code}")
 
 def extract_gz_file(file_path, output_path):
     """
     解压 .gz 文件
     """
-    print(f"Extracting {file_path} ...")
+    print(f"正在解压 {file_path} ...")
     with gzip.open(file_path, 'rb') as f_in:
         with open(output_path, 'wb') as f_out:
             shutil.copyfileobj(f_in, f_out)
-    print(f"Extracted to {output_path}")
+    print(f"解压到 {output_path}")
 
 def extract_zip_file(file_path, output_dir):
     """
     解压 .zip 文件
     """
-    print(f"Extracting {file_path} ...")
+    print(f"正在解压 {file_path} ...")
     with ZipFile(file_path, 'r') as zip_ref:
         zip_ref.extractall(output_dir)
-    print(f"Extracted to {output_dir}")
+    print(f"解压到 {output_dir}")
 
 def merge_binaries(arm_file, x86_file, output_file):
     """
     使用 lipo 合并 arm64 和 x86_64 的二进制文件
     """
-    print(f"Merging {arm_file} and {x86_file} into {output_file}")
+    print(f"正在合并 {arm_file} 和 {x86_file} 到 {output_file}")
     subprocess.run(['lipo', '-create', '-output', output_file, arm_file, x86_file], check=True)
-    print(f"Successfully merged into {output_file}")
+    print(f"成功合并到 {output_file}")
 
 def add_executable_permission(file_path):
     """
     添加可执行权限(适用于 macOS 和 Linux)
     """
-    print(f"Adding executable permission to {file_path}")
+    print(f"正在添加可执行权限到 {file_path}")
     st = os.stat(file_path)
     os.chmod(file_path, st.st_mode | stat.S_IEXEC)
-    print(f"Executable permission added to {file_path}")
+    print(f"已添加可执行权限到 {file_path}")
 
 def rename_file(original_path, new_name):
     """
     重命名文件
     """
-    new_path = os.path.join(os.path.dirname(original_path), new_name)
-    os.rename(original_path, new_path)
-    print(f"Renamed {original_path} to {new_path}")
+    new_path = os.path.join(ASSETS_DIR, new_name)
+    shutil.move(original_path, new_path)
+    print(f"已重命名 {original_path} 为 {new_path}")
     return new_path
 
-def construct_new_name(service_name, arch, ext):
-    """
-    构建新的文件名,符合 ClashName 的命名格式
-    """
-    platform_name, _ = get_platform_arch()
-    if "core-service" in service_name:
-        new_name = f"ccore-service-{platform_name}-{arch}{ext}"
-    else:
-        new_name = f"ccore-{platform_name}-{arch}{ext}"
-    return new_name
-
 def main():
     # 获取下载链接和文件名
     download_urls = get_download_urls()
 
-    # 存储架构对应的下载路径(仅适用于 macOS 合并)
-    downloads = {
-        'mihomo': {'arm64': '', 'x86_64': ''},
-        'core-service': {'arm64': '', 'x86_64': ''}
-    }
-
-    for file_name, url, dir_path, arch in download_urls:
-        # 下载文件
-        download_path = os.path.join(dir_path, file_name)
-        download_file(url, download_path)
-
-        # 确定服务名称
-        if 'core-service' in file_name:
-            service_name = 'core-service'
-        elif 'mihomo' in file_name:
-            service_name = 'mihomo'
-        else:
-            raise ValueError(f"Unknown service name in file: {file_name}")
-
-        # 构建输出路径
-        output_file = os.path.join(dir_path, service_name + '-' + arch)
-
-        # 解压文件
-        if file_name.endswith('.gz'):
-            extract_gz_file(download_path, output_file)
-        elif file_name.endswith('.zip'):
-            extract_zip_file(download_path, dir_path)
-
-        # 如果是 macOS,记录不同架构的文件路径
+    # 创建临时目录
+    with tempfile.TemporaryDirectory() as temp_dir:
+        # 存储架构对应的下载路径(仅适用于 macOS 合并)
+        downloads = {
+            'mihomo': {'arm64': '', 'x86_64': ''},
+            'core-service': {'arm64': '', 'x86_64': ''}
+        }
+
+        for file_name, url, arch in download_urls:
+            # 下载文件
+            download_path = os.path.join(temp_dir, file_name)
+            download_file(url, download_path)
+
+            # 确定服务名称
+            if 'core-service' in file_name:
+                service_name = 'core-service'
+            elif 'mihomo' in file_name:
+                service_name = 'mihomo'
+            else:
+                raise ValueError(f"未知的服务名称: {file_name}")
+
+            # 构建输出路径
+            output_file = os.path.join(temp_dir, service_name + '-' + arch)
+
+            # 解压文件
+            if file_name.endswith('.gz'):
+                extract_gz_file(download_path, output_file)
+            elif file_name.endswith('.zip'):
+                extract_zip_file(download_path, temp_dir)
+
+            # 如果是 macOS,记录不同架构的文件路径
+            if SYSTEM == 'darwin':
+                downloads[service_name][arch] = output_file
+
+        # 合并 macOS 下的二进制文件或直接重命名
         if SYSTEM == 'darwin':
-            downloads[service_name][arch] = output_file
+            for service in ['mihomo', 'core-service']:
+                arm_file = downloads[service]['arm64']
+                x86_file = downloads[service]['x86_64']
+                if arm_file and x86_file:
+                    merged_output = os.path.join(temp_dir, service)
+                    merge_binaries(arm_file, x86_file, merged_output)
+                    add_executable_permission(merged_output)
+                    
+                    # 如果是合并改名,core-{SYSTEM}
+                    # service-{SYSTEM}
+                    # 其他平台不变
+                    new_name = f"core-{SYSTEM}" if service == 'mihomo' else f"service-{SYSTEM}"
+                    rename_file(merged_output, new_name)
         else:
-            # 如果不是 macOS,直接重命名并添加执行权限
-            ext = '.gz' if file_name.endswith('.gz') else '.zip'
-            new_name = construct_new_name(service_name, arch, ext)
-            renamed_output = rename_file(output_file, new_name)
-            add_executable_permission(renamed_output)
-
-    # 合并 macOS 下的二进制文件
-    if SYSTEM == 'darwin':
-        for service in ['mihomo', 'core-service']:
-            arm_file = downloads[service]['arm64']
-            x86_file = downloads[service]['x86_64']
-            if arm_file and x86_file:
-                # 构建新的名称
-                ext = '.gz' if 'gz' in arm_file else '.zip'
-                merged_output = os.path.join(ASSETS_DIR, f'ccore-{service}-darwin-universal')
-                merge_binaries(arm_file, x86_file, merged_output)
-                add_executable_permission(merged_output)
+            # 对于 Linux 和 Windows,直接重命名最新下载的文件
+            for service in ['mihomo', 'core-service']:
+                latest_file = max(
+                    [f for f in os.listdir(temp_dir) if service in f],
+                    key=lambda x: os.path.getmtime(os.path.join(temp_dir, x))
+                )
+                new_name = f"core-{SYSTEM}-{platform.machine()}" if service == 'mihomo' else f"service-{SYSTEM}-{platform.machine()}"
+                if SYSTEM == 'windows':
+                    new_name += '.exe'
+                rename_file(os.path.join(temp_dir, latest_file), new_name)
+                if SYSTEM == 'linux':
+                    add_executable_permission(os.path.join(ASSETS_DIR, new_name))
 
 if __name__ == "__main__":
     main()

+ 5 - 1
lib/app/clash/mode/config.dart

@@ -7,7 +7,8 @@ class GuiConfig {
     required this.startAtLogin,
     required this.breakConnections,
     required this.language,
-    required this.servicePort
+    required this.servicePort,
+    required this.dnsPort
   });
   late String selected;
   late int updateInterval;
@@ -17,6 +18,7 @@ class GuiConfig {
   late bool breakConnections;
   late String language;
   late int servicePort;
+  late String dnsPort;
 
 
   GuiConfig.fromJson(Map<String, dynamic> json) {
@@ -28,6 +30,7 @@ class GuiConfig {
     breakConnections = json['breakConnections'];
     language = json['language'];
     servicePort = json['servicePort'];
+    dnsPort = json['dnsPort'];
   }
 
   Map<String, dynamic> toJson() {
@@ -40,6 +43,7 @@ class GuiConfig {
     data['breakConnections'] = breakConnections;
     data['language'] = language;
     data['servicePort'] = servicePort;
+    data['dnsPort'] = dnsPort;
     return data;
   }
 

+ 95 - 4
lib/app/clash/service/clash_service.dart

@@ -1,3 +1,4 @@
+import 'dart:convert';
 import 'dart:io';
 import 'dart:async';
 import 'package:get/get.dart';
@@ -19,10 +20,90 @@ class ClashService extends GetxController {
   final coreStatus = RunningState.stoped.obs;
   final serviceMode = false.obs;
   bool get clashServiceIsRuning => coreStatus.value == RunningState.running;
+
+
+  Future<void> updatePorts() async {
+
+    // 检查端口占用
+    int newPort = await findAvailablePort(controllers.config.mixedPort.value+1);
+    controllers.config.mixedPort.value = newPort;
+
+    controllers.global.updateMsg("混合端口已更新为: $newPort");
+    // 检查API端口占用
+    int newApiPort = await findAvailablePort(controllers.config.apiAddressPort.value);
+    controllers.config.apiAddressPort.value = newApiPort;
+    controllers.global.updateMsg("API端口已更新为: $newApiPort");
+
+    int newDnsPort = await findAvailablePort(int.parse(controllers.config.dnsPort.value.split(':').last));
+    controllers.config.dnsPort.value = '${controllers.config.dnsPort.value.split(':').first}:$newDnsPort';
+
+    await controllers.config.saveConfig();
+    //await controllers.config.readClashCoreApi();
+
+  }
+  Future<void> makeInitConfig() async {
+    //await updatePorts();
+    //await controllers.config.readClashCoreApi();
+    var mode = controllers.global.modesSelect.value;
+
+    var clashConfig = ClashConfig(
+        mixedPort: controllers.config.mixedPort.value,
+        allowLan: true,
+        bindAddress: '*',
+        mode: mode,
+        logLevel: 'debug',
+        externalController: '127.0.0.1:${controllers.config.apiAddressPort.value}',
+        unifiedDelay: false,
+        geodataMode: true,
+        tcpConcurrent: false,
+        findProcessMode: 'strict',
+        globalClientFingerprint: 'chrome',
+        dns: DNS(
+          enable: false,
+          listen: controllers.config.dnsPort.value,
+          ipv6: false,
+          enhancedMode: kRedirHostMode,
+          fakeIpFilter: null,
+          nameserver: kDomesticDNS,
+          proxyServerNameserver: kDomesticDNS,
+          nameserverPolicy: {
+            'geosite:cn,private': kDomesticDNS,
+            'geosite:geolocation-!cn': kForeignDNS,
+          },
+        ),
+        tun: Tun(
+          enable: false,
+          stack: 'system',
+          autoRoute: true,
+          autoRedirect: true,
+          autoDetectInterface: true,
+          dnsHijack: ['any:53'],
+        ),
+        proxies: [],
+
+        rules: [
+          'GEOIP,CN,DIRECT',
+          'MATCH,DIRECT'
+        ]
+    );
+    try {
+
+      final file = File(path.join(Paths.config.path, Files.makeInitProxyConfig.path ));
+      await file.writeAsString(clashConfig.toYaml());
+      print('配置文件已成功保存到: ${file.path}');
+    } catch (e) {
+      print('保存配置文件时发生错误: $e');
+      throw Exception('无法保存配置文件');
+    }
+
+
+  }
+
   Future<bool> startClashCore() async {
     final timeout = const Duration(seconds: 30);
     final checkInterval = const Duration(milliseconds: 200);
     var startTime = DateTime.now();
+
     await controllers.config.readClashCoreApi();
 
     try {
@@ -66,7 +147,7 @@ class ClashService extends GetxController {
       
       await controllers.core.updateConfig();
       coreStatus.value = RunningState.running;
-      controllers.global.updateMsg("内核状态:${coreStatus.value == RunningState.running} ");
+      controllers.global.updateMsg("重新加载内核成功,点击连接 ");
       return true;
     } catch (e) {
       controllers.global.updateMsg("启动内核错误");
@@ -86,6 +167,7 @@ class ClashService extends GetxController {
 
   Future<bool> initClashCoreConfig() async {
     controllers.config.config.value.selected = Files.makeInitProxyConfig.path;
+    await makeInitConfig();
     await startClashCore();
     if (coreStatus.value == RunningState.error) {
       controllers.global.updateMsg("启动内核失败...");
@@ -108,6 +190,10 @@ class ClashService extends GetxController {
 
   Future<void> reloadClashCore() async {
     try {
+      // if(coreStatus.value == RunningState.stoped){
+      //   await updatePorts();
+      // }
+
       controllers.config.config.value.selected = Files.makeProxyConfig.path;
       if (coreStatus.value == RunningState.running) {
         controllers.global.updateMsg("切换配置...");
@@ -117,6 +203,10 @@ class ClashService extends GetxController {
         controllers.global.updateMsg("fetchReloadConfig${controllers.config.clashCoreApiAddress.value}...");
       }
     } catch (e) {
+      // if(coreStatus.value == RunningState.stoped){
+      //   await updatePorts();
+      // }
+
       controllers.global.updateMsg("重新配置...");
       await controllers.config.readClashCoreApi();
       controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
@@ -125,13 +215,14 @@ class ClashService extends GetxController {
   }
 
   Future<String> generateYamlConfig(List<NodeMode> nodes) async {
+   // await updatePorts();
     final config = ClashConfig(
-      mixedPort: 9788,
+      mixedPort: controllers.config.mixedPort.value,
       allowLan: true,
       bindAddress: '*',
-      mode: controllers.global.modesSelect.value,
+      mode: controllers.global.modesSelect.value == "global" ? "global" : "rule",
       logLevel: 'info',
-      externalController: '127.0.0.1:9799',
+      externalController: '127.0.0.1:${controllers.config.apiAddressPort.value}',
       unifiedDelay: false,
       geodataMode: true,
       tcpConcurrent: false,

+ 1 - 1
lib/app/component/dialogs.dart

@@ -1,4 +1,4 @@
-import 'package:get/get.dart';
+
 import 'package:flutter/material.dart';
 import 'package:styled_widget/styled_widget.dart';
 

+ 8 - 2
lib/app/const/const.dart

@@ -124,10 +124,16 @@ class Files {
   }
 
   static File get assetsClashService {
-    return File(path.join(Paths.assetsBin.path, 'ccore-service-${ClashName.platform}-${ClashName.arch}${ClashName.ext}'));
+    if (Platform.isMacOS){
+      return File(path.join(Paths.assetsBin.path, 'service-${ClashName.platform}'));
+    }
+    return File(path.join(Paths.assetsBin.path, 'service-${ClashName.platform}-${ClashName.arch}'));
   }
   static File get assetsCCore {
-    return File(path.join(Paths.assetsBin.path, 'ccore-${ClashName.platform}-${ClashName.arch}${ClashName.ext}'));
+    if (Platform.isMacOS){
+      return File(path.join(Paths.assetsBin.path, 'core-${ClashName.platform}'));
+    }
+    return File(path.join(Paths.assetsBin.path, 'core-${ClashName.platform}-${ClashName.arch}'));
   }
 
   static File get assetsSysProxyWin {

+ 62 - 2
lib/app/controller/GlobalController.dart

@@ -7,14 +7,12 @@ import 'package:naiyouwl/app/common/SharedPreferencesUtil.dart';
 import 'package:naiyouwl/app/controller/controllers.dart';
 import 'package:naiyouwl/app/data/model/NodeMode.dart';
 import 'package:naiyouwl/app/network/api_service.dart';
-import 'package:naiyouwl/app/utils/system_proxy.dart';
 import 'package:proxy_manager/proxy_manager.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import 'package:tray_manager/tray_manager.dart';
 import 'package:window_manager/window_manager.dart';
 import 'package:path/path.dart' as path;
 import 'package:wl_base_help/wl_base_help.dart';
-import '../clash/service/clash_service.dart';
 import '../common/constants.dart';
 import '../const/const.dart';
 import '../data/model/UserMode.dart';
@@ -67,6 +65,68 @@ class GlobalController extends GetxController {
     await controllers.cc_service.isCanOperationService();
   }
 
+  Future<bool> checkAllCoresStopped() async {
+    bool allStopped = true;
+
+    // 检查 Clash 核心是否已停止
+    if (Platform.isWindows) {
+      allStopped =  controllers.service.coreStatus.value == RunningState.running;
+    } else if (Platform.isMacOS) {
+      if(await controllers.cc_service.isCanOperationService()){
+        allStopped = controllers.cc_service.coreStatus.value == RunningState.running;
+      } else {
+        allStopped =  controllers.service.coreStatus.value == RunningState.running;
+      }
+
+    }
+
+    // 检查其他可能的核心进程
+    // if (!controllers.service.serviceMode.value) {
+    //   if (Platform.isWindows) {
+    //     allStopped = allStopped && !(await isProcessRunning(path.basename(Files.assetsClashService.path)));
+    //   }
+    //   allStopped = allStopped && !(await isProcessRunning(path.basename(Files.assetsCCore.path)));
+    //   allStopped = allStopped && !(await isProcessRunning(path.basename(Files.assetsClashService.path)));
+    // }
+
+    return allStopped;
+  }
+
+  Future<bool> isProcessRunning(String processName) async {
+    try {
+      String command = Platform.isWindows ? 'tasklist' : 'ps aux';
+      ProcessResult result = await Process.run(command, []);
+      return result.stdout.toString().contains(processName);
+    } catch (e) {
+      print('检查进程运行状态时出错: $e');
+      return false;
+    }
+  }
+
+
+  Future<void> checkAndStopIfPortsOccupied() async {
+    List<int> portsToCheck = [
+      controllers.config.mixedPort.value,
+      controllers.config.apiAddressPort.value,
+      controllers.config.servicePort.value
+    ];
+
+    for (int port in portsToCheck) {
+      if (!(await isPortAvailable(port))) {
+        LogHelper().d('端口 $port 被占用,正在停止所有内核');
+        await stopAllCore();
+        updateMsg('端口被占用,已停止所有内核');
+        return;
+      }
+    }
+  }
+
+  @override
+  void onInit() {
+    super.onInit();
+    // checkAndStopIfPortsOccupied();
+  }
+
   void updateMsg(String msg) {
     msgStatus.value = msg;
   }

+ 15 - 54
lib/app/controller/config.dart

@@ -1,16 +1,11 @@
 import 'dart:io';
 import 'dart:convert';
-import 'dart:ui';
 import 'package:get/get.dart';
 import 'package:naiyouwl/app/clash/mode/config.dart';
-import 'package:naiyouwl/app/clash/mode/clash_config.dart';
-import 'package:naiyouwl/app/clash/service/clash_service.dart';
 import 'package:naiyouwl/app/const/const.dart';
-import 'package:naiyouwl/app/controller/controllers.dart';
 import 'package:path/path.dart' as path;
 import 'package:yaml/yaml.dart';
 import 'package:yaml_edit/yaml_edit.dart';
-import 'package:flutter_emoji/flutter_emoji.dart';
 
 import '../common/constants.dart';
 
@@ -23,12 +18,13 @@ final Map<String, dynamic> _defaultConfig = {
   'breakConnections': false,
   'language': 'zh_CN',
   'servicePort': 9899,
+  'dnsPort': ":1053",
 };
 
 final Map<String, dynamic> _defaultVersionConfig = {
   'serviceVersion': '1.0.0',
   'coreVersion': '1.18.8',
-  'appVersion': kVersion,
+  'appVersion': kDnsListenPort,
 };
 
 class ConfigController extends GetxController {
@@ -42,6 +38,7 @@ class ConfigController extends GetxController {
   var mixedPort = 9788.obs;
   var apiAddressPort = 9799.obs;
   var servicePort  = 9899.obs;
+  var dnsPort  = kDnsListenPort.obs;
 
   Future<void> initConfig() async {
 
@@ -99,8 +96,18 @@ class ConfigController extends GetxController {
     // 保存 VersionConfig 到 version_config.json
     await saveVersionConfig();
 
-    await makeInitConfig();
+   // await makeInitConfig();
   }
+  Future<void> saveConfig() async {
+    // 保存 GuiConfig 到 gui_config.json
+    await saveGuiConfig();
+
+    // 保存 VersionConfig 到 version_config.json
+    await saveVersionConfig();
+
+   // await makeInitConfig();
+  }
+
   
   // 比较两个版本号
   int compareVersions(String v1, String v2) {
@@ -187,53 +194,7 @@ class ConfigController extends GetxController {
     }
   }
 
-  Future<void> makeInitConfig() async {
-    var mode = controllers.global.modesSelect.value;
-    
-    var clashConfig = ClashConfig(
-      mixedPort: mixedPort.value,
-      allowLan: true,
-      bindAddress: '*',
-      mode: mode,
-      logLevel: 'debug',
-      externalController: '127.0.0.1:${apiAddressPort.value}',
-      unifiedDelay: false,
-      geodataMode: true,
-      tcpConcurrent: false,
-      findProcessMode: 'strict',
-      globalClientFingerprint: 'chrome',
-        dns: DNS(
-          enable: false,
-          listen: kDnsListenPort,
-          ipv6: false,
-          enhancedMode: kRedirHostMode,
-          fakeIpFilter: null,
-          nameserver: kDomesticDNS,
-          proxyServerNameserver: kDomesticDNS,
-          nameserverPolicy: {
-            'geosite:cn,private': kDomesticDNS,
-            'geosite:geolocation-!cn': kForeignDNS,
-          },
-        ),
-        tun: Tun(
-          enable: false,
-          stack: 'system',
-          autoRoute: true,
-          autoRedirect: true,
-          autoDetectInterface: true,
-          dnsHijack: ['any:53'],
-        ),
-      proxies: [],
-
-      rules: [
-        'GEOIP,CN,DIRECT',
-        'MATCH,DIRECT'
-      ]
-    );
-
-    final initConfigFile = File(path.join(Paths.config.path, Files.makeInitProxyConfig.path ));
-    await initConfigFile.writeAsString(clashConfig.toYaml());
-  }
+
 
   // ... 其他方法保持不变
 }

+ 0 - 1
lib/app/controller/controllers.dart

@@ -4,7 +4,6 @@ import 'package:naiyouwl/app/controller/GlobalController.dart';
 import 'package:naiyouwl/app/controller/config.dart';
 import 'package:naiyouwl/app/controller/core.dart';
 import 'package:naiyouwl/app/controller/dialog.dart';
-import 'package:naiyouwl/app/controller/protocol.dart';
 import 'package:naiyouwl/app/controller/service.dart';
 import 'package:naiyouwl/app/controller/tray.dart';
 import 'package:naiyouwl/app/controller/window.dart';

+ 1 - 5
lib/app/controller/core.dart

@@ -1,14 +1,9 @@
 import 'dart:convert';
-
-import 'package:dio/dio.dart';
 import 'package:get/get.dart';
 import 'package:naiyouwl/app/bean/clash_core.dart';
 import 'package:naiyouwl/app/bean/connect.dart';
-import 'package:naiyouwl/app/bean/proxie.dart';
 import 'package:naiyouwl/app/bean/rule.dart';
 import 'package:naiyouwl/app/common/LogHelper.dart';
-import 'package:naiyouwl/app/controller/controllers.dart';
-import 'package:naiyouwl/app/controller/service.dart';
 
 import 'package:naiyouwl/app/utils/system_proxy.dart';
 import 'package:web_socket_channel/io.dart';
@@ -199,6 +194,7 @@ class CoreController extends GetxController {
       } on Exception catch (e) {
         // 处理其他类型的异常
         print('Exception changeConfig : $e');
+
         continue;
       }
     }

+ 54 - 11
lib/app/controller/service.dart

@@ -4,6 +4,7 @@ import 'dart:convert';
 
 import 'package:get/get.dart';
 import 'package:naiyouwl/app/bean/ClashServiceInfo.dart';
+import 'package:naiyouwl/app/clash/service/clash_service.dart';
 import 'package:naiyouwl/app/const/const.dart';
 import 'package:naiyouwl/app/controller/controllers.dart';
 import 'package:naiyouwl/app/utils/logger.dart';
@@ -12,6 +13,8 @@ import 'package:naiyouwl/app/utils/shell.dart';
 import 'package:path/path.dart' as path;
 import 'package:http/http.dart' as http;
 
+import '../clash/mode/clash_config.dart';
+import '../common/constants.dart';
 import '../utils/utils.dart';
 
 final headers = {"User-Agent": "ccore-for-flutter/0.0.1"};
@@ -25,6 +28,7 @@ class ServiceController extends GetxController {
   var serviceStatus = RunningState.stoped.obs;
   var installStatus = false.obs; // 新增变量用于记录安装/卸载状态
   bool get serviceIsRuning => serviceStatus.value == RunningState.running;
+  bool get coreIsRuning => coreStatus.value == RunningState.running;
 
   Future<void> initConfig() async{
     url = "127.0.0.1:${controllers.config.config.value.servicePort}";
@@ -62,7 +66,7 @@ class ServiceController extends GetxController {
   }
 
   Future<void> fetchStartInit() async {
-
+    await controllers.service.makeInitConfig();
     controllers.config.config.value.selected = Files.makeInitProxyConfig.path;
     controllers.global.updateMsg("服务模式初始化...");
     await fetchStop();
@@ -165,6 +169,13 @@ class ServiceController extends GetxController {
 
       await waitServiceStart();
       serviceStatus.value = RunningState.running;
+
+      if (serviceStatus.value == RunningState.running) {
+        await controllers.service.stopClashCore();
+        await Future.delayed(Duration(seconds: 3));
+        await fetchStartInit();
+      }
+
       installStatus.value = true; // 安装成功
     } catch (e) {
       installStatus.value = false; // 安装失败
@@ -175,22 +186,31 @@ class ServiceController extends GetxController {
 
   Future<void> uninstall() async {
     try {
-      await stopService();
+      serviceStatus.value = RunningState.uninstall;
       final res = await runAsAdmin(Files.assetsClashService.path, ["stop", "uninstall"]);
       log.debug('uninstall', res.stdout, res.stderr);
       if (res.exitCode != 0) throw res.stderr;
       installStatus.value = false; // 卸载成功
-      serviceStatus.value = RunningState.stoped;
-
-      if(controllers.service.coreStatus == RunningState.stoped){
-        controllers.global.updateMsg("卸载服务完成.重新启动内核...");
-        await controllers.service.startClashCore();
+      await Future.delayed(Duration(seconds: 2));
+      // final conn = await controllers.core.fetchConnection();
+      // if(conn != null)
+      // {
+      //   for (final it in conn.connections) {
+      //     await controllers.core.fetchCloseConnections(it.id);
+      //   }
+      // }
+      await controllers.service.updatePorts();
+      await Future.delayed(Duration(seconds: 2));
+      await controllers.service.makeInitConfig();
+      await Future.delayed(Duration(seconds: 5));
+      await controllers.service.startClashCore();
 
-      }
+      serviceStatus.value = RunningState.stoped;
 
     } catch (e) {
       installStatus.value = true; // 卸载失败
       log.debug('卸载失败: $e');
+
       throw e;
     }
   }
@@ -202,12 +222,14 @@ class ServiceController extends GetxController {
   }
 
   Future<void> serviceModeSwitch(bool open) async {
-    if (serviceStatus.value == RunningState.running) await stopService();
-    await controllers.service.stopClashCore();
-    await fetchStop();
+
+   // if (serviceStatus.value == RunningState.running) await controllers.service.stopClashCore();
+    // if (serviceStatus.value == RunningState.running) await fetchStop();
+    serviceStatus.value = RunningState.starting;
     try {
       controllers.global.updateMsg(open ? "安装服务" : "卸载服务");
       open ? await install() : await uninstall();
+
     } catch (e) {
       print(e.toString());
     }
@@ -237,6 +259,10 @@ class ServiceController extends GetxController {
         controllers.global.updateMsg("fetchReloadConfig${controllers.config.clashCoreApiAddress.value}...");
       }
     } catch (e) {
+
+
+
+
       controllers.global.updateMsg("重新配置...");
       await controllers.config.readClashCoreApi();
       controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
@@ -262,4 +288,21 @@ class ServiceController extends GetxController {
       return false;
     }
   }
+
+  Future<bool> isClashCoreRunning() async {
+    try {
+      final res = await client.post(Uri.http(url, 'info'), headers: headers);
+      if (res.statusCode == 200) {
+        var jsonResponse = jsonDecode(res.body) as Map<String, dynamic>;
+        return jsonResponse['status'] == 'running';
+      }
+      return false;
+    } catch (e) {
+      print('Exception when checking Clash core: $e');
+      return false;
+    }
+  }
+
+
+
 }

+ 11 - 0
lib/app/modules/connect/bindings/connect_binding.dart

@@ -0,0 +1,11 @@
+import 'package:get/get.dart';
+
+import '../controllers/connect_controller.dart';
+class ConnectBinding extends Bindings {
+  @override
+  void dependencies() {
+    Get.lazyPut<ConnectController>(
+      () => ConnectController(),
+    );
+  }
+}

+ 41 - 0
lib/app/modules/connect/controllers/connect_controller.dart

@@ -0,0 +1,41 @@
+
+import 'package:get/get.dart';
+import 'package:naiyouwl/app/controller/core.dart';
+import 'package:web_socket_channel/io.dart';
+import 'dart:convert';
+import 'package:naiyouwl/app/bean/connect.dart';
+
+class ConnectController extends GetxController {
+  final CoreController coreController = Get.find<CoreController>();
+  late IOWebSocketChannel channel;
+  var connect = Rx<Connect?>(null);
+  
+  @override
+  void onInit() {
+    super.onInit();
+    connectWebSocket();
+  }
+
+  void connectWebSocket() {
+    channel = coreController.fetchConnectionsWs();
+    channel.stream.listen((message) {
+      try {
+        final data = jsonDecode(message);
+        if (data != null && data is Map<String, dynamic>) {
+          connect.value = Connect.fromJson(data);
+        } else {
+          connect.value = null;
+        }
+      } catch (e) {
+        print('解析WebSocket消息时出错: $e');
+        connect.value = null;
+      }
+    });
+  }
+
+  @override
+  void onClose() {
+    channel.sink.close();
+    super.onClose();
+  }
+}

+ 62 - 0
lib/app/modules/connect/views/connect_view.dart

@@ -0,0 +1,62 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import '../../../bean/connect.dart';
+import '../../../component/sys_app_bar.dart';
+import '../controllers/connect_controller.dart';
+
+class ConnectView extends GetView<ConnectController> {
+
+  const ConnectView({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      decoration: const BoxDecoration(
+        image: DecorationImage(
+          image: AssetImage("assets/images/node/nodebg.png"),
+          fit: BoxFit.fill,
+        ),
+      ),
+      child: Scaffold(
+        backgroundColor: Colors.transparent,
+        appBar: SysAppBar(title: const Text(""), actions: [
+          Row(
+            children: [
+                Text("连接状态")
+              ]
+          ),
+        ],),
+        body: Obx(() {
+          return ListView.builder(
+            itemCount: controller.connect.value?.connections.length ?? 0,
+            itemBuilder: (context, index) {
+              ConnectConnection? metadata = controller.connect.value?.connections[index];
+              return Column(
+                children: [
+                  Container(
+                    color: Colors.white70,
+                    child: ListTile(
+                      subtitle: Column(
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: [
+                          Text('${metadata?.metadata.network ?? ""}://${metadata!.metadata.host}${metadata.metadata.destinationIP ?? ""}:${metadata.metadata.destinationPort ?? ""}'),
+                          SizedBox(height: 10,),
+                          Text('${metadata.start}'),
+                        ],
+                      ),
+                    ),
+                  ),
+                  Divider(
+                    color: Colors.grey,
+                    thickness: 1,
+                  ),
+                ],
+              );
+            },
+          );
+        })
+      ),
+    );
+  }
+}

+ 34 - 19
lib/app/modules/home/controllers/home_controller.dart

@@ -112,6 +112,16 @@ class HomeController extends GetxController {
       return;
     }
 
+    if(controllers.cc_service.serviceStatus == RunningState.starting){
+      controllers.global.handleApiError("服务安装中,请等待");
+      return;
+    }
+
+    if(controllers.cc_service.serviceStatus.value == RunningState.uninstall){
+      controllers.global.handleApiError("服务卸载中,请等待");
+      return;
+    }
+
     if (connectStatus.value == ConnectionStatus.connecting) {
 
       return;
@@ -134,26 +144,31 @@ class HomeController extends GetxController {
 
 
       updateStatus(ConnectionStatus.connecting);
-      // 检查服务是否运行
-      if (!controllers.cc_service.serviceIsRuning) {
-        // 尝试获取 hello 的返回结果并处理
-        if(controllers.service.coreStatus.value != RunningState.running)
-        {
-          var hellow = await controllers.core.fetchHello();
-
-          // 如果获取不到 hellow,则重试启动内核
-          if (hellow == null) {
-            controllers.global.updateMsg("尝试重新启动内核");
-            bool success = await restartClashCoreWithRetry(maxRetries: 3);  // 重试 3 次
-            if (!success) {
-              controllers.global.updateMsg("内核重启失败,已达到最大重试次数");
-              return;  // 如果重启失败,直接返回,阻止继续进行
-            }
-          }
-        }
-      }
 
-      await Future.delayed(Duration(seconds: 3)); // 等待核心状态更新
+      // if(Platform.isMacOS){
+      //   // 检查服务是否运行
+      //   if (!controllers.cc_service.serviceIsRuning) {
+      //     // 尝试获取 hello 的返回结果并处理
+      //     if(controllers.service.coreStatus.value != RunningState.running)
+      //     {
+      //       var hellow = await controllers.core.fetchHello();
+      //
+      //       // 如果获取不到 hellow,则重试启动内核
+      //       if (hellow == null) {
+      //         controllers.global.updateMsg("尝试重新启动内核");
+      //         bool success = await restartClashCoreWithRetry(maxRetries: 3);  // 重试 3 次
+      //         if (!success) {
+      //           controllers.global.updateMsg("内核重启失败,已达到最大重试次数");
+      //           updateStatus(ConnectionStatus.stopped);
+      //           return;  // 如果重启失败,直接返回,阻止继续进行
+      //         }
+      //       }
+      //     }
+      //   }
+      // }
+
+
+     // await Future.delayed(Duration(seconds: 3)); // 等待核心状态更新
       // 继续后面的逻辑
       var error = await networkService.fetchAuthUser();
 

+ 9 - 0
lib/app/modules/home/views/home_view.dart

@@ -9,8 +9,10 @@ import 'package:naiyouwl/app/component/sys_app_bar.dart';
 import 'package:naiyouwl/app/controller/controllers.dart';
 import 'package:naiyouwl/app/data/model/UserMode.dart';
 import 'package:url_launcher/url_launcher.dart';
+import '../../../routes/app_pages.dart';
 import '../../../utils/shell.dart';
 import '../controllers/home_controller.dart';
+import '../../connect/views/connect_view.dart';
 
 class HomeView extends GetView<HomeController> {
 
@@ -43,6 +45,13 @@ class HomeView extends GetView<HomeController> {
                 },
                 tooltip: '切换账号', 
                 icon: const Icon(Icons.exit_to_app)
+              ),
+              IconButton(
+                onPressed: () {
+                  Get.toNamed(Routes.CONNECT);
+                },
+                tooltip: '连接详情',
+                icon: const Icon(Icons.info_outline)
               )
             ],
           ),

+ 8 - 0
lib/app/routes/app_pages.dart

@@ -1,4 +1,6 @@
 import 'package:get/get.dart';
+import 'package:naiyouwl/app/modules/connect/bindings/connect_binding.dart';
+import 'package:naiyouwl/app/modules/connect/views/connect_view.dart';
 
 import '../modules/home/bindings/home_binding.dart';
 import '../modules/home/views/home_view.dart';
@@ -37,5 +39,11 @@ class AppPages {
       page: () => const NodeView(),
       binding: NodeBinding(),
     ),
+
+    GetPage(
+      name: _Paths.CONNECT,
+      page: () => const ConnectView(),
+      binding: ConnectBinding(),
+    ),
   ];
 }

+ 2 - 0
lib/app/routes/app_routes.dart

@@ -7,6 +7,7 @@ abstract class Routes {
   static const WELCOME = _Paths.WELCOME;
   static const LOGIN = _Paths.LOGIN;
   static const NODE = _Paths.NODE;
+  static const CONNECT = _Paths.CONNECT;
 }
 
 abstract class _Paths {
@@ -15,4 +16,5 @@ abstract class _Paths {
   static const WELCOME = '/welcome';
   static const LOGIN = '/login';
   static const NODE = '/node';
+  static const CONNECT = '/connect';
 }

+ 29 - 0
lib/app/service/connection_service.dart

@@ -18,8 +18,35 @@ class ConnectionService {
 
   ConnectionService(this.globalController, this.updateStatus);
 
+
   Future<void> startConnection() async {
     try {
+
+      // // 检查所有内核是否已停止
+      // bool allCoresStopped = await globalController.checkAllCoresStopped();
+      //
+      // if (!allCoresStopped) {
+      //   // 检查并停止占用端口
+      //   await globalController.checkAndStopIfPortsOccupied();
+      //
+      //   // 如果端口被占用,尝试更换端口
+      //   if (globalController.msgStatus.value == '端口被占用,已停止所有内核') {
+      //     // 尝试为每个端口找到新的可用端口
+      //     int newMixedPort = await findAvailablePort(controllers.config.mixedPort.value);
+      //     int newApiPort = await findAvailablePort(controllers.config.apiAddressPort.value);
+      //     int newServicePort = await findAvailablePort(controllers.config.servicePort.value);
+      //
+      //     // 更新配置
+      //     controllers.config.mixedPort.value = newMixedPort;
+      //     controllers.config.apiAddressPort.value = newApiPort;
+      //     controllers.config.servicePort.value = newServicePort;
+      //
+      //     // 保存新的配置
+      //     await controllers.config.saveConfig();
+      //
+      //     globalController.updateMsg("端口已更新,正在重新启动...");
+      //   }
+      // }
       updateStatus(ConnectionStatus.connecting);
       globalController.updateMsg("正在启动连接...");
 
@@ -148,6 +175,8 @@ class ConnectionService {
 
   Future<void> coreInit() async {
     try {
+
+
       globalController.updateMsg("正在初始化核心...");
       if (serviceController.installStatus.value) {
         //每次启动多要初始化

+ 23 - 2
lib/app/utils/utils.dart

@@ -1,5 +1,5 @@
 import 'dart:math' as math;
-
+import 'dart:io';
 import 'package:flutter/services.dart';
 
 String bytesToSize(int bytes) {
@@ -63,4 +63,25 @@ enum RunningState {
   stopping,
   stoped,
   error,
-}
+  uninstall,
+}
+
+
+
+Future<bool> isPortAvailable(int port) async {
+  try {
+    final socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, port, shared: true);
+    await socket.close();
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+Future<int> findAvailablePort(int startPort, {int maxAttempts = 100}) async {
+  for (int i = 1; i < maxAttempts; i++) {
+    int port = startPort + i;
+    return port;
+  }
+  throw Exception('无法找到可用端口');
+}

+ 1 - 1
macos/Runner/Info.plist

@@ -13,7 +13,7 @@
 	<key>CFBundleInfoDictionaryVersion</key>
 	<string>6.0</string>
 	<key>CFBundleName</key>
-	<string>naiyou</string>
+	<string>macjsq</string>
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>