alroyso 7 months ago
parent
commit
bf49bca0a3

BIN
AppIcon.icns


+ 6 - 1
README.md

@@ -29,4 +29,9 @@ appdmg ./config.json ./MAC加速器-arm64.dmg
 
 appdmg ./config.json ./MAC加速器通用.dmg
 
-flutter run macos
+flutter run macos
+
+
+lipo -info build/macos/Build/Products/Release/naiyout.app/Contents/MacOS/naiyout
+
+appdmg config.json MACOS加速器.dmg

+ 8 - 0
config.json

@@ -0,0 +1,8 @@
+{
+  "title": "MAC加速器",
+  "icon": "AppIcon.icns",
+  "contents": [
+    { "x": 448, "y": 344, "type": "link", "path": "/Applications" },
+    { "x": 192, "y": 344, "type": "file", "path": "build/macos/Build/Products/Release/naiyout.app" }
+  ]
+}

+ 132 - 50
download.py

@@ -5,37 +5,68 @@ import gzip
 import shutil
 from zipfile import ZipFile
 import stat
+import argparse
+import subprocess
 
 # 定义常量
-BASE_URL = 'https://github.com/MetaCubeX/mihomo/releases/download'
-VERSION = 'v1.18.8'
+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'
+MIHOMO_VERSION = 'v1.18.8'  # 根据实际版本修改
+CORE_SERVICE_VERSION = 'test_2023.11.22_09-33'
 ASSETS_DIR = './assets/bin'
 SYSTEM = platform.system().lower()
-ARCH = platform.machine()
+
+# ClashName.platform 和 ClashName.arch 是动态生成的
+def get_platform_arch():
+    return SYSTEM, platform.machine()
 
 # 如果目录不存在,创建下载目录
 if not os.path.exists(ASSETS_DIR):
     os.makedirs(ASSETS_DIR)
 
-def get_download_url():
+def get_download_urls():
     """
-    根据系统和架构生成下载链接
+    根据系统生成 mihomo 和 core-service 的下载链接,分别下载 arm64 和 x86_64 架构
     """
-    if SYSTEM == 'darwin' and ARCH == 'x86_64':
-        file_name = f'mihomo-darwin-amd64-{VERSION}.gz'
-    elif SYSTEM == 'darwin' and ARCH == 'arm64':
-        file_name = f'mihomo-darwin-arm64-{VERSION}.gz'
-    elif SYSTEM == 'linux' and ARCH == 'x86_64':
-        file_name = f'mihomo-linux-amd64-{VERSION}.gz'
-    elif SYSTEM == 'linux' and ARCH == 'arm64':
-        file_name = f'mihomo-linux-arm64-{VERSION}.gz'
-    elif SYSTEM == 'windows' and ARCH == 'x86_64':
-        file_name = f'mihomo-windows-amd64-{VERSION}.zip'
-    else:
-        raise ValueError(f"Unsupported system/architecture: {SYSTEM} {ARCH}")
-
-    return f"{BASE_URL}/{VERSION}/{file_name}", file_name
-
+    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 架构
+    if SYSTEM == 'darwin':
+        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'
+        ))
+        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'
+        ))
+        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'
+        ))
+        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'
+        ))
+
+    return download_urls
 
 def download_file(url, output_path):
     """
@@ -50,7 +81,6 @@ def download_file(url, output_path):
     else:
         print(f"Failed to download {url}, status code: {response.status_code}")
 
-
 def extract_gz_file(file_path, output_path):
     """
     解压 .gz 文件
@@ -61,7 +91,6 @@ def extract_gz_file(file_path, output_path):
             shutil.copyfileobj(f_in, f_out)
     print(f"Extracted to {output_path}")
 
-
 def extract_zip_file(file_path, output_dir):
     """
     解压 .zip 文件
@@ -71,43 +100,96 @@ def extract_zip_file(file_path, output_dir):
         zip_ref.extractall(output_dir)
     print(f"Extracted to {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}")
+    subprocess.run(['lipo', '-create', '-output', output_file, arm_file, x86_file], check=True)
+    print(f"Successfully merged into {output_file}")
 
 def add_executable_permission(file_path):
     """
     添加可执行权限(适用于 macOS 和 Linux)
     """
-    if SYSTEM != 'windows':
-        print(f"Adding executable permission to {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"Adding executable permission to {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}")
 
+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}")
+    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():
     # 获取下载链接和文件名
-    url, file_name = get_download_url()
-
-    # 下载文件
-    download_path = os.path.join(ASSETS_DIR, file_name)
-    download_file(url, download_path)
-
-    # 构建输出路径和重命名文件
-    service_name = f'ccore-{SYSTEM}-{ARCH}'
-    output_file = os.path.join(ASSETS_DIR, service_name)
-
-    # 解压文件并重命名
-    if file_name.endswith('.gz'):
-        extract_gz_file(download_path, output_file)
-    elif file_name.endswith('.zip'):
-        extract_zip_file(download_path, ASSETS_DIR)
-
-    # 添加可执行权限
-    add_executable_permission(output_file)
-
-    # 删除下载的压缩文件
-    if os.path.exists(download_path):
-        os.remove(download_path)
-        print(f"Deleted temp file: {download_path}")
+    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,记录不同架构的文件路径
+        if SYSTEM == 'darwin':
+            downloads[service_name][arch] = output_file
+        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)
 
 if __name__ == "__main__":
     main()

+ 0 - 2
lib/app/clash/service/clash_service.dart

@@ -187,8 +187,6 @@ class ClashService extends GetxController {
         ),
       ],
       rules: [
-        'GEOSITE,geolocation-!cn,proxy',
-        'GEOSITE,cn,DIRECT',
         'GEOIP,CN,DIRECT',
         'MATCH,proxy',
       ],

+ 1 - 1
lib/app/common/constants.dart

@@ -8,7 +8,7 @@ const KLogout = "/api/client/v2/logout";
 const KAuthUser = "/api/client/v2/authUser";
 const kNode = '';
 
-const kVersion = '2.1.1';
+const kVersion = '2.1.2';
 
 
 const kFakeIpMode = 'fake-ip';

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

@@ -154,8 +154,12 @@ class GlobalController extends GetxController {
   }
 
   Future<void> systemProxySwitch(bool open) async {
-    systemProxySwitchIng.value = true;
-    systemProxySwitchIng.value = false;
+    systemProxySwitchIng.value = !open;
+    if (!open){
+      await closeProxy();
+    } else {
+      await openProxy();
+    }
   }
 
   void watchExit() {

+ 13 - 0
lib/app/controller/service.dart

@@ -70,6 +70,7 @@ class ServiceController extends GetxController {
     try {
       var ut = Uri.http(url, 'start');
       final body = json.encode({
+        "coreName": Files.assetsCCore.path,
         "args": [
           '-d',
           Paths.config.path,
@@ -174,11 +175,19 @@ class ServiceController extends GetxController {
 
   Future<void> uninstall() async {
     try {
+      await stopService();
       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();
+
+      }
+
     } catch (e) {
       installStatus.value = true; // 卸载失败
       log.debug('卸载失败: $e');
@@ -245,6 +254,10 @@ class ServiceController extends GetxController {
 
       return installStatus.value = res.statusCode == 200;
     } catch (e) {
+
+      serviceStatus.value = RunningState.stoped;
+      installStatus.value = false;
+      return false;
       print('Exception when checking service: $e');
       return false;
     }

+ 6 - 4
lib/app/controller/tray.dart

@@ -31,13 +31,15 @@ class TrayController extends GetxController with TrayListener {
       isSHow = true;
     }
 
-    final disabled = controllers.service.coreStatus.value != RunningState.running;
+    final disabled = controllers.global.connectStatus.value == false;
 
-    var disabledSerivce = false;
+    var disabledSerivce = controllers.global.connectStatus.value;
     if(Platform.isWindows){
       disabledSerivce = true;
     }
 
+    var serivceName = controllers.cc_service.installStatus.value == true? 'setting_service_uninstall'.tr : 'setting_service_install'.tr,
+
 
     trayMenu = Menu(items: [
       MenuItem.checkbox(label: 'tray_show'.tr, checked: visible, onClick: handleClickShow),
@@ -84,11 +86,11 @@ class TrayController extends GetxController with TrayListener {
       MenuItem.checkbox(
         label: 'route_tun_title'.tr,
         checked: disabledTun.value,
-        disabled: false,
+        disabled: disabledSerivce,
         onClick: handleClickSetAsTunProxy,
       ),
       MenuItem.checkbox(
-        label: 'setting_service_open'.tr,
+        label: serivceName,
         checked: controllers.cc_service.installStatus.value,
         disabled: disabledSerivce,
         onClick: handleClickServiceModeSwitch,

+ 4 - 1
lib/app/i18n/i18n.dart

@@ -52,7 +52,8 @@ class I18n extends Translations {
           "setting_http_proxy_port": "HTTP proxy port",
           "setting_mixed_proxy_port": "Mixed proxy port",
           "setting_external_controller": "External controller",
-          "setting_service_open": "Open Service",
+          "setting_service_install": "install Service",
+          "setting_service_uninstall": "uninstall Service",
           "setting_mode_global": "Global",
           "setting_mode_rules": "Rules",
           "setting_mode_direct": "Direct",
@@ -171,6 +172,8 @@ class I18n extends Translations {
           "setting_mixed_proxy_port": "混合代理端口",
           "setting_external_controller": "外部控制设置",
           "setting_service_open": "开启服务",
+          "setting_service_install": "安装服务",
+          "setting_service_uninstall": "卸载服务",
           "setting_mode_global": "全局",
           "setting_mode_rules": "局部",
           "setting_mode_direct": "直连",

+ 28 - 13
lib/app/modules/home/controllers/home_controller.dart

@@ -93,6 +93,7 @@ class HomeController extends GetxController {
 
   void updateStatus(ConnectionStatus newStatus) {
     connectStatus.value = newStatus;
+
   }
 
   void _handleStateChange([dynamic _]) async {
@@ -101,9 +102,7 @@ class HomeController extends GetxController {
     if (coreStatus == RunningState.running && isVisible &&
         controllers.global.allowStatusUpdate) {
       // 核心运行中且窗口可见时的逻辑
-      if (connectStatus.value == ConnectionStatus.connecting) {
-        controllers.global.updateMsg("连接完成...");
-      }
+
     }
   }
 
@@ -114,26 +113,42 @@ class HomeController extends GetxController {
     }
 
     if (connectStatus.value == ConnectionStatus.connecting) {
+
       return;
     }
 
     if (connectStatus.value == ConnectionStatus.connected) {
       await connectionService.stopConnection();
     } else {
-      updateStatus(ConnectionStatus.connecting);
 
+
+      if(controllers.global.routeModesSelect.value  == "tun"){
+        //网卡模式
+        if(!controllers.cc_service.serviceIsRuning){
+          controllers.global.errorMsg.value = "通过右上角安装服务来获取管理员权限";
+          return;
+        }
+
+
+      }
+
+
+      updateStatus(ConnectionStatus.connecting);
       // 检查服务是否运行
       if (!controllers.cc_service.serviceIsRuning) {
         // 尝试获取 hello 的返回结果并处理
-        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;  // 如果重启失败,直接返回,阻止继续进行
+        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;  // 如果重启失败,直接返回,阻止继续进行
+            }
           }
         }
       }

+ 1 - 1
lib/app/network/dio_client.dart

@@ -99,7 +99,7 @@ class TokenInterceptor extends Interceptor {
 
 class CustomInterceptors extends Interceptor {
   int _retryCount = 0;
-  final List<String> _backupUrls = ['http://107.148.49.147:8383','https://api.androidrj02.top','https://api.androidrj88.com','https://user.b161.cn','https://api.androidrj03.top'];
+  final List<String> _backupUrls = ['http://107.148.49.147:8383','https://api.androidrj02.top','https://api.androidrj88.com','https://api.androidrj03.top'];
   final Dio _dio;  // 添加 Dio 作为参数
   CustomInterceptors(this._dio);
   Future<bool> isConnected() async {

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

@@ -65,6 +65,7 @@ class ConnectionService {
           updateStatus(ConnectionStatus.connected);
           globalController.connectStatus.value = true;
           globalController.updateMsg("连接成功");
+
         } else {
           throw Exception("内核启动失败");
         }
@@ -72,6 +73,7 @@ class ConnectionService {
 
       updateStatus(ConnectionStatus.connected);
       globalController.updateMsg("连接成功");
+      controllers.global.connectStatus.value = true;
 
     } catch (e) {
       updateStatus(ConnectionStatus.disconnected);
@@ -114,6 +116,7 @@ class ConnectionService {
       globalController.connectStatus.value = false;
       updateStatus(ConnectionStatus.disconnected);
       globalController.updateMsg("已断开连接");
+      controllers.global.connectStatus.value = false;
     } catch (e) {
       globalController.handleApiError("断开连接失败: $e");
     }

+ 3 - 3
macos/Runner.xcodeproj/project.pbxproj

@@ -571,7 +571,7 @@
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				INFOPLIST_FILE = Runner/Info.plist;
-				INFOPLIST_KEY_CFBundleDisplayName = "MACOS加速器";
+				INFOPLIST_KEY_CFBundleDisplayName = naiyout;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/../Frameworks",
@@ -706,7 +706,7 @@
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				INFOPLIST_FILE = Runner/Info.plist;
-				INFOPLIST_KEY_CFBundleDisplayName = "MACOS加速器";
+				INFOPLIST_KEY_CFBundleDisplayName = naiyout;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/../Frameworks",
@@ -735,7 +735,7 @@
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
 				INFOPLIST_FILE = Runner/Info.plist;
-				INFOPLIST_KEY_CFBundleDisplayName = "MACOS加速器";
+				INFOPLIST_KEY_CFBundleDisplayName = naiyout;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/../Frameworks",

+ 5 - 5
macos/Runner/Base.lproj/MainMenu.xib

@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23094" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
     <dependencies>
         <deployment identifier="macosx"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23094"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -13,7 +13,7 @@
         </customObject>
         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
         <customObject id="-3" userLabel="Application" customClass="NSObject"/>
-        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="naiyout" customModuleProvider="target">
             <connections>
                 <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
                 <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
@@ -330,10 +330,10 @@
             </items>
             <point key="canvasLocation" x="142" y="-258"/>
         </menu>
-        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
+        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="naiyout" customModuleProvider="target">
             <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
             <rect key="contentRect" x="335" y="390" width="800" height="600"/>
-            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
             <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
                 <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
                 <autoresizingMask key="autoresizingMask"/>

+ 1 - 1
scripts/init.dart

@@ -18,7 +18,7 @@ Future downloadLatestClashCore() async {
   final String clashCoreName = 'clash.meta-${ClashName.platform}-${ClashName.arch}-cgo-$version';
 
 // Fetching release info from the GitHub API
-  final info = await dio.get('https://api.github.com/repos/MetaCubeX/mihomo/releases/tags/$version');
+  final info = await dio.get('https://api.github.com/repos/MetaCubeX/mihomo/releases/latest');
 
 // Extracting the download URL from the API response based on the asset name
   final Map<String, dynamic> latest = (info.data['assets'] as List<dynamic>)