浏览代码

feat: Support Custom Tray Icon

MystiPanda 1 年之前
父节点
当前提交
a30d07b924

+ 2 - 0
UPDATELOG.md

@@ -12,6 +12,8 @@
 
 ## v1.5.2
 
+---
+
 ### Features
 
 - 支持自定义延迟测试超时时间

+ 1 - 1
src-tauri/Cargo.toml

@@ -39,7 +39,7 @@ serde = { version = "1.0", features = ["derive"] }
 reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
 sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
 auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
-tauri = { version = "1.5", features = [ "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
+tauri = { version = "1.5", features = [ "protocol-asset", "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
 
 [target.'cfg(windows)'.dependencies]
 runas = "=1.0.0" # 高版本会返回错误 Status

+ 10 - 0
src-tauri/src/config/verge.rs

@@ -36,6 +36,13 @@ pub struct IVerge {
     /// show memory info (only for Clash Meta)
     pub enable_memory_usage: Option<bool>,
 
+    /// common tray icon
+    pub common_tray_icon: Option<String>,
+
+    pub sysproxy_tray_icon: Option<String>,
+
+    pub tun_tray_icon: Option<String>,
+
     /// clash tun mode
     pub enable_tun_mode: Option<bool>,
 
@@ -204,6 +211,9 @@ impl IVerge {
         patch!(startup_script);
         patch!(traffic_graph);
         patch!(enable_memory_usage);
+        patch!(common_tray_icon);
+        patch!(sysproxy_tray_icon);
+        patch!(tun_tray_icon);
 
         patch!(enable_tun_mode);
         patch!(enable_service_mode);

+ 27 - 6
src-tauri/src/core/tray.rs

@@ -129,26 +129,47 @@ impl Tray {
         let verge = verge.latest();
         let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
         let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
+        let common_tray_icon = verge.common_tray_icon.clone().unwrap_or("".to_string());
+        let sysproxy_tray_icon = verge.sysproxy_tray_icon.clone().unwrap_or("".to_string());
+        let tun_tray_icon = verge.tun_tray_icon.clone().unwrap_or("".to_string());
 
         let mut indication_icon = if *system_proxy {
             #[cfg(not(target_os = "macos"))]
-            let icon = include_bytes!("../../icons/tray-icon-sys.png").to_vec();
+            let mut icon = include_bytes!("../../icons/tray-icon-sys.png").to_vec();
             #[cfg(target_os = "macos")]
-            let icon = include_bytes!("../../icons/mac-tray-icon-sys.png").to_vec();
+            let mut icon = include_bytes!("../../icons/mac-tray-icon-sys.png").to_vec();
+            if !sysproxy_tray_icon.is_empty() {
+                let path = std::path::Path::new(&sysproxy_tray_icon);
+                if path.exists() {
+                    icon = std::fs::read(path).unwrap();
+                }
+            }
             icon
         } else {
             #[cfg(not(target_os = "macos"))]
-            let icon = include_bytes!("../../icons/tray-icon.png").to_vec();
+            let mut icon = include_bytes!("../../icons/tray-icon.png").to_vec();
             #[cfg(target_os = "macos")]
-            let icon = include_bytes!("../../icons/mac-tray-icon.png").to_vec();
+            let mut icon = include_bytes!("../../icons/mac-tray-icon.png").to_vec();
+            if !common_tray_icon.is_empty() {
+                let path = std::path::Path::new(&common_tray_icon);
+                if path.exists() {
+                    icon = std::fs::read(path).unwrap();
+                }
+            }
             icon
         };
 
         if *tun_mode {
             #[cfg(not(target_os = "macos"))]
-            let icon = include_bytes!("../../icons/tray-icon-tun.png").to_vec();
+            let mut icon = include_bytes!("../../icons/tray-icon-tun.png").to_vec();
             #[cfg(target_os = "macos")]
-            let icon = include_bytes!("../../icons/mac-tray-icon-tun.png").to_vec();
+            let mut icon = include_bytes!("../../icons/mac-tray-icon-tun.png").to_vec();
+            if !tun_tray_icon.is_empty() {
+                let path = std::path::Path::new(&tun_tray_icon);
+                if path.exists() {
+                    icon = std::fs::read(path).unwrap();
+                }
+            }
             indication_icon = icon
         }
 

+ 9 - 1
src-tauri/src/feat.rs

@@ -230,6 +230,9 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
     let proxy_bypass = patch.system_proxy_bypass;
     let language = patch.language;
     let port = patch.verge_mixed_port;
+    let common_tray_icon = patch.common_tray_icon;
+    let sysproxy_tray_icon = patch.sysproxy_tray_icon;
+    let tun_tray_icon = patch.tun_tray_icon;
 
     match {
         #[cfg(target_os = "windows")]
@@ -269,7 +272,12 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
 
         if language.is_some() {
             handle::Handle::update_systray()?;
-        } else if system_proxy.or(tun_mode).is_some() {
+        } else if system_proxy.is_some()
+            || tun_mode.is_some()
+            || common_tray_icon.is_some()
+            || sysproxy_tray_icon.is_some()
+            || tun_tray_icon.is_some()
+        {
             handle::Handle::update_systray_part()?;
         }
 

+ 5 - 1
src-tauri/tauri.conf.json

@@ -58,11 +58,15 @@
       "dialog": {
         "all": false,
         "open": true
+      },
+      "protocol": {
+        "asset": true,
+        "assetScope": ["**"]
       }
     },
     "windows": [],
     "security": {
-      "csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src http: https: data: 'self';"
+      "csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src asset: http: https: data: 'self';"
     }
   }
 }

+ 138 - 1
src/components/setting/mods/layout-viewer.tsx

@@ -1,10 +1,12 @@
 import { forwardRef, useImperativeHandle, useState } from "react";
 import { useTranslation } from "react-i18next";
-import { List, Switch } from "@mui/material";
+import { List, Switch, Button } from "@mui/material";
 import { useVerge } from "@/hooks/use-verge";
 import { BaseDialog, DialogRef, Notice } from "@/components/base";
 import { SettingItem } from "./setting-comp";
 import { GuardState } from "./guard-state";
+import { open as openDialog } from "@tauri-apps/api/dialog";
+import { convertFileSrc } from "@tauri-apps/api/tauri";
 
 export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
   const { t } = useTranslation();
@@ -61,6 +63,141 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
             <Switch edge="end" />
           </GuardState>
         </SettingItem>
+
+        <SettingItem label={t("Common Tray Icon")}>
+          <GuardState
+            value={verge?.common_tray_icon}
+            onCatch={onError}
+            onChange={(e) => onChangeData({ common_tray_icon: e })}
+            onGuard={(e) => patchVerge({ common_tray_icon: e })}
+          >
+            <Button
+              variant="outlined"
+              size="small"
+              startIcon={
+                verge?.common_tray_icon && (
+                  <img
+                    height="20px"
+                    src={convertFileSrc(verge?.common_tray_icon)}
+                  />
+                )
+              }
+              onClick={async () => {
+                if (verge?.common_tray_icon) {
+                  onChangeData({ common_tray_icon: "" });
+                  patchVerge({ common_tray_icon: "" });
+                } else {
+                  const path = await openDialog({
+                    directory: false,
+                    multiple: false,
+                    filters: [
+                      {
+                        name: "Tray Icon Image",
+                        extensions: ["png"],
+                      },
+                    ],
+                  });
+                  if (path?.length) {
+                    onChangeData({ common_tray_icon: `${path}` });
+                    patchVerge({ common_tray_icon: `${path}` });
+                  }
+                }
+              }}
+            >
+              {verge?.common_tray_icon ? t("Clear") : t("Browse")}
+            </Button>
+          </GuardState>
+        </SettingItem>
+
+        <SettingItem label={t("System Proxy Tray Icon")}>
+          <GuardState
+            value={verge?.sysproxy_tray_icon}
+            onCatch={onError}
+            onChange={(e) => onChangeData({ sysproxy_tray_icon: e })}
+            onGuard={(e) => patchVerge({ sysproxy_tray_icon: e })}
+          >
+            <Button
+              variant="outlined"
+              size="small"
+              startIcon={
+                verge?.sysproxy_tray_icon && (
+                  <img
+                    height="20px"
+                    src={convertFileSrc(verge?.sysproxy_tray_icon)}
+                  />
+                )
+              }
+              onClick={async () => {
+                if (verge?.sysproxy_tray_icon) {
+                  onChangeData({ sysproxy_tray_icon: "" });
+                  patchVerge({ sysproxy_tray_icon: "" });
+                } else {
+                  const path = await openDialog({
+                    directory: false,
+                    multiple: false,
+                    filters: [
+                      {
+                        name: "Tray Icon Image",
+                        extensions: ["png"],
+                      },
+                    ],
+                  });
+                  if (path?.length) {
+                    onChangeData({ sysproxy_tray_icon: `${path}` });
+                    patchVerge({ sysproxy_tray_icon: `${path}` });
+                  }
+                }
+              }}
+            >
+              {verge?.sysproxy_tray_icon ? t("Clear") : t("Browse")}
+            </Button>
+          </GuardState>
+        </SettingItem>
+
+        <SettingItem label={t("Tun Tray Icon")}>
+          <GuardState
+            value={verge?.tun_tray_icon}
+            onCatch={onError}
+            onChange={(e) => onChangeData({ tun_tray_icon: e })}
+            onGuard={(e) => patchVerge({ tun_tray_icon: e })}
+          >
+            <Button
+              variant="outlined"
+              size="small"
+              startIcon={
+                verge?.tun_tray_icon && (
+                  <img
+                    height="20px"
+                    src={convertFileSrc(verge?.tun_tray_icon)}
+                  />
+                )
+              }
+              onClick={async () => {
+                if (verge?.tun_tray_icon) {
+                  onChangeData({ tun_tray_icon: "" });
+                  patchVerge({ tun_tray_icon: "" });
+                } else {
+                  const path = await openDialog({
+                    directory: false,
+                    multiple: false,
+                    filters: [
+                      {
+                        name: "Tray Icon Image",
+                        extensions: ["png"],
+                      },
+                    ],
+                  });
+                  if (path?.length) {
+                    onChangeData({ tun_tray_icon: `${path}` });
+                    patchVerge({ tun_tray_icon: `${path}` });
+                  }
+                }
+              }}
+            >
+              {verge?.tun_tray_icon ? t("Clear") : t("Browse")}
+            </Button>
+          </GuardState>
+        </SettingItem>
       </List>
     </BaseDialog>
   );

+ 3 - 0
src/locales/en.json

@@ -110,6 +110,9 @@
   "Hotkey Setting": "Hotkey Setting",
   "Traffic Graph": "Traffic Graph",
   "Memory Usage": "Memory Usage",
+  "Common Tray Icon": "Common Tray Icon",
+  "System Proxy Tray Icon": "System Proxy Tray Icon",
+  "Tun Tray Icon": "Tun Tray Icon",
   "Language": "Language",
   "Open App Dir": "Open App Dir",
   "Open Core Dir": "Open Core Dir",

+ 3 - 0
src/locales/zh.json

@@ -110,6 +110,9 @@
   "Hotkey Setting": "热键设置",
   "Traffic Graph": "流量图显",
   "Memory Usage": "内存使用",
+  "Common Tray Icon": "常规托盘图标",
+  "System Proxy Tray Icon": "系统代理托盘图标",
+  "Tun Tray Icon": "Tun模式托盘图标",
   "Language": "语言设置",
   "Open App Dir": "应用目录",
   "Open Core Dir": "内核目录",

+ 3 - 0
src/services/types.d.ts

@@ -200,6 +200,9 @@ interface IVergeConfig {
   theme_mode?: "light" | "dark" | "system";
   traffic_graph?: boolean;
   enable_memory_usage?: boolean;
+  common_tray_icon?: string;
+  sysproxy_tray_icon?: string;
+  tun_tray_icon?: string;
   enable_tun_mode?: boolean;
   enable_auto_launch?: boolean;
   enable_service_mode?: boolean;