Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3c3407c1f | ||
|
|
db7fd8a3d3 | ||
|
|
2b74606764 | ||
|
|
da2000c1e5 | ||
|
|
ab276045ea | ||
|
|
75aee23241 | ||
|
|
3a59d95732 | ||
|
|
6916009cc7 | ||
|
|
9b82d02c67 | ||
|
|
099cf8065f | ||
|
|
ad8b5a5171 | ||
|
|
ac09de615e | ||
|
|
743cc42879 | ||
|
|
3fd969b9b0 | ||
|
|
92ba69078d | ||
|
|
20ca8619f7 | ||
|
|
892738e198 | ||
|
|
1aa0c7bc34 | ||
|
|
aba9715453 | ||
|
|
c8f61d6359 | ||
|
|
1fd018f3f8 | ||
|
|
d7cfd7d3ac | ||
|
|
e310381735 | ||
|
|
8b5385b701 | ||
|
|
a1e1fedc3f | ||
|
|
84dc631d80 | ||
|
|
6a3072fe04 | ||
|
|
98d943f39d | ||
|
|
bcf724273d | ||
|
|
8703918a8c | ||
|
|
7e88f3ba29 | ||
|
|
d9a2f221db | ||
|
|
a4b3a257ed | ||
|
|
10397d0847 | ||
|
|
db442b2746 | ||
|
|
8cb3c69b78 | ||
|
|
967f21cc23 | ||
|
|
3ecd73f430 | ||
|
|
ca7f6b86d7 | ||
|
|
00cee81812 | ||
|
|
25f5db82dc | ||
|
|
9e5c5d5e69 | ||
|
|
2cfd1784d8 | ||
|
|
bec1b95ad3 | ||
|
|
e26f500ad0 | ||
|
|
9c33f007a1 | ||
|
|
902256d461 | ||
|
|
6051bd6d06 | ||
|
|
c82f4e50d2 | ||
|
|
94e785c75c | ||
|
|
8b8daa7b4c | ||
|
|
c95e63014f | ||
|
|
32bf42cbb9 | ||
|
|
175ec98947 | ||
|
|
0abd9343a9 | ||
|
|
c9976382a9 | ||
|
|
d38e93ac7e | ||
|
|
e51f1d20c0 | ||
|
|
df595f4835 | ||
|
|
63e4d2f686 | ||
|
|
971580def8 | ||
|
|
ffd32426b5 | ||
|
|
d2d26cc822 | ||
|
|
a373b0b6eb | ||
|
|
f515fa1443 | ||
|
|
e32e83d45e | ||
|
|
7be3cdeb65 | ||
|
|
b234b9166d | ||
|
|
2c485b5efb | ||
|
|
b7d7e1a1af | ||
|
|
01be6ae70a | ||
|
|
445eaadac3 | ||
|
|
d5b1dfddee | ||
|
|
c68ea04f06 | ||
|
|
9abc30b60c | ||
|
|
1f7561298c | ||
|
|
611c5757e0 | ||
|
|
ab56e82173 | ||
|
|
34350fadb6 | ||
|
|
77786da53f | ||
|
|
f794ca5426 | ||
|
|
a2010e6d1d | ||
|
|
4ce6e9bfd7 | ||
|
|
9a3794073b | ||
|
|
d6197d6d21 | ||
|
|
1f321cf6bc | ||
|
|
5c6d3f4078 | ||
|
|
6b8b95e4ca | ||
|
|
ae08d48641 | ||
|
|
d1ce5566cf | ||
|
|
5f027ebc79 | ||
|
|
8cf83f8338 | ||
|
|
b96e2c1fe0 | ||
|
|
4ad1379773 | ||
|
|
ef0883f732 | ||
|
|
a2076b4e2d | ||
|
|
0a3998530e | ||
|
|
ed2ec56a44 | ||
|
|
87473bdf92 | ||
|
|
8186a6841a | ||
|
|
0a0b5b6612 | ||
|
|
72704f9dc9 | ||
|
|
06ad23d904 | ||
|
|
fbd1c55f44 | ||
|
|
9668a04a1a | ||
|
|
24af375a8e | ||
|
|
a32c973ab8 | ||
|
|
50beb913de | ||
|
|
05f1ec7b34 | ||
|
|
9271b107b6 | ||
|
|
e7208dd7d2 | ||
|
|
e5dfb34082 | ||
|
|
2ba5c4e706 | ||
|
|
27bcc5f4f8 | ||
|
|
d884bd539b | ||
|
|
580a56727c | ||
|
|
ac3163d061 | ||
|
|
8bc7a6c3e1 | ||
|
|
31d368979e | ||
|
|
5e855e4755 | ||
|
|
a8b75aeabd | ||
|
|
854d42180a | ||
|
|
e94724595c | ||
|
|
6f1d9ba1b4 | ||
|
|
c090ae3b11 | ||
|
|
3303e95713 | ||
|
|
5cdc5075f8 | ||
|
|
eb1e4fe0c3 | ||
|
|
b1e3283a24 | ||
|
|
ce3b0bb479 | ||
|
|
25b295f2a8 | ||
|
|
18b7366258 | ||
|
|
565771a3ea | ||
|
|
f9376f6903 | ||
|
|
8e0f5b6abd | ||
|
|
41f32231f0 | ||
|
|
e1968891ac | ||
|
|
f04e707b10 | ||
|
|
0bb795b0e1 | ||
|
|
1c5e43690e | ||
|
|
f604416532 | ||
|
|
87ee07d481 | ||
|
|
7dec9cbe9b | ||
|
|
1274ba2324 | ||
|
|
d6014865d6 | ||
|
|
48a5ff6948 | ||
|
|
dd3950e46d | ||
|
|
1708246866 | ||
|
|
b0734f5935 | ||
|
|
11768862d3 | ||
|
|
ef409216d8 | ||
|
|
f739afea3d | ||
|
|
d5266fa003 | ||
|
|
149bdd5175 | ||
|
|
ec99e24ca1 | ||
|
|
7cc893383e | ||
|
|
3902480d39 |
6
.github/FUNDING.yml
vendored
@@ -1 +1,5 @@
|
|||||||
github: clash-verge-rev
|
custom:
|
||||||
|
[
|
||||||
|
"https://t.me/tribute/app?startapp=dtfk",
|
||||||
|
"https://t.me/tribute/app?startapp=dtLE",
|
||||||
|
]
|
||||||
|
|||||||
82
.github/workflows/autobuild.yml
vendored
@@ -2,9 +2,9 @@ name: Auto Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
# schedule:
|
||||||
# UTC+8 0,6,12,18
|
# # UTC+8 0,6,12,18
|
||||||
- cron: "0 16,22,4,10 * * *"
|
# - cron: "0 16,22,4,10 * * *"
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
env:
|
env:
|
||||||
TAG_NAME: autobuild
|
TAG_NAME: autobuild
|
||||||
@@ -77,6 +77,15 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Update git tag
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git tag -f ${{ env.TAG_NAME }}
|
||||||
|
git push --force origin ${{ env.TAG_NAME }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Fetch UPDATE logs
|
- name: Fetch UPDATE logs
|
||||||
id: fetch_update_logs
|
id: fetch_update_logs
|
||||||
run: |
|
run: |
|
||||||
@@ -111,31 +120,25 @@ jobs:
|
|||||||
cat > release.txt << EOF
|
cat > release.txt << EOF
|
||||||
$UPDATE_LOGS
|
$UPDATE_LOGS
|
||||||
|
|
||||||
## 我应该下载哪个版本?
|
## Which version should I download?
|
||||||
|
|
||||||
### MacOS
|
### MacOS
|
||||||
- MacOS intel芯片: x64.dmg
|
- MacOS Intel Chip: x64.dmg
|
||||||
- MacOS apple M芯片: aarch64.dmg
|
- MacOS Apple M Chip: aarch64.dmg
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
- Linux 64位: amd64.deb/amd64.rpm
|
- Linux 64-bit: amd64.deb/amd64.rpm
|
||||||
- Linux arm64 architecture: arm64.deb/aarch64.rpm
|
- Linux arm64: arm64.deb/aarch64.rpm
|
||||||
- Linux armv7架构: armhf.deb/armhfp.rpm
|
- Linux armv7: armhf.deb/armhfp.rpm
|
||||||
|
|
||||||
### Windows (不再支持Win7)
|
### Windows (Win7 is no longer supported)
|
||||||
#### 正常版本(推荐)
|
#### Normal version (recommended)
|
||||||
- 64位: x64-setup.exe
|
- 64-bit: x64-setup.exe
|
||||||
- arm64架构: arm64-setup.exe
|
- arm64: arm64-setup.exe
|
||||||
#### 便携版问题很多不再提供
|
#### Portable version is no longer available with many problems
|
||||||
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
|
#### Built-in Webview version 2 (large size, only used in enterprise version of the system or can not install webview2)
|
||||||
- 64位: x64_fixed_webview2-setup.exe
|
- 64-bit: x64_fixed_webview2-setup.exe
|
||||||
- arm64架构: arm64_fixed_webview2-setup.exe
|
- arm64: arm64_fixed_webview2-setup.exe
|
||||||
|
|
||||||
### FAQ
|
|
||||||
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
|
||||||
|
|
||||||
### 稳定机场VPN推荐
|
|
||||||
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
|
||||||
|
|
||||||
Created at ${{ env.BUILDTIME }}.
|
Created at ${{ env.BUILDTIME }}.
|
||||||
EOF
|
EOF
|
||||||
@@ -187,9 +190,12 @@ jobs:
|
|||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
- os: ubuntu-22.04
|
- os: ubuntu-latest
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
CARGO_NET_RETRY: "5"
|
||||||
|
CARGO_HTTP_CHECK_REVOKE: "false"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -208,7 +214,7 @@ jobs:
|
|||||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||||
|
|
||||||
- name: Install dependencies (ubuntu only)
|
- name: Install dependencies (ubuntu only)
|
||||||
if: matrix.os == 'ubuntu-22.04'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||||
@@ -239,12 +245,6 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
||||||
with:
|
with:
|
||||||
tagName: ${{ env.TAG_NAME }}
|
tagName: ${{ env.TAG_NAME }}
|
||||||
releaseName: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
|
releaseName: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
|
||||||
@@ -261,10 +261,10 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-22.04
|
- os: ubuntu-latest
|
||||||
target: aarch64-unknown-linux-gnu
|
target: aarch64-unknown-linux-gnu
|
||||||
arch: arm64
|
arch: arm64
|
||||||
- os: ubuntu-22.04
|
- os: ubuntu-latest
|
||||||
target: armv7-unknown-linux-gnueabihf
|
target: armv7-unknown-linux-gnueabihf
|
||||||
arch: armhf
|
arch: armhf
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@@ -309,15 +309,15 @@ jobs:
|
|||||||
sudo ls -lR /etc/apt/
|
sudo ls -lR /etc/apt/
|
||||||
|
|
||||||
cat > /tmp/sources.list << EOF
|
cat > /tmp/sources.list << EOF
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
|
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble main multiverse universe restricted
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
|
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble-security main multiverse universe restricted
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-updates main multiverse universe restricted
|
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble-updates main multiverse universe restricted
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-backports main multiverse universe restricted
|
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble-backports main multiverse universe restricted
|
||||||
|
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main multiverse universe restricted
|
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports noble main multiverse universe restricted
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main multiverse universe restricted
|
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports noble-security main multiverse universe restricted
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main multiverse universe restricted
|
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports noble-updates main multiverse universe restricted
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main multiverse universe restricted
|
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports noble-backports main multiverse universe restricted
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
|
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
|
||||||
|
|||||||
3
.github/workflows/clippy.yml
vendored
@@ -17,6 +17,9 @@ jobs:
|
|||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
CARGO_NET_RETRY: "5"
|
||||||
|
CARGO_HTTP_CHECK_REVOKE: "false"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
3
.github/workflows/dev.yml
vendored
@@ -28,6 +28,9 @@ jobs:
|
|||||||
bundle: dmg
|
bundle: dmg
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
CARGO_NET_RETRY: "5"
|
||||||
|
CARGO_HTTP_CHECK_REVOKE: "false"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
3
.github/workflows/fmt.yml
vendored
@@ -10,6 +10,9 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
rustfmt:
|
rustfmt:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_NET_RETRY: "5"
|
||||||
|
CARGO_HTTP_CHECK_REVOKE: "false"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
316
.github/workflows/release.yml
vendored
@@ -6,8 +6,8 @@ on:
|
|||||||
# workflow_dispatch:
|
# workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
# 应当限制在 main 分支上触发发布。
|
# 应当限制在 main 分支上触发发布。
|
||||||
branches:
|
# branches:
|
||||||
- main
|
# - main
|
||||||
# 应当限制 v*.*.* 的 tag 触发发布。
|
# 应当限制 v*.*.* 的 tag 触发发布。
|
||||||
tags:
|
tags:
|
||||||
- "v*.*.*"
|
- "v*.*.*"
|
||||||
@@ -40,9 +40,95 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Tag and package.json version are consistent."
|
echo "Tag and package.json version are consistent."
|
||||||
|
|
||||||
|
create_release_notes:
|
||||||
|
name: Create Release Notes
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Fetch UPDATE logs
|
||||||
|
id: fetch_update_logs
|
||||||
|
run: |
|
||||||
|
if [ -f "UPDATELOG.md" ]; then
|
||||||
|
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
|
||||||
|
if [ -n "$UPDATE_LOGS" ]; then
|
||||||
|
echo "Found update logs"
|
||||||
|
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$UPDATE_LOGS" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "No update sections found in UPDATELOG.md"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "UPDATELOG.md file not found"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install jq
|
||||||
|
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||||
|
echo "BUILDTIME=$(TZ=Europe/Moscow date)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
if [ -z "$UPDATE_LOGS" ]; then
|
||||||
|
echo "No update logs found, using default message"
|
||||||
|
UPDATE_LOGS="More new features are now supported. Check for detailed changelog soon."
|
||||||
|
else
|
||||||
|
echo "Using found update logs"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > release.txt << EOF
|
||||||
|
$UPDATE_LOGS
|
||||||
|
|
||||||
|
## Which version should I download?
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_aarch64.dmg"><img src="https://img.shields.io/badge/DMG-default?style=flat&logo=apple&label=Apple%20Silicon"></a>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_x64.dmg"><img src="https://img.shields.io/badge/DMG-default?style=flat&logo=apple&label=Intel"></a><br>
|
||||||
|
> :warning: **Warning**
|
||||||
|
If you get a notification that the application is corrupted when you run it on macOS, run this command:<br>
|
||||||
|
<code>sudo xattr -r -c /Applications/Koala\ Clash.app</code>
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_amd64.deb"><img src="https://img.shields.io/badge/x64-default?style=flat&logo=debian&label=DEB"> </a>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash.x86_64.rpm"><img src="https://img.shields.io/badge/x64-default?style=flat&logo=fedora&label=RPM"> </a>
|
||||||
|
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_arm64.deb"><img src="https://img.shields.io/badge/arm64-default?style=flat&logo=debian&label=DEB"> </a>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash.aarch64.rpm"><img src="https://img.shields.io/badge/aarch64-default?style=flat&logo=fedora&label=RPM"> </a>
|
||||||
|
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_armhf.deb"><img src="https://img.shields.io/badge/armhf-default?style=flat&logo=debian&label=DEB"> </a>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash.armhfp.rpm"><img src="https://img.shields.io/badge/armhfp-default?style=flat&logo=fedora&label=RPM"> </a>
|
||||||
|
|
||||||
|
#### Package availability for many distributions
|
||||||
|
|
||||||
|
<a href="https://aur.archlinux.org/packages/koala-clash-bin"><img src="https://img.shields.io/aur/version/koala-clash-bin"></a>
|
||||||
|
|
||||||
|
### Windows (Win7 is no longer supported)
|
||||||
|
#### Normal version (recommended)
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_x64-setup.exe"><img src="https://badgen.net/badge/icon/x64?icon=windows&label=exe"></a>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_arm64-setup.exe"><img src="https://badgen.net/badge/icon/arm64?icon=windows&label=exe"></a>
|
||||||
|
#### Portable version is no longer available with many problems
|
||||||
|
#### Built-in Webview version 2 (large size, only used in enterprise version of the system or can not install webview2)
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_x64_fixed_webview2-setup.exe"><img src="https://badgen.net/badge/icon/x64?icon=windows&label=exe"></a>
|
||||||
|
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Koala.Clash_arm64_fixed_webview2-setup.exe"><img src="https://badgen.net/badge/icon/arm64?icon=windows&label=exe"></a>
|
||||||
|
|
||||||
|
Created at ${{ env.BUILDTIME }}.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Upload Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: v${{env.VERSION}}
|
||||||
|
name: "Koala Clash v${{env.VERSION}}"
|
||||||
|
body_path: release.txt
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release Build
|
name: Release Build
|
||||||
needs: check_tag_version
|
needs: [check_tag_version, create_release_notes]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -96,7 +182,13 @@ jobs:
|
|||||||
pnpm i
|
pnpm i
|
||||||
pnpm run prebuild ${{ matrix.target }}
|
pnpm run prebuild ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Create .p8 file
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.appstoreconnect/private_keys
|
||||||
|
echo "${{ secrets.APPLE_API_KEY_CONTENT }}" > ~/.appstoreconnect/private_keys/AuthKey_${{ secrets.APPLE_API_KEY }}.p8
|
||||||
|
|
||||||
- name: Tauri build
|
- name: Tauri build
|
||||||
|
id: build
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||||
@@ -106,16 +198,114 @@ jobs:
|
|||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
APPLE_API_KEY_PATH: "~/.appstoreconnect/private_keys/AuthKey_${{ secrets.APPLE_API_KEY }}.p8"
|
||||||
with:
|
with:
|
||||||
tagName: v__VERSION__
|
|
||||||
releaseName: "Clash Verge Rev v__VERSION__"
|
|
||||||
releaseBody: "More new features are now supported."
|
|
||||||
tauriScript: pnpm
|
tauriScript: pnpm
|
||||||
args: --target ${{ matrix.target }}
|
args: --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Rename Artifact (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
# Rename .exe files
|
||||||
|
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
||||||
|
foreach ($file in $files) {
|
||||||
|
$newName = $file.Name -replace "_${{steps.build.outputs.appVersion}}_", "_"
|
||||||
|
Rename-Item $file.FullName $newName
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rename .exe.sig files
|
||||||
|
$sigFiles = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
|
||||||
|
foreach ($file in $sigFiles) {
|
||||||
|
$newName = $file.Name -replace "_${{steps.build.outputs.appVersion}}_", "_"
|
||||||
|
Rename-Item $file.FullName $newName
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Rename Artifact (Linux/macOS)
|
||||||
|
if: runner.os == 'Linux' || runner.os == 'macOS'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
TARGET_DIR="src-tauri/target/${{ matrix.target }}/release/bundle"
|
||||||
|
|
||||||
|
if [ ! -d "$TARGET_DIR" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
find "$TARGET_DIR" -type f \( -name "*.dmg" -o -name "*.deb" -o -name "*.rpm" \) -print0 | while IFS= read -r -d '' old_path; do
|
||||||
|
dir_path=$(dirname "$old_path")
|
||||||
|
old_filename=$(basename "$old_path")
|
||||||
|
new_filename=$(echo "$old_filename" \
|
||||||
|
| sed -E 's/_[0-9]+\.[0-9]+\.[0-9]+_/_/' \
|
||||||
|
| sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+//' \
|
||||||
|
)
|
||||||
|
new_path="${dir_path}/${new_filename}"
|
||||||
|
if [ "$old_path" != "$new_path" ]; then
|
||||||
|
echo " - '$old_filename' -> '$new_filename'"
|
||||||
|
mv "$old_path" "$new_path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Rename macOS Updater Files with Architecture Prefix
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
MACOS_DIR="src-tauri/target/${{ matrix.target }}/release/bundle/macos"
|
||||||
|
|
||||||
|
if [ ! -d "$MACOS_DIR" ]; then
|
||||||
|
echo "macOS bundle directory not found, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine architecture suffix
|
||||||
|
if [ "${{ matrix.target }}" == "aarch64-apple-darwin" ]; then
|
||||||
|
ARCH_SUFFIX="_aarch64"
|
||||||
|
elif [ "${{ matrix.target }}" == "x86_64-apple-darwin" ]; then
|
||||||
|
ARCH_SUFFIX="_x64"
|
||||||
|
else
|
||||||
|
echo "Unknown target: ${{ matrix.target }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rename .app.tar.gz files
|
||||||
|
find "$MACOS_DIR" -type f -name "*.app.tar.gz" ! -name "*.app.tar.gz.sig" -print0 | while IFS= read -r -d '' old_path; do
|
||||||
|
dir_path=$(dirname "$old_path")
|
||||||
|
old_filename=$(basename "$old_path")
|
||||||
|
new_filename=$(echo "$old_filename" | sed -E "s/\.app\.tar\.gz$/${ARCH_SUFFIX}.app.tar.gz/")
|
||||||
|
new_path="${dir_path}/${new_filename}"
|
||||||
|
if [ "$old_path" != "$new_path" ]; then
|
||||||
|
echo "Renaming updater: '$old_filename' -> '$new_filename'"
|
||||||
|
mv "$old_path" "$new_path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Rename .app.tar.gz.sig files
|
||||||
|
find "$MACOS_DIR" -type f -name "*.app.tar.gz.sig" -print0 | while IFS= read -r -d '' old_path; do
|
||||||
|
dir_path=$(dirname "$old_path")
|
||||||
|
old_filename=$(basename "$old_path")
|
||||||
|
new_filename=$(echo "$old_filename" | sed -E "s/\.app\.tar\.gz\.sig$/${ARCH_SUFFIX}.app.tar.gz.sig/")
|
||||||
|
new_path="${dir_path}/${new_filename}"
|
||||||
|
if [ "$old_path" != "$new_path" ]; then
|
||||||
|
echo "Renaming signature: '$old_filename' -> '$new_filename'"
|
||||||
|
mv "$old_path" "$new_path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Upload Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: v${{steps.build.outputs.appVersion}}
|
||||||
|
name: "Koala Clash v${{steps.build.outputs.appVersion}}"
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
files: |
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.tar.gz
|
||||||
|
src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.tar.gz.sig
|
||||||
|
|
||||||
release-for-linux-arm:
|
release-for-linux-arm:
|
||||||
name: Release Build for Linux ARM
|
name: Release Build for Linux ARM
|
||||||
strategy:
|
strategy:
|
||||||
@@ -225,14 +415,36 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install jq
|
sudo apt-get install jq
|
||||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
echo "BUILDTIME=$(TZ=Europe/Moscow date)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Rename
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
TARGET_DIR="src-tauri/target/${{ matrix.target }}/release/bundle"
|
||||||
|
|
||||||
|
if [ ! -d "$TARGET_DIR" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
find "$TARGET_DIR" -type f \( -name "*.dmg" -o -name "*.deb" -o -name "*.rpm" \) -print0 | while IFS= read -r -d '' old_path; do
|
||||||
|
dir_path=$(dirname "$old_path")
|
||||||
|
old_filename=$(basename "$old_path")
|
||||||
|
new_filename=$(echo "$old_filename" \
|
||||||
|
| sed -E 's/_[0-9]+\.[0-9]+\.[0-9]+_/_/' \
|
||||||
|
| sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+//' \
|
||||||
|
)
|
||||||
|
new_path="${dir_path}/${new_filename}"
|
||||||
|
if [ "$old_path" != "$new_path" ]; then
|
||||||
|
echo " - '$old_filename' -> '$new_filename'"
|
||||||
|
mv "$old_path" "$new_path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: v${{env.VERSION}}
|
tag_name: v${{env.VERSION}}
|
||||||
name: "Clash Verge Rev v${{env.VERSION}}"
|
name: "Koala Clash v${{env.VERSION}}"
|
||||||
body: "More new features are now supported."
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: |
|
files: |
|
||||||
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||||
@@ -281,8 +493,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Download WebView2 Runtime
|
- name: Download WebView2 Runtime
|
||||||
run: |
|
run: |
|
||||||
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
|
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
|
||||||
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
|
Expand .\Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -F:* ./src-tauri
|
||||||
Remove-Item .\src-tauri\tauri.windows.conf.json
|
Remove-Item .\src-tauri\tauri.windows.conf.json
|
||||||
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
|
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
|
||||||
|
|
||||||
@@ -302,19 +514,19 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
||||||
foreach ($file in $files) {
|
foreach ($file in $files) {
|
||||||
$newName = $file.Name -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
|
$newName = $file.Name -replace "_${{steps.build.outputs.appVersion}}_", "_" -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
|
||||||
Rename-Item $file.FullName $newName
|
Rename-Item $file.FullName $newName
|
||||||
}
|
}
|
||||||
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
|
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
|
||||||
foreach ($file in $files) {
|
foreach ($file in $files) {
|
||||||
$newName = $file.Name -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
|
$newName = $file.Name -replace "_${{steps.build.outputs.appVersion}}_", "_" -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
|
||||||
Rename-Item $file.FullName $newName
|
Rename-Item $file.FullName $newName
|
||||||
}
|
}
|
||||||
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
|
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
|
||||||
foreach ($file in $files) {
|
foreach ($file in $files) {
|
||||||
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
|
$newName = $file.Name -replace "_${{steps.build.outputs.appVersion}}_", "_" -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
|
||||||
Rename-Item $file.FullName $newName
|
Rename-Item $file.FullName $newName
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,8 +534,7 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: v${{steps.build.outputs.appVersion}}
|
tag_name: v${{steps.build.outputs.appVersion}}
|
||||||
name: "Clash Verge Rev v${{steps.build.outputs.appVersion}}"
|
name: "Koala Clash v${{steps.build.outputs.appVersion}}"
|
||||||
body: "More new features are now supported."
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
||||||
|
|
||||||
@@ -383,25 +594,68 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
submit-to-winget:
|
push-notify-to-telegram:
|
||||||
name: Submit to Winget
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [release-update]
|
needs: [release-update, release-update-for-fixed-webview2]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
- name: Fetch UPDATE logs
|
||||||
|
id: fetch_update_logs
|
||||||
|
run: |
|
||||||
|
if [ -f "UPDATELOG.md" ]; then
|
||||||
|
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
|
||||||
|
if [ -n "$UPDATE_LOGS" ]; then
|
||||||
|
echo "Found update logs"
|
||||||
|
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$UPDATE_LOGS" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "No update sections found in UPDATELOG.md"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "UPDATELOG.md file not found"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install jq
|
sudo apt-get install jq
|
||||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||||
- name: Submit to Winget
|
echo "BUILDTIME=$(TZ=Europe/Moscow date)" >> $GITHUB_ENV
|
||||||
uses: vedantmgoyal9/winget-releaser@main
|
|
||||||
|
- run: |
|
||||||
|
if [ -z "$UPDATE_LOGS" ]; then
|
||||||
|
echo "No update logs found, using default message"
|
||||||
|
UPDATE_LOGS="More new features are now supported. Check for detailed changelog soon."
|
||||||
|
else
|
||||||
|
echo "Using found update logs"
|
||||||
|
UPDATE_LOGS=$(echo "$UPDATE_LOGS" | sed 's/^## \(v.*\)/\*\1\*/')
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > release.txt << EOF
|
||||||
|
Вышло обновление!
|
||||||
|
|
||||||
|
$UPDATE_LOGS
|
||||||
|
|
||||||
|
[Ссылка на релиз](https://github.com/coolcoala/clash-verge-rev-lite/releases/latest)
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: notify to channel
|
||||||
|
uses: appleboy/telegram-action@master
|
||||||
with:
|
with:
|
||||||
identifier: ClashVergeRev.ClashVergeRev
|
to: ${{ secrets.TELEGRAM_TO_CHANNEL }}
|
||||||
version: ${{env.VERSION}}
|
token: ${{ secrets.TELEGRAM_TOKEN }}
|
||||||
release-tag: v${{env.VERSION}}
|
message_file: release.txt
|
||||||
installers-regex: '_(arm64|x64|x86)-setup\.exe$'
|
format: markdown
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
|
||||||
|
- name: notify to group
|
||||||
|
uses: appleboy/telegram-action@master
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.TELEGRAM_TO_GROUP }}
|
||||||
|
token: ${{ secrets.TELEGRAM_TOKEN }}
|
||||||
|
message_file: release.txt
|
||||||
|
format: markdown
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -10,3 +10,4 @@ scripts/_env.sh
|
|||||||
.tool-versions
|
.tool-versions
|
||||||
.idea
|
.idea
|
||||||
.old
|
.old
|
||||||
|
bun.lock
|
||||||
74
README.md
@@ -1,7 +1,7 @@
|
|||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<img src="./src-tauri/icons/icon.png" alt="Clash" width="128" />
|
<img src="./src-tauri/icons/icon.png" alt="Clash" width="128" />
|
||||||
<br>
|
<br>
|
||||||
Continuation of <a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a>
|
Fork of <a href="https://github.com/clash-verge-rev/clash-verge-rev">Clash Verge Rev</a>
|
||||||
<br>
|
<br>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -9,76 +9,24 @@
|
|||||||
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
## Preview
|

|
||||||
|
|
||||||
| Dark | Light |
|
|
||||||
| -------------------------------- | --------------------------------- |
|
|
||||||
|  |  |
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
|
Go to the [Release page](https://github.com/coolcoala/clash-verge-rev-lite/releases) to download the corresponding installation package<br>
|
||||||
Go to the [Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases) to download the corresponding installation package<br>
|
|
||||||
Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||||
|
|
||||||
#### 我应当怎样选择发行版
|
### Telegram channel: ---
|
||||||
|
|
||||||
| 版本 | 特征 | 链接 |
|
|
||||||
| :-------- | :--------------------------------------- | :------------------------------------------------------------------------------------- |
|
|
||||||
| Stable | 正式版,高可靠性,适合日常使用。 | [Release](https://github.com/clash-verge-rev/clash-verge-rev/releases) |
|
|
||||||
| Alpha | 早期测试版,功能未完善,可能存在缺陷。 | [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) |
|
|
||||||
| AutoBuild | 滚动更新版,持续集成更新,适合开发测试。 | [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) |
|
|
||||||
|
|
||||||
#### 安装说明和常见问题,请到 [文档页](https://clash-verge-rev.github.io/) 查看
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### TG 频道: [@clash_verge_rev](https://t.me/clash_verge_re)
|
|
||||||
|
|
||||||
## Promotion
|
|
||||||
|
|
||||||
#### [狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
|
||||||
|
|
||||||
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
|
|
||||||
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:[点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
|
||||||
- Clash Verge 专属 8 折优惠码: verge20 (仅有 500 份)
|
|
||||||
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
|
|
||||||
- 海外团队,无跑路风险,高达 50% 返佣
|
|
||||||
- 集群负载均衡设计,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
|
|
||||||
- 全球首家 Hysteria 协议机场,现已上线更快的 `Hysteria2` 协议(Clash Verge 客户端最佳搭配)
|
|
||||||
- 解锁流媒体及 ChatGPT
|
|
||||||
- 官网:[https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
|
||||||
|
|
||||||
#### 本项目的构建与发布环境由 [YXVM](https://yxvm.com/aff.php?aff=827) 独立服务器全力支持,
|
|
||||||
|
|
||||||
感谢提供 独享资源、高性能、高速网络 的强大后端环境。如果你觉得下载够快、使用够爽,那是因为我们用了好服务器!
|
|
||||||
|
|
||||||
🧩 YXVM 独立服务器优势:
|
|
||||||
|
|
||||||
- 🌎 优质网络,回程优化,下载快到飞起
|
|
||||||
- 🔧 物理机独享资源,非VPS可比,性能拉满
|
|
||||||
- 🧠 适合跑代理、搭建 WEB 站 CDN 站 、搞 CI/CD 或任何高负载应用
|
|
||||||
- 💡 支持即开即用,多机房选择,CN2 / IEPL 可选
|
|
||||||
- 📦 本项目使用配置已在售,欢迎同款入手!
|
|
||||||
- 🎯 想要同款构建体验?[立即下单 YXVM 独立服务器!](https://yxvm.com/aff.php?aff=827)
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 基于性能强劲的 Rust 和 Tauri 2 框架
|
- Based on the powerful Rust and Tauri 2 frameworks.
|
||||||
- 内置[Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo)内核,并支持切换 `Alpha` 版本内核。
|
- Built-in [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) kernel with support for switching between `Alpha` versions of the kernel.
|
||||||
- 简洁美观的用户界面,支持自定义主题颜色、代理组/托盘图标以及 `CSS Injection`。
|
- Simple and beautiful user interface with support for custom theme colors, agent group/tray icons, and `CSS Injection`.
|
||||||
- 配置文件管理和增强(Merge 和 Script),配置文件语法提示。
|
- Configuration file management, configuration file syntax hints.
|
||||||
- 系统代理和守卫、`TUN(虚拟网卡)` 模式。
|
- System Agent and Guard, `TUN (Virtual NIC)` mode.
|
||||||
- 可视化节点和规则编辑
|
- Visual node and rule editing
|
||||||
- WebDav 配置备份和同步
|
- WebDav configuration backup and synchronization
|
||||||
|
|
||||||
### FAQ
|
|
||||||
|
|
||||||
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq/windows.html)
|
|
||||||
|
|
||||||
### Donation
|
|
||||||
|
|
||||||
[捐助Clash Verge Rev的开发](https://github.com/sponsors/clash-verge-rev)
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
1839
UPDATELOG.md
BIN
docs/preview.png
Normal file
|
After Width: | Height: | Size: 954 KiB |
|
Before Width: | Height: | Size: 314 KiB |
|
Before Width: | Height: | Size: 274 KiB |
21
hooks/use-mobile.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
const MOBILE_BREAKPOINT = 768;
|
||||||
|
|
||||||
|
export function useIsMobile() {
|
||||||
|
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
||||||
|
const onChange = () => {
|
||||||
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
|
};
|
||||||
|
mql.addEventListener("change", onChange);
|
||||||
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
|
return () => mql.removeEventListener("change", onChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return !!isMobile;
|
||||||
|
}
|
||||||
101
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clash-verge",
|
"name": "koala-clash",
|
||||||
"version": "2.3.2",
|
"version": "0.2.8",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev",
|
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev",
|
||||||
@@ -29,53 +29,55 @@
|
|||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@hookform/resolvers": "^5.1.1",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@juggle/resize-observer": "^3.4.0",
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
"@mui/icons-material": "^7.1.1",
|
"@mui/icons-material": "^7.3.2",
|
||||||
"@mui/lab": "7.0.0-beta.13",
|
"@mui/lab": "7.0.0-beta.13",
|
||||||
"@mui/material": "^7.1.1",
|
"@mui/material": "^7.3.2",
|
||||||
"@mui/x-data-grid": "^8.5.1",
|
"@mui/x-data-grid": "^8.11.3",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.14",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-context-menu": "^2.2.15",
|
"@radix-ui/react-context-menu": "^2.2.16",
|
||||||
"@radix-ui/react-dialog": "^1.1.14",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-popover": "^1.1.14",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
"@radix-ui/react-progress": "^1.1.7",
|
"@radix-ui/react-progress": "^1.1.7",
|
||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-switch": "^1.2.5",
|
"@radix-ui/react-switch": "^1.2.6",
|
||||||
"@radix-ui/react-tooltip": "^1.2.7",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@tauri-apps/api": "2.5.0",
|
"@tauri-apps/api": "2.5.0",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
|
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.2.2",
|
"@tauri-apps/plugin-deep-link": "~2.4.3",
|
||||||
"@tauri-apps/plugin-fs": "^2.3.0",
|
"@tauri-apps/plugin-dialog": "^2.4.0",
|
||||||
"@tauri-apps/plugin-global-shortcut": "^2.2.1",
|
"@tauri-apps/plugin-fs": "^2.4.2",
|
||||||
"@tauri-apps/plugin-notification": "^2.2.2",
|
"@tauri-apps/plugin-global-shortcut": "^2.3.0",
|
||||||
"@tauri-apps/plugin-process": "^2.2.1",
|
"@tauri-apps/plugin-notification": "^2.3.1",
|
||||||
|
"@tauri-apps/plugin-process": "^2.3.0",
|
||||||
"@tauri-apps/plugin-shell": "2.2.1",
|
"@tauri-apps/plugin-shell": "2.2.1",
|
||||||
"@tauri-apps/plugin-updater": "2.7.1",
|
"@tauri-apps/plugin-updater": "2.7.1",
|
||||||
"@tauri-apps/plugin-window-state": "^2.2.2",
|
"@tauri-apps/plugin-window-state": "^2.4.0",
|
||||||
"@types/d3-shape": "^3.1.7",
|
"@types/d3-shape": "^3.1.7",
|
||||||
"@types/json-schema": "^7.0.15",
|
"@types/json-schema": "^7.0.15",
|
||||||
"ahooks": "^3.8.5",
|
"ahooks": "^3.9.5",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.12.2",
|
||||||
"chart.js": "^4.4.9",
|
"chart.js": "^4.5.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"cli-color": "^2.0.4",
|
"cli-color": "^2.0.4",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
"d3-shape": "^3.2.0",
|
"d3-shape": "^3.2.0",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"foxact": "^0.2.45",
|
"foxact": "^0.2.49",
|
||||||
"glob": "^11.0.2",
|
"framer-motion": "^12.23.16",
|
||||||
"i18next": "^25.2.1",
|
"glob": "^11.0.3",
|
||||||
"js-base64": "^3.7.7",
|
"i18next": "^25.5.2",
|
||||||
|
"js-base64": "^3.7.8",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lucide-react": "^0.514.0",
|
"lucide-react": "^0.514.0",
|
||||||
@@ -83,25 +85,26 @@
|
|||||||
"monaco-yaml": "^5.4.0",
|
"monaco-yaml": "^5.4.0",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"peggy": "^5.0.3",
|
"peggy": "^5.0.6",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-chartjs-2": "^5.3.0",
|
"react-chartjs-2": "^5.3.0",
|
||||||
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-error-boundary": "6.0.0",
|
"react-error-boundary": "6.0.0",
|
||||||
"react-hook-form": "^7.57.0",
|
"react-hook-form": "^7.63.0",
|
||||||
"react-i18next": "15.5.2",
|
"react-i18next": "15.5.2",
|
||||||
"react-markdown": "10.1.0",
|
"react-markdown": "10.1.0",
|
||||||
"react-monaco-editor": "0.58.0",
|
"react-monaco-editor": "0.58.0",
|
||||||
"react-router-dom": "7.6.2",
|
"react-router-dom": "7.6.2",
|
||||||
"react-virtuoso": "^4.12.8",
|
"react-virtuoso": "^4.14.0",
|
||||||
"sockette": "^2.0.6",
|
"sockette": "^2.0.6",
|
||||||
"sonner": "^2.0.5",
|
"sonner": "^2.0.7",
|
||||||
"swr": "^2.3.3",
|
"swr": "^2.3.6",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tar": "^7.4.3",
|
"tar": "^7.4.3",
|
||||||
"types-pac": "^1.0.3",
|
"types-pac": "^1.0.3",
|
||||||
"zod": "^3.25.67",
|
"zod": "^3.25.76",
|
||||||
"zustand": "^5.0.5"
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/github": "^6.0.1",
|
"@actions/github": "^6.0.1",
|
||||||
@@ -109,30 +112,30 @@
|
|||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^24.0.0",
|
"@types/node": "^24.5.2",
|
||||||
"@types/react": "19.1.6",
|
"@types/react": "19.1.6",
|
||||||
"@types/react-dom": "19.1.6",
|
"@types/react-dom": "19.1.6",
|
||||||
"@vitejs/plugin-legacy": "^6.1.1",
|
"@vitejs/plugin-legacy": "^6.1.1",
|
||||||
"@vitejs/plugin-react": "4.5.1",
|
"@vitejs/plugin-react": "4.5.1",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"meta-json-schema": "^1.19.10",
|
"meta-json-schema": "^1.19.13",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"postcss": "^8.5.4",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.6.2",
|
||||||
"pretty-quick": "^4.2.2",
|
"pretty-quick": "^4.2.2",
|
||||||
"sass": "^1.89.1",
|
"sass": "^1.93.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.13",
|
||||||
"terser": "^5.41.0",
|
"terser": "^5.44.0",
|
||||||
"tw-animate-css": "^1.3.4",
|
"tw-animate-css": "^1.3.8",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.6",
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vite-plugin-svgr": "^4.3.0"
|
"vite-plugin-svgr": "^4.5.0"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
|
|||||||
2698
pnpm-lock.yaml
generated
@@ -42,16 +42,16 @@ async function resolvePortable() {
|
|||||||
|
|
||||||
const zip = new AdmZip();
|
const zip = new AdmZip();
|
||||||
|
|
||||||
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
|
zip.addLocalFile(path.join(releaseDir, "Koala Clash.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-mihomo.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-mihomo-alpha.exe"));
|
||||||
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
||||||
zip.addLocalFolder(
|
zip.addLocalFolder(
|
||||||
path.join(
|
path.join(
|
||||||
releaseDir,
|
releaseDir,
|
||||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
`Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${arch}`,
|
||||||
),
|
),
|
||||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
`Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${arch}`,
|
||||||
);
|
);
|
||||||
zip.addLocalFolder(configDir, ".config");
|
zip.addLocalFolder(configDir, ".config");
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ async function resolvePortable() {
|
|||||||
}
|
}
|
||||||
const zip = new AdmZip();
|
const zip = new AdmZip();
|
||||||
|
|
||||||
zip.addLocalFile(path.join(releaseDir, "clash-verge.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-clash.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-mihomo.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
|
zip.addLocalFile(path.join(releaseDir, "koala-mihomo-alpha.exe"));
|
||||||
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
||||||
zip.addLocalFolder(configDir, ".config");
|
zip.addLocalFolder(configDir, ".config");
|
||||||
|
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ async function getLatestAlphaVersion() {
|
|||||||
|
|
||||||
/* ======= clash meta stable ======= */
|
/* ======= clash meta stable ======= */
|
||||||
const META_VERSION_URL =
|
const META_VERSION_URL =
|
||||||
"https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt";
|
"https://github.com/vffuunnyy/mihetero/releases/download/Prerelease-xhttp/version.txt";
|
||||||
const META_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download`;
|
const META_URL_PREFIX = `https://github.com/vffuunnyy/mihetero/releases/download`;
|
||||||
let META_VERSION;
|
let META_VERSION;
|
||||||
|
|
||||||
const META_MAP = {
|
const META_MAP = {
|
||||||
@@ -175,8 +175,8 @@ function clashMetaAlpha() {
|
|||||||
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
|
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "verge-mihomo-alpha",
|
name: "koala-mihomo-alpha",
|
||||||
targetFile: `verge-mihomo-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
targetFile: `koala-mihomo-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
||||||
exeFile,
|
exeFile,
|
||||||
zipFile,
|
zipFile,
|
||||||
downloadURL,
|
downloadURL,
|
||||||
@@ -187,13 +187,13 @@ function clashMeta() {
|
|||||||
const name = META_MAP[`${platform}-${arch}`];
|
const name = META_MAP[`${platform}-${arch}`];
|
||||||
const isWin = platform === "win32";
|
const isWin = platform === "win32";
|
||||||
const urlExt = isWin ? "zip" : "gz";
|
const urlExt = isWin ? "zip" : "gz";
|
||||||
const downloadURL = `${META_URL_PREFIX}/${META_VERSION}/${name}-${META_VERSION}.${urlExt}`;
|
const downloadURL = `${META_URL_PREFIX}/Prerelease-xhttp/${name}-${META_VERSION}.${urlExt}`;
|
||||||
const exeFile = `${name}${isWin ? ".exe" : ""}`;
|
const exeFile = `${name}${isWin ? ".exe" : ""}`;
|
||||||
const zipFile = `${name}-${META_VERSION}.${urlExt}`;
|
const zipFile = `${name}-${META_VERSION}.${urlExt}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "verge-mihomo",
|
name: "koala-mihomo",
|
||||||
targetFile: `verge-mihomo-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
targetFile: `koala-mihomo-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
||||||
exeFile,
|
exeFile,
|
||||||
zipFile,
|
zipFile,
|
||||||
downloadURL,
|
downloadURL,
|
||||||
@@ -381,7 +381,7 @@ const resolvePlugin = async () => {
|
|||||||
// service chmod
|
// service chmod
|
||||||
const resolveServicePermission = async () => {
|
const resolveServicePermission = async () => {
|
||||||
const serviceExecutables = [
|
const serviceExecutables = [
|
||||||
"clash-verge-service*",
|
"koala-clash-service*",
|
||||||
"install-service*",
|
"install-service*",
|
||||||
"uninstall-service*",
|
"uninstall-service*",
|
||||||
];
|
];
|
||||||
@@ -429,14 +429,14 @@ async function resolveLocales() {
|
|||||||
/**
|
/**
|
||||||
* main
|
* main
|
||||||
*/
|
*/
|
||||||
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
|
const SERVICE_URL = `https://github.com/coolcoala/koala-clash-service/releases/download/${SIDECAR_HOST}`;
|
||||||
|
|
||||||
const resolveService = () => {
|
const resolveService = () => {
|
||||||
let ext = platform === "win32" ? ".exe" : "";
|
let ext = platform === "win32" ? ".exe" : "";
|
||||||
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
||||||
resolveResource({
|
resolveResource({
|
||||||
file: "clash-verge-service" + suffix + ext,
|
file: "koala-clash-service" + suffix + ext,
|
||||||
downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`,
|
downloadURL: `${SERVICE_URL}/koala-clash-service${ext}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -489,13 +489,13 @@ const resolveWinSysproxy = () =>
|
|||||||
const tasks = [
|
const tasks = [
|
||||||
// { name: "clash", func: resolveClash, retry: 5 },
|
// { name: "clash", func: resolveClash, retry: 5 },
|
||||||
{
|
{
|
||||||
name: "verge-mihomo-alpha",
|
name: "koala-mihomo-alpha",
|
||||||
func: () =>
|
func: () =>
|
||||||
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
||||||
retry: 5,
|
retry: 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "verge-mihomo",
|
name: "koala-mihomo",
|
||||||
func: () =>
|
func: () =>
|
||||||
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
||||||
retry: 5,
|
retry: 5,
|
||||||
|
|||||||
2134
src-tauri/Cargo.lock
generated
@@ -1,21 +1,24 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "clash-verge"
|
name = "koala-clash"
|
||||||
version = "2.3.2"
|
version = "0.2.8"
|
||||||
description = "clash verge"
|
description = "koala clash"
|
||||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
authors = ["zzzgydi", "wonfen", "MystiPanda", "coolcoala"]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/clash-verge-rev/clash-verge-rev.git"
|
repository = "https://github.com/coolcoala/clash-verge-rev-lite.git"
|
||||||
default-run = "clash-verge"
|
default-run = "koala-clash"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[package.metadata.bundle]
|
[package.metadata.bundle]
|
||||||
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
identifier = "io.github.koala-clash"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.3.0", features = [] }
|
tauri-build = { version = "2.3.0", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
url = "2.5.4"
|
||||||
|
os_info = "3.0"
|
||||||
|
machine-uid = "0.5.3"
|
||||||
warp = "0.3.7"
|
warp = "0.3.7"
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
dirs = "6.0"
|
dirs = "6.0"
|
||||||
@@ -25,7 +28,7 @@ dunce = "1.0.5"
|
|||||||
log4rs = "1.3.0"
|
log4rs = "1.3.0"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
sysinfo = "0.35.2"
|
sysinfo = "0.36.1"
|
||||||
boa_engine = "0.20.0"
|
boa_engine = "0.20.0"
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
serde_yaml = "0.9.34-deprecated"
|
serde_yaml = "0.9.34-deprecated"
|
||||||
@@ -42,7 +45,7 @@ tokio = { version = "1.45.1", features = [
|
|||||||
"sync",
|
"sync",
|
||||||
] }
|
] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
reqwest = { version = "0.12.20", features = ["json", "rustls-tls", "cookies"] }
|
reqwest = { version = "0.12.20", features = ["json", "rustls-tls", "cookies", "brotli", "gzip", "zstd"] }
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
|
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
|
||||||
image = "0.25.6"
|
image = "0.25.6"
|
||||||
@@ -60,7 +63,6 @@ tauri-plugin-dialog = "2.3.0"
|
|||||||
tauri-plugin-fs = "2.4.0"
|
tauri-plugin-fs = "2.4.0"
|
||||||
tauri-plugin-process = "2.3.0"
|
tauri-plugin-process = "2.3.0"
|
||||||
tauri-plugin-clipboard-manager = "2.3.0"
|
tauri-plugin-clipboard-manager = "2.3.0"
|
||||||
tauri-plugin-deep-link = "2.4.0"
|
|
||||||
tauri-plugin-devtools = "2.0.0"
|
tauri-plugin-devtools = "2.0.0"
|
||||||
tauri-plugin-window-state = "2.3.0"
|
tauri-plugin-window-state = "2.3.0"
|
||||||
zip = "4.2.0"
|
zip = "4.2.0"
|
||||||
@@ -82,6 +84,7 @@ sha2 = "0.10.9"
|
|||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
scopeguard = "1.2.0"
|
scopeguard = "1.2.0"
|
||||||
tauri-plugin-notification = "2.3.0"
|
tauri-plugin-notification = "2.3.0"
|
||||||
|
tauri-plugin-deep-link = "2"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
runas = "=1.2.0"
|
runas = "=1.2.0"
|
||||||
@@ -107,6 +110,7 @@ users = "0.11.0"
|
|||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-autostart = "2.5.0"
|
tauri-plugin-autostart = "2.5.0"
|
||||||
tauri-plugin-global-shortcut = "2.3.0"
|
tauri-plugin-global-shortcut = "2.3.0"
|
||||||
|
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
|
||||||
tauri-plugin-updater = "2.9.0"
|
tauri-plugin-updater = "2.9.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"autostart:allow-disable",
|
"autostart:allow-disable",
|
||||||
"autostart:allow-is-enabled",
|
"autostart:allow-is-enabled",
|
||||||
"core:window:allow-set-theme",
|
"core:window:allow-set-theme",
|
||||||
"notification:default"
|
"notification:default",
|
||||||
|
"core:webview:allow-set-webview-zoom"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
chmod +x /usr/bin/install-service
|
chmod +x /usr/bin/install-service
|
||||||
chmod +x /usr/bin/uninstall-service
|
chmod +x /usr/bin/uninstall-service
|
||||||
chmod +x /usr/bin/clash-verge-service
|
chmod +x /usr/bin/koala-clash-service
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<false/>
|
<false/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>io.github.clash-verge-rev.clash-verge-rev</string>
|
<string>io.github.koala-clash</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.security.inherit</key>
|
<key>com.apple.security.inherit</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|||||||
@@ -427,52 +427,52 @@ Function .onInit
|
|||||||
!endif
|
!endif
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|
||||||
!macro CheckAllVergeProcesses
|
!macro CheckAllKoalaProcesses
|
||||||
; Check if clash-verge-service.exe is running
|
; Check if koala-clash-service.exe is running
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::FindProcessCurrentUser "clash-verge-service.exe"
|
nsis_tauri_utils::FindProcessCurrentUser "koala-clash-service.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::FindProcess "clash-verge-service.exe"
|
nsis_tauri_utils::FindProcess "koala-clash-service.exe"
|
||||||
!endif
|
!endif
|
||||||
Pop $R0
|
Pop $R0
|
||||||
${If} $R0 = 0
|
${If} $R0 = 0
|
||||||
DetailPrint "Kill clash-verge-service.exe..."
|
DetailPrint "Kill koala-clash-service.exe..."
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
|
nsis_tauri_utils::KillProcessCurrentUser "koala-clash-service.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::KillProcess "clash-verge-service.exe"
|
nsis_tauri_utils::KillProcess "koala-clash-service.exe"
|
||||||
!endif
|
!endif
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; Check if verge-mihomo-alpha.exe is running
|
; Check if koala-mihomo-alpha.exe is running
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::FindProcessCurrentUser "verge-mihomo-alpha.exe"
|
nsis_tauri_utils::FindProcessCurrentUser "koala-mihomo-alpha.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::FindProcess "verge-mihomo-alpha.exe"
|
nsis_tauri_utils::FindProcess "koala-mihomo-alpha.exe"
|
||||||
!endif
|
!endif
|
||||||
Pop $R0
|
Pop $R0
|
||||||
${If} $R0 = 0
|
${If} $R0 = 0
|
||||||
DetailPrint "Kill verge-mihomo-alpha.exe..."
|
DetailPrint "Kill koala-mihomo-alpha.exe..."
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::KillProcessCurrentUser "verge-mihomo-alpha.exe"
|
nsis_tauri_utils::KillProcessCurrentUser "koala-mihomo-alpha.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::KillProcess "verge-mihomo-alpha.exe"
|
nsis_tauri_utils::KillProcess "koala-mihomo-alpha.exe"
|
||||||
!endif
|
!endif
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; Check if verge-mihomo.exe is running
|
; Check if koala-mihomo.exe is running
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::FindProcessCurrentUser "verge-mihomo.exe"
|
nsis_tauri_utils::FindProcessCurrentUser "koala-mihomo.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::FindProcess "verge-mihomo.exe"
|
nsis_tauri_utils::FindProcess "koala-mihomo.exe"
|
||||||
!endif
|
!endif
|
||||||
Pop $R0
|
Pop $R0
|
||||||
${If} $R0 = 0
|
${If} $R0 = 0
|
||||||
DetailPrint "Kill verge-mihomo.exe..."
|
DetailPrint "Kill koala-mihomo.exe..."
|
||||||
!if "${INSTALLMODE}" == "currentUser"
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
nsis_tauri_utils::KillProcessCurrentUser "verge-mihomo.exe"
|
nsis_tauri_utils::KillProcessCurrentUser "koala-mihomo.exe"
|
||||||
!else
|
!else
|
||||||
nsis_tauri_utils::KillProcess "verge-mihomo.exe"
|
nsis_tauri_utils::KillProcess "koala-mihomo.exe"
|
||||||
!endif
|
!endif
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
@@ -509,22 +509,22 @@ FunctionEnd
|
|||||||
${EndIf}
|
${EndIf}
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
!macro StartVergeService
|
!macro StartKoalaService
|
||||||
; Check if the service exists
|
; Check if the service exists
|
||||||
SimpleSC::ExistsService "clash_verge_service"
|
SimpleSC::ExistsService "koala_clash_service"
|
||||||
Pop $0 ; 0:service exists;other: service not exists
|
Pop $0 ; 0:service exists;other: service not exists
|
||||||
; Service exists
|
; Service exists
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
Push $0
|
Push $0
|
||||||
; Check if the service is running
|
; Check if the service is running
|
||||||
SimpleSC::ServiceIsRunning "clash_verge_service"
|
SimpleSC::ServiceIsRunning "koala_clash_service"
|
||||||
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||||
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
Push $0
|
Push $0
|
||||||
${If} $1 == 0
|
${If} $1 == 0
|
||||||
DetailPrint "Restart Clash Verge Service..."
|
DetailPrint "Restart Koala Clash Service..."
|
||||||
SimpleSC::StartService "clash_verge_service" "" 30
|
SimpleSC::StartService "koala_clash_service" "" 30
|
||||||
${EndIf}
|
${EndIf}
|
||||||
${ElseIf} $0 != 0
|
${ElseIf} $0 != 0
|
||||||
Push $0
|
Push $0
|
||||||
@@ -535,35 +535,35 @@ FunctionEnd
|
|||||||
${EndIf}
|
${EndIf}
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
!macro RemoveVergeService
|
!macro RemoveKoalaService
|
||||||
; Check if the service exists
|
; Check if the service exists
|
||||||
SimpleSC::ExistsService "clash_verge_service"
|
SimpleSC::ExistsService "koala_clash_service"
|
||||||
Pop $0 ; 0:service exists;other: service not exists
|
Pop $0 ; 0:service exists;other: service not exists
|
||||||
; Service exists
|
; Service exists
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
Push $0
|
Push $0
|
||||||
; Check if the service is running
|
; Check if the service is running
|
||||||
SimpleSC::ServiceIsRunning "clash_verge_service"
|
SimpleSC::ServiceIsRunning "koala_clash_service"
|
||||||
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||||
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
Push $0
|
Push $0
|
||||||
${If} $1 == 1
|
${If} $1 == 1
|
||||||
DetailPrint "Stop Clash Verge Service..."
|
DetailPrint "Stop Koala Clash Service..."
|
||||||
SimpleSC::StopService "clash_verge_service" 1 30
|
SimpleSC::StopService "koala_clash_service" 1 30
|
||||||
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||||
${If} $0 == 0
|
${If} $0 == 0
|
||||||
DetailPrint "Removing Clash Verge Service..."
|
DetailPrint "Removing Koala Clash Service..."
|
||||||
SimpleSC::RemoveService "clash_verge_service"
|
SimpleSC::RemoveService "koala_clash_service"
|
||||||
${ElseIf} $0 != 0
|
${ElseIf} $0 != 0
|
||||||
Push $0
|
Push $0
|
||||||
SimpleSC::GetErrorMessage
|
SimpleSC::GetErrorMessage
|
||||||
Pop $0
|
Pop $0
|
||||||
MessageBox MB_OK|MB_ICONSTOP "Clash Verge Service Stop Error ($0)"
|
MessageBox MB_OK|MB_ICONSTOP "Koala Clash Service Stop Error ($0)"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
${ElseIf} $1 == 0
|
${ElseIf} $1 == 0
|
||||||
DetailPrint "Removing Clash Verge Service..."
|
DetailPrint "Removing Koala Clash Service..."
|
||||||
SimpleSC::RemoveService "clash_verge_service"
|
SimpleSC::RemoveService "koala_clash_service"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
${ElseIf} $0 != 0
|
${ElseIf} $0 != 0
|
||||||
Push $0
|
Push $0
|
||||||
@@ -764,7 +764,7 @@ Section Install
|
|||||||
SetOutPath $INSTDIR
|
SetOutPath $INSTDIR
|
||||||
nsExec::Exec 'netsh int tcp res'
|
nsExec::Exec 'netsh int tcp res'
|
||||||
!insertmacro CheckIfAppIsRunning
|
!insertmacro CheckIfAppIsRunning
|
||||||
!insertmacro CheckAllVergeProcesses
|
!insertmacro CheckAllKoalaProcesses
|
||||||
|
|
||||||
; 清理自启动注册表项
|
; 清理自启动注册表项
|
||||||
DetailPrint "Cleaning auto-launch registry entries..."
|
DetailPrint "Cleaning auto-launch registry entries..."
|
||||||
@@ -772,32 +772,32 @@ Section Install
|
|||||||
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||||
|
|
||||||
SetRegView 64
|
SetRegView 64
|
||||||
; 清理旧版本的注册表项 (Clash Verge)
|
; 清理旧版本的注册表项 (Koala Clash)
|
||||||
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
|
ReadRegStr $R2 HKCU "$R1" "Koala Clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKCU "$R1" "Clash Verge"
|
DeleteRegValue HKCU "$R1" "Koala Clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
|
ReadRegStr $R2 HKLM "$R1" "Koala Clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKLM "$R1" "Clash Verge"
|
DeleteRegValue HKLM "$R1" "Koala Clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; 清理新版本的注册表项 (clash-verge)
|
; 清理新版本的注册表项 (koala-clash)
|
||||||
ReadRegStr $R2 HKCU "$R1" "clash-verge"
|
ReadRegStr $R2 HKCU "$R1" "koala-clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKCU "$R1" "clash-verge"
|
DeleteRegValue HKCU "$R1" "koala-clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
ReadRegStr $R2 HKLM "$R1" "clash-verge"
|
ReadRegStr $R2 HKLM "$R1" "koala-clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKLM "$R1" "clash-verge"
|
DeleteRegValue HKLM "$R1" "koala-clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; Delete old files before installation
|
; Delete old files before installation
|
||||||
; Delete clash-verge.desktop
|
; Delete koala-clash.desktop
|
||||||
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
IfFileExists "$INSTDIR\Koala Clash.exe" 0 +2
|
||||||
Delete "$INSTDIR\Clash Verge.exe"
|
Delete "$INSTDIR\Koala Clash.exe"
|
||||||
|
|
||||||
; Copy main executable
|
; Copy main executable
|
||||||
File "${MAINBINARYSRCPATH}"
|
File "${MAINBINARYSRCPATH}"
|
||||||
@@ -815,7 +815,7 @@ Section Install
|
|||||||
File /a "/oname={{this}}" "{{@key}}"
|
File /a "/oname={{this}}" "{{@key}}"
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
!insertmacro StartVergeService
|
!insertmacro StartKoalaService
|
||||||
|
|
||||||
; Create uninstaller
|
; Create uninstaller
|
||||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||||
@@ -918,11 +918,11 @@ FunctionEnd
|
|||||||
Section Uninstall
|
Section Uninstall
|
||||||
;删除 window-state.json 文件
|
;删除 window-state.json 文件
|
||||||
SetShellVarContext current
|
SetShellVarContext current
|
||||||
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\window-state.json"
|
Delete "$APPDATA\io.github.koala-clash\window-state.json"
|
||||||
|
|
||||||
!insertmacro CheckIfAppIsRunning
|
!insertmacro CheckIfAppIsRunning
|
||||||
!insertmacro CheckAllVergeProcesses
|
!insertmacro CheckAllKoalaProcesses
|
||||||
!insertmacro RemoveVergeService
|
!insertmacro RemoveKoalaService
|
||||||
|
|
||||||
; 清理自启动注册表项
|
; 清理自启动注册表项
|
||||||
DetailPrint "Cleaning auto-launch registry entries..."
|
DetailPrint "Cleaning auto-launch registry entries..."
|
||||||
@@ -930,26 +930,26 @@ Section Uninstall
|
|||||||
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||||
|
|
||||||
SetRegView 64
|
SetRegView 64
|
||||||
; 清理旧版本的注册表项 (Clash Verge)
|
; 清理旧版本的注册表项 (Koala Clash)
|
||||||
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
|
ReadRegStr $R2 HKCU "$R1" "Koala Clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKCU "$R1" "Clash Verge"
|
DeleteRegValue HKCU "$R1" "Koala Clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
|
ReadRegStr $R2 HKLM "$R1" "Koala Clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKLM "$R1" "Clash Verge"
|
DeleteRegValue HKLM "$R1" "Koala Clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; 清理新版本的注册表项 (clash-verge)
|
; 清理新版本的注册表项 (koala-clash)
|
||||||
ReadRegStr $R2 HKCU "$R1" "clash-verge"
|
ReadRegStr $R2 HKCU "$R1" "koala-clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKCU "$R1" "clash-verge"
|
DeleteRegValue HKCU "$R1" "koala-clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
ReadRegStr $R2 HKLM "$R1" "clash-verge"
|
ReadRegStr $R2 HKLM "$R1" "koala-clash"
|
||||||
${If} $R2 != ""
|
${If} $R2 != ""
|
||||||
DeleteRegValue HKLM "$R1" "clash-verge"
|
DeleteRegValue HKLM "$R1" "koala-clash"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
; Delete the app directory and its content from disk
|
; Delete the app directory and its content from disk
|
||||||
@@ -966,9 +966,9 @@ Section Uninstall
|
|||||||
Delete "$INSTDIR\\{{this}}"
|
Delete "$INSTDIR\\{{this}}"
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
; Delete clash-verge.desktop
|
; Delete koala-clash.desktop
|
||||||
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
IfFileExists "$INSTDIR\Koala Clash.exe" 0 +2
|
||||||
Delete "$INSTDIR\Clash Verge.exe"
|
Delete "$INSTDIR\Koala Clash.exe"
|
||||||
|
|
||||||
; Delete uninstaller
|
; Delete uninstaller
|
||||||
Delete "$INSTDIR\uninstall.exe"
|
Delete "$INSTDIR\uninstall.exe"
|
||||||
@@ -982,20 +982,20 @@ Section Uninstall
|
|||||||
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
|
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
|
||||||
!insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk"
|
!insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk"
|
||||||
; 兼容旧名称快捷方式
|
; 兼容旧名称快捷方式
|
||||||
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\clash-verge.lnk"
|
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\koala-clash.lnk"
|
||||||
!insertmacro UnpinShortcut "$DESKTOP\clash-verge.lnk"
|
!insertmacro UnpinShortcut "$DESKTOP\koala-clash.lnk"
|
||||||
|
|
||||||
; Remove start menu shortcut
|
; Remove start menu shortcut
|
||||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
||||||
Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
|
Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
|
||||||
; 兼容旧名称快捷方式
|
; 兼容旧名称快捷方式
|
||||||
Delete "$SMPROGRAMS\$AppStartMenuFolder\clash-verge.lnk"
|
Delete "$SMPROGRAMS\$AppStartMenuFolder\koala-clash.lnk"
|
||||||
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
|
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
|
||||||
|
|
||||||
; Remove desktop shortcuts
|
; Remove desktop shortcuts
|
||||||
Delete "$DESKTOP\${PRODUCTNAME}.lnk"
|
Delete "$DESKTOP\${PRODUCTNAME}.lnk"
|
||||||
; 兼容旧名称快捷方式
|
; 兼容旧名称快捷方式
|
||||||
Delete "$DESKTOP\clash-verge.lnk"
|
Delete "$DESKTOP\koala-clash.lnk"
|
||||||
|
|
||||||
; Remove registry information for add/remove programs
|
; Remove registry information for add/remove programs
|
||||||
!if "${INSTALLMODE}" == "both"
|
!if "${INSTALLMODE}" == "both"
|
||||||
@@ -1017,7 +1017,7 @@ Section Uninstall
|
|||||||
|
|
||||||
;删除 window-state.json 文件
|
;删除 window-state.json 文件
|
||||||
SetShellVarContext current
|
SetShellVarContext current
|
||||||
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\window-state.json"
|
Delete "$APPDATA\io.github.koala-clash\window-state.json"
|
||||||
|
|
||||||
${GetOptions} $CMDLINE "/P" $R0
|
${GetOptions} $CMDLINE "/P" $R0
|
||||||
IfErrors +2 0
|
IfErrors +2 0
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
|
|||||||
Ok(icon_path.to_string_lossy().to_string())
|
Ok(icon_path.to_string_lossy().to_string())
|
||||||
} else {
|
} else {
|
||||||
let _ = std::fs::remove_file(&temp_path);
|
let _ = std::fs::remove_file(&temp_path);
|
||||||
Err(format!("下载的内容不是有效图片: {url}"))
|
Err(format!("Downloaded content is not a valid image: {url}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,15 +209,17 @@ pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
|
|||||||
/// 通知UI已准备就绪
|
/// 通知UI已准备就绪
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn notify_ui_ready() -> CmdResult<()> {
|
pub fn notify_ui_ready() -> CmdResult<()> {
|
||||||
log::info!(target: "app", "前端UI已准备就绪");
|
log::info!(target: "app", "Frontend UI is ready");
|
||||||
crate::utils::resolve::mark_ui_ready();
|
crate::utils::resolve::mark_ui_ready();
|
||||||
|
// Flush any pending messages queued while UI was not ready (e.g. minimized to tray)
|
||||||
|
crate::core::handle::Handle::global().flush_ui_pending_messages();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UI加载阶段
|
/// UI加载阶段
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn update_ui_stage(stage: String) -> CmdResult<()> {
|
pub fn update_ui_stage(stage: String) -> CmdResult<()> {
|
||||||
log::info!(target: "app", "UI加载阶段更新: {stage}");
|
log::info!(target: "app", "UI loading stage updated: {stage}");
|
||||||
|
|
||||||
use crate::utils::resolve::UiReadyStage;
|
use crate::utils::resolve::UiReadyStage;
|
||||||
|
|
||||||
@@ -228,8 +230,8 @@ pub fn update_ui_stage(stage: String) -> CmdResult<()> {
|
|||||||
"ResourcesLoaded" => UiReadyStage::ResourcesLoaded,
|
"ResourcesLoaded" => UiReadyStage::ResourcesLoaded,
|
||||||
"Ready" => UiReadyStage::Ready,
|
"Ready" => UiReadyStage::Ready,
|
||||||
_ => {
|
_ => {
|
||||||
log::warn!(target: "app", "未知的UI加载阶段: {stage}");
|
log::warn!(target: "app", "Unknown UI loading stage: {stage}");
|
||||||
return Err(format!("未知的UI加载阶段: {stage}"));
|
return Err(format!("Unknown UI loading stage: {stage}"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -240,7 +242,7 @@ pub fn update_ui_stage(stage: String) -> CmdResult<()> {
|
|||||||
/// 重置UI就绪状态
|
/// 重置UI就绪状态
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn reset_ui_ready_state() -> CmdResult<()> {
|
pub fn reset_ui_ready_state() -> CmdResult<()> {
|
||||||
log::info!(target: "app", "重置UI就绪状态");
|
log::info!(target: "app", "Reset UI ready state");
|
||||||
crate::utils::resolve::reset_ui_ready();
|
crate::utils::resolve::reset_ui_ready();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use serde_yaml::Mapping;
|
|||||||
/// get the system proxy
|
/// get the system proxy
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_sys_proxy() -> CmdResult<Mapping> {
|
pub async fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||||
log::debug!(target: "app", "异步获取系统代理配置");
|
log::debug!(target: "app", "Asynchronously getting system proxy configuration");
|
||||||
|
|
||||||
let current = AsyncProxyQuery::get_system_proxy().await;
|
let current = AsyncProxyQuery::get_system_proxy().await;
|
||||||
|
|
||||||
@@ -19,14 +19,14 @@ pub async fn get_sys_proxy() -> CmdResult<Mapping> {
|
|||||||
);
|
);
|
||||||
map.insert("bypass".into(), current.bypass.into());
|
map.insert("bypass".into(), current.bypass.into());
|
||||||
|
|
||||||
log::debug!(target: "app", "返回系统代理配置: enable={}, {}:{}", current.enable, current.host, current.port);
|
log::debug!(target: "app", "Return system proxy configuration: enable={}, {}:{}", current.enable, current.host, current.port);
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取自动代理配置
|
/// 获取自动代理配置
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_auto_proxy() -> CmdResult<Mapping> {
|
pub async fn get_auto_proxy() -> CmdResult<Mapping> {
|
||||||
log::debug!(target: "app", "开始获取自动代理配置(事件驱动)");
|
log::debug!(target: "app", "Start retrieving auto proxy configuration (event-driven)");
|
||||||
|
|
||||||
let proxy_manager = EventDrivenProxyManager::global();
|
let proxy_manager = EventDrivenProxyManager::global();
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ pub async fn get_auto_proxy() -> CmdResult<Mapping> {
|
|||||||
map.insert("enable".into(), current.enable.into());
|
map.insert("enable".into(), current.enable.into());
|
||||||
map.insert("url".into(), current.url.clone().into());
|
map.insert("url".into(), current.url.clone().into());
|
||||||
|
|
||||||
log::debug!(target: "app", "返回自动代理配置(缓存): enable={}, url={}", current.enable, current.url);
|
log::debug!(target: "app", "Return auto proxy configuration (cached): enable={}, url={}", current.enable, current.url);
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ pub async fn get_proxies() -> CmdResult<serde_json::Value> {
|
|||||||
state.proxies = Box::new(proxies);
|
state.proxies = Box::new(proxies);
|
||||||
state.need_refresh = false;
|
state.need_refresh = false;
|
||||||
}
|
}
|
||||||
log::debug!(target: "app", "proxies刷新成功");
|
log::debug!(target: "app", "Proxies refreshed successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
let proxies = {
|
let proxies = {
|
||||||
@@ -50,7 +50,7 @@ pub async fn force_refresh_proxies() -> CmdResult<serde_json::Value> {
|
|||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
let cmd_proxy_state = app_handle.state::<Mutex<CmdProxyState>>();
|
let cmd_proxy_state = app_handle.state::<Mutex<CmdProxyState>>();
|
||||||
|
|
||||||
log::debug!(target: "app", "强制刷新代理缓存");
|
log::debug!(target: "app", "Force refresh proxy cache");
|
||||||
|
|
||||||
let proxies = manager.get_refresh_proxies().await?;
|
let proxies = manager.get_refresh_proxies().await?;
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ pub async fn force_refresh_proxies() -> CmdResult<serde_json::Value> {
|
|||||||
state.last_refresh_time = Instant::now();
|
state.last_refresh_time = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!(target: "app", "强制刷新代理缓存完成");
|
log::debug!(target: "app", "Force refresh proxy cache completed");
|
||||||
Ok(proxies)
|
Ok(proxies)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
|
|||||||
state.providers_proxies = Box::new(providers);
|
state.providers_proxies = Box::new(providers);
|
||||||
state.need_refresh = false;
|
state.need_refresh = false;
|
||||||
}
|
}
|
||||||
log::debug!(target: "app", "providers_proxies刷新成功");
|
log::debug!(target: "app", "providers_proxies refreshed successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
let providers_proxies = {
|
let providers_proxies = {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
|
|||||||
wrap_err!(fs::write(&file_path, original_content))?;
|
wrap_err!(fs::write(&file_path, original_content))?;
|
||||||
// 发送合并文件专用错误通知
|
// 发送合并文件专用错误通知
|
||||||
let result = (false, error_msg.clone());
|
let result = (false, error_msg.clone());
|
||||||
crate::cmd::validate::handle_yaml_validation_notice(&result, "合并配置文件");
|
crate::cmd::validate::handle_yaml_validation_notice(&result, "Merge config file");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -133,17 +133,17 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
|
|||||||
|| (!file_path_str.ends_with(".js") && !is_script_error)
|
|| (!file_path_str.ends_with(".js") && !is_script_error)
|
||||||
{
|
{
|
||||||
// 普通YAML错误使用YAML通知处理
|
// 普通YAML错误使用YAML通知处理
|
||||||
log::info!(target: "app", "[cmd配置save] YAML配置文件验证失败,发送通知");
|
log::info!(target: "app", "[cmd config save] YAML config file validation failed, sending notification");
|
||||||
let result = (false, error_msg.clone());
|
let result = (false, error_msg.clone());
|
||||||
crate::cmd::validate::handle_yaml_validation_notice(&result, "YAML配置文件");
|
crate::cmd::validate::handle_yaml_validation_notice(&result, "YAML config file");
|
||||||
} else if is_script_error {
|
} else if is_script_error {
|
||||||
// 脚本错误使用专门的通知处理
|
// 脚本错误使用专门的通知处理
|
||||||
log::info!(target: "app", "[cmd配置save] 脚本文件验证失败,发送通知");
|
log::info!(target: "app", "[cmd config save] Script file validation failed, sending notification");
|
||||||
let result = (false, error_msg.clone());
|
let result = (false, error_msg.clone());
|
||||||
crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件");
|
crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件");
|
||||||
} else {
|
} else {
|
||||||
// 普通配置错误使用一般通知
|
// 普通配置错误使用一般通知
|
||||||
log::info!(target: "app", "[cmd配置save] 其他类型验证失败,发送一般通知");
|
log::info!(target: "app", "[cmd config save] Other validation failure type, sending general notification");
|
||||||
handle::Handle::notice_message("config_validate::error", &error_msg);
|
handle::Handle::notice_message("config_validate::error", &error_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
|
|||||||
error,
|
error,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"[cmd配置save] 验证过程发生错误: {}",
|
"[cmd config save] Error occurred during validation: {}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
// 恢复原始配置文件
|
// 恢复原始配置文件
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
|
|||||||
warn,
|
warn,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"{} 验证失败: {}",
|
"{} validation failed: {}",
|
||||||
file_type,
|
file_type,
|
||||||
error_msg
|
error_msg
|
||||||
);
|
);
|
||||||
@@ -43,14 +43,14 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
|
|||||||
/// 验证指定脚本文件
|
/// 验证指定脚本文件
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
|
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
|
||||||
logging!(info, Type::Config, true, "验证脚本文件: {}", file_path);
|
logging!(info, Type::Config, true, "Validating script file: {}", file_path);
|
||||||
|
|
||||||
match CoreManager::global()
|
match CoreManager::global()
|
||||||
.validate_config_file(&file_path, None)
|
.validate_config_file(&file_path, None)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
handle_script_validation_notice(&result, "脚本文件");
|
handle_script_validation_notice(&result, "Script file");
|
||||||
Ok(result.0) // 返回验证结果布尔值
|
Ok(result.0) // 返回验证结果布尔值
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -129,7 +129,7 @@ pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
|
|||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"[通知] 发送通知: status={}, msg={}",
|
"[Notice] Sending notice: status={}, msg={}",
|
||||||
status,
|
status,
|
||||||
error_msg
|
error_msg
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,12 +20,7 @@ impl IClashTemp {
|
|||||||
map.insert(key.clone(), template.0.get(key).unwrap().clone());
|
map.insert(key.clone(), template.0.get(key).unwrap().clone());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 确保 secret 字段存在且不为空
|
// Allow empty secret - user may want to disable authentication
|
||||||
if let Some(Value::String(s)) = map.get_mut("secret") {
|
|
||||||
if s.is_empty() {
|
|
||||||
*s = "set-your-secret".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self(Self::guard(map))
|
Self(Self::guard(map))
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -42,7 +37,7 @@ impl IClashTemp {
|
|||||||
tun.insert("enable".into(), false.into());
|
tun.insert("enable".into(), false.into());
|
||||||
tun.insert("stack".into(), "gvisor".into());
|
tun.insert("stack".into(), "gvisor".into());
|
||||||
tun.insert("auto-route".into(), true.into());
|
tun.insert("auto-route".into(), true.into());
|
||||||
tun.insert("strict-route".into(), false.into());
|
tun.insert("strict-route".into(), true.into());
|
||||||
tun.insert("auto-detect-interface".into(), true.into());
|
tun.insert("auto-detect-interface".into(), true.into());
|
||||||
tun.insert("dns-hijack".into(), vec!["any:53"].into());
|
tun.insert("dns-hijack".into(), vec!["any:53"].into());
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
@@ -87,7 +82,13 @@ impl IClashTemp {
|
|||||||
let mixed_port = Self::guard_mixed_port(&config);
|
let mixed_port = Self::guard_mixed_port(&config);
|
||||||
let socks_port = Self::guard_socks_port(&config);
|
let socks_port = Self::guard_socks_port(&config);
|
||||||
let port = Self::guard_port(&config);
|
let port = Self::guard_port(&config);
|
||||||
let ctrl = Self::guard_server_ctrl(&config);
|
|
||||||
|
// Only set external-controller if it doesn't exist or is invalid
|
||||||
|
// Don't overwrite valid user-configured values
|
||||||
|
if !config.contains_key("external-controller") {
|
||||||
|
config.insert("external-controller".into(), "127.0.0.1:9097".into());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
config.insert("redir-port".into(), redir_port.into());
|
config.insert("redir-port".into(), redir_port.into());
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -95,7 +96,6 @@ impl IClashTemp {
|
|||||||
config.insert("mixed-port".into(), mixed_port.into());
|
config.insert("mixed-port".into(), mixed_port.into());
|
||||||
config.insert("socks-port".into(), socks_port.into());
|
config.insert("socks-port".into(), socks_port.into());
|
||||||
config.insert("port".into(), port.into());
|
config.insert("port".into(), port.into());
|
||||||
config.insert("external-controller".into(), ctrl.into());
|
|
||||||
|
|
||||||
// 强制覆盖 external-controller-cors 字段,允许本地和 tauri 前端
|
// 强制覆盖 external-controller-cors 字段,允许本地和 tauri 前端
|
||||||
let mut cors_map = Mapping::new();
|
let mut cors_map = Mapping::new();
|
||||||
@@ -129,7 +129,7 @@ impl IClashTemp {
|
|||||||
help::save_yaml(
|
help::save_yaml(
|
||||||
&dirs::clash_path()?,
|
&dirs::clash_path()?,
|
||||||
&self.0,
|
&self.0,
|
||||||
Some("# Generated by Clash Verge"),
|
Some("# Generated by Koala Clash"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use once_cell::sync::OnceCell;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
|
pub const RUNTIME_CONFIG: &str = "koala-clash.yaml";
|
||||||
pub const CHECK_CONFIG: &str = "clash-verge-check.yaml";
|
pub const CHECK_CONFIG: &str = "koala-clash-check.yaml";
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
clash_config: Draft<Box<IClashTemp>>,
|
clash_config: Draft<Box<IClashTemp>>,
|
||||||
@@ -69,9 +69,9 @@ impl Config {
|
|||||||
}
|
}
|
||||||
// 生成运行时配置
|
// 生成运行时配置
|
||||||
if let Err(err) = Self::generate().await {
|
if let Err(err) = Self::generate().await {
|
||||||
logging!(error, Type::Config, true, "生成运行时配置失败: {}", err);
|
logging!(error, Type::Config, true, "Failed to generate runtime config: {}", err);
|
||||||
} else {
|
} else {
|
||||||
logging!(info, Type::Config, true, "生成运行时配置成功");
|
logging!(info, Type::Config, true, "Runtime config generated successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成运行时配置文件并验证
|
// 生成运行时配置文件并验证
|
||||||
@@ -79,7 +79,7 @@ impl Config {
|
|||||||
|
|
||||||
let validation_result = if config_result.is_ok() {
|
let validation_result = if config_result.is_ok() {
|
||||||
// 验证配置文件
|
// 验证配置文件
|
||||||
logging!(info, Type::Config, true, "开始验证配置");
|
logging!(info, Type::Config, true, "Starting config validation");
|
||||||
|
|
||||||
match CoreManager::global().validate_config().await {
|
match CoreManager::global().validate_config().await {
|
||||||
Ok((is_valid, error_msg)) => {
|
Ok((is_valid, error_msg)) => {
|
||||||
@@ -88,7 +88,7 @@ impl Config {
|
|||||||
warn,
|
warn,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"[首次启动] 配置验证失败,使用默认最小配置启动: {}",
|
"[First launch] Config validation failed, starting with minimal default config: {}",
|
||||||
error_msg
|
error_msg
|
||||||
);
|
);
|
||||||
CoreManager::global()
|
CoreManager::global()
|
||||||
@@ -96,12 +96,12 @@ impl Config {
|
|||||||
.await?;
|
.await?;
|
||||||
Some(("config_validate::boot_error", error_msg))
|
Some(("config_validate::boot_error", error_msg))
|
||||||
} else {
|
} else {
|
||||||
logging!(info, Type::Config, true, "配置验证成功");
|
logging!(info, Type::Config, true, "Config validation succeeded");
|
||||||
Some(("config_validate::success", String::new()))
|
Some(("config_validate::success", String::new()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(warn, Type::Config, true, "验证进程执行失败: {}", err);
|
logging!(warn, Type::Config, true, "Validation process execution failed: {}", err);
|
||||||
CoreManager::global()
|
CoreManager::global()
|
||||||
.use_default_config("config_validate::process_terminated", "")
|
.use_default_config("config_validate::process_terminated", "")
|
||||||
.await?;
|
.await?;
|
||||||
@@ -109,7 +109,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logging!(warn, Type::Config, true, "生成配置文件失败,使用默认配置");
|
logging!(warn, Type::Config, true, "Failed to generate config file; using default config");
|
||||||
CoreManager::global()
|
CoreManager::global()
|
||||||
.use_default_config("config_validate::error", "")
|
.use_default_config("config_validate::error", "")
|
||||||
.await?;
|
.await?;
|
||||||
@@ -141,7 +141,7 @@ impl Config {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(anyhow!("failed to get runtime config"))?;
|
.ok_or(anyhow!("failed to get runtime config"))?;
|
||||||
|
|
||||||
help::save_yaml(&path, &config, Some("# Generated by Clash Verge"))?;
|
help::save_yaml(&path, &config, Some("# Generated by Koala Clash"))?;
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ macro_rules! draft_define {
|
|||||||
|
|
||||||
impl Draft<Box<$id>> {
|
impl Draft<Box<$id>> {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn data(&self) -> MappedMutexGuard<Box<$id>> {
|
pub fn data(&self) -> MappedMutexGuard<'_, Box<$id>> {
|
||||||
MutexGuard::map(self.inner.lock(), |guard| &mut guard.0)
|
MutexGuard::map(self.inner.lock(), |guard| &mut guard.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn latest(&self) -> MappedMutexGuard<Box<$id>> {
|
pub fn latest(&self) -> MappedMutexGuard<'_, Box<$id>> {
|
||||||
MutexGuard::map(self.inner.lock(), |inner| {
|
MutexGuard::map(self.inner.lock(), |inner| {
|
||||||
if inner.1.is_none() {
|
if inner.1.is_none() {
|
||||||
&mut inner.0
|
&mut inner.0
|
||||||
@@ -33,7 +33,7 @@ macro_rules! draft_define {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draft(&self) -> MappedMutexGuard<Box<$id>> {
|
pub fn draft(&self) -> MappedMutexGuard<'_, Box<$id>> {
|
||||||
MutexGuard::map(self.inner.lock(), |inner| {
|
MutexGuard::map(self.inner.lock(), |inner| {
|
||||||
if inner.1.is_none() {
|
if inner.1.is_none() {
|
||||||
inner.1 = Some(inner.0.clone());
|
inner.1 = Some(inner.0.clone());
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ use crate::utils::{
|
|||||||
tmpl,
|
tmpl,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::{fs, time::Duration};
|
use std::{fs, time::Duration};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use super::Config;
|
use super::Config;
|
||||||
|
|
||||||
@@ -53,6 +55,18 @@ pub struct PrfItem {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub home: Option<String>,
|
pub home: Option<String>,
|
||||||
|
|
||||||
|
/// profile support url
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub support_url: Option<String>,
|
||||||
|
|
||||||
|
/// profile announce
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub announce: Option<String>,
|
||||||
|
|
||||||
|
/// profile announce url
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub announce_url: Option<String>,
|
||||||
|
|
||||||
/// the file data
|
/// the file data
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub file_data: Option<String>,
|
pub file_data: Option<String>,
|
||||||
@@ -113,6 +127,12 @@ pub struct PrfOption {
|
|||||||
pub proxies: Option<String>,
|
pub proxies: Option<String>,
|
||||||
|
|
||||||
pub groups: Option<String>,
|
pub groups: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub use_hwid: Option<bool>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub update_always: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrfOption {
|
impl PrfOption {
|
||||||
@@ -132,6 +152,8 @@ impl PrfOption {
|
|||||||
a.proxies = b.proxies.or(a.proxies);
|
a.proxies = b.proxies.or(a.proxies);
|
||||||
a.groups = b.groups.or(a.groups);
|
a.groups = b.groups.or(a.groups);
|
||||||
a.timeout_seconds = b.timeout_seconds.or(a.timeout_seconds);
|
a.timeout_seconds = b.timeout_seconds.or(a.timeout_seconds);
|
||||||
|
a.use_hwid = b.use_hwid.or(a.use_hwid);
|
||||||
|
a.update_always = b.update_always.or(a.update_always);
|
||||||
Some(a)
|
Some(a)
|
||||||
}
|
}
|
||||||
t => t.0.or(t.1),
|
t => t.0.or(t.1),
|
||||||
@@ -230,6 +252,9 @@ impl PrfItem {
|
|||||||
..PrfOption::default()
|
..PrfOption::default()
|
||||||
}),
|
}),
|
||||||
home: None,
|
home: None,
|
||||||
|
support_url: None,
|
||||||
|
announce: None,
|
||||||
|
announce_url: None,
|
||||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||||
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
|
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
|
||||||
})
|
})
|
||||||
@@ -251,6 +276,7 @@ impl PrfItem {
|
|||||||
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
|
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
|
||||||
let update_interval = opt_ref.and_then(|o| o.update_interval);
|
let update_interval = opt_ref.and_then(|o| o.update_interval);
|
||||||
let timeout = opt_ref.and_then(|o| o.timeout_seconds).unwrap_or(20);
|
let timeout = opt_ref.and_then(|o| o.timeout_seconds).unwrap_or(20);
|
||||||
|
let use_hwid = Config::verge().latest().enable_send_hwid.unwrap_or(true);
|
||||||
let mut merge = opt_ref.and_then(|o| o.merge.clone());
|
let mut merge = opt_ref.and_then(|o| o.merge.clone());
|
||||||
let mut script = opt_ref.and_then(|o| o.script.clone());
|
let mut script = opt_ref.and_then(|o| o.script.clone());
|
||||||
let mut rules = opt_ref.and_then(|o| o.rules.clone());
|
let mut rules = opt_ref.and_then(|o| o.rules.clone());
|
||||||
@@ -274,6 +300,7 @@ impl PrfItem {
|
|||||||
Some(timeout),
|
Some(timeout),
|
||||||
user_agent.clone(),
|
user_agent.clone(),
|
||||||
accept_invalid_certs,
|
accept_invalid_certs,
|
||||||
|
use_hwid,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -291,6 +318,21 @@ impl PrfItem {
|
|||||||
|
|
||||||
let header = resp.headers();
|
let header = resp.headers();
|
||||||
|
|
||||||
|
let mut final_url = url.to_string();
|
||||||
|
|
||||||
|
if let Some(new_domain_value) = header.get("new-sub-domain") {
|
||||||
|
if let Ok(new_domain) = new_domain_value.to_str() {
|
||||||
|
if !new_domain.is_empty() {
|
||||||
|
if let Ok(mut parsed_url) = Url::parse(url) {
|
||||||
|
if parsed_url.set_host(Some(new_domain)).is_ok() {
|
||||||
|
final_url = parsed_url.to_string();
|
||||||
|
log::info!(target: "app", "URL host updated to -> {final_url}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// parse the Subscription UserInfo
|
// parse the Subscription UserInfo
|
||||||
let extra = match header.get("Subscription-Userinfo") {
|
let extra = match header.get("Subscription-Userinfo") {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
@@ -340,6 +382,11 @@ impl PrfItem {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let update_always = match header.get("update-always") {
|
||||||
|
Some(value) => value.to_str().unwrap_or("false").parse::<bool>().ok(),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
let home = match header.get("profile-web-page-url") {
|
let home = match header.get("profile-web-page-url") {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
let str_value = value.to_str().unwrap_or("");
|
let str_value = value.to_str().unwrap_or("");
|
||||||
@@ -348,9 +395,64 @@ impl PrfItem {
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let support_url = match header.get("support-url") {
|
||||||
|
Some(value) => {
|
||||||
|
let str_value = value.to_str().unwrap_or("");
|
||||||
|
Some(str_value.to_string())
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let announce = match header.get("announce") {
|
||||||
|
Some(value) => {
|
||||||
|
let str_value = value.to_str().unwrap_or("");
|
||||||
|
if let Some(b64_data) = str_value.strip_prefix("base64:") {
|
||||||
|
STANDARD
|
||||||
|
.decode(b64_data)
|
||||||
|
.ok()
|
||||||
|
.and_then(|bytes| String::from_utf8(bytes).ok())
|
||||||
|
} else {
|
||||||
|
Some(str_value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(announce_msg) = &announce {
|
||||||
|
let lower_msg = announce_msg.to_lowercase();
|
||||||
|
if lower_msg.contains("device") || lower_msg.contains("устройств") {
|
||||||
|
bail!(announce_msg.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let announce_url = match header.get("announce-url") {
|
||||||
|
Some(value) => {
|
||||||
|
let str_value = value.to_str().unwrap_or("");
|
||||||
|
Some(str_value.to_string())
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile_title = match header.get("profile-title") {
|
||||||
|
Some(value) => {
|
||||||
|
let str_value = value.to_str().unwrap_or("");
|
||||||
|
if let Some(b64_data) = str_value.strip_prefix("base64:") {
|
||||||
|
STANDARD
|
||||||
|
.decode(b64_data)
|
||||||
|
.ok()
|
||||||
|
.and_then(|bytes| String::from_utf8(bytes).ok())
|
||||||
|
} else {
|
||||||
|
Some(str_value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
let uid = help::get_uid("R");
|
let uid = help::get_uid("R");
|
||||||
let file = format!("{uid}.yaml");
|
let file = format!("{uid}.yaml");
|
||||||
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
|
let name = name
|
||||||
|
.or(profile_title)
|
||||||
|
.unwrap_or(filename.unwrap_or("Remote File".into()));
|
||||||
let data = resp.text_with_charset("utf-8").await?;
|
let data = resp.text_with_charset("utf-8").await?;
|
||||||
|
|
||||||
// process the charset "UTF-8 with BOM"
|
// process the charset "UTF-8 with BOM"
|
||||||
@@ -398,19 +500,33 @@ impl PrfItem {
|
|||||||
name: Some(name),
|
name: Some(name),
|
||||||
desc,
|
desc,
|
||||||
file: Some(file),
|
file: Some(file),
|
||||||
url: Some(url.into()),
|
url: Some(final_url),
|
||||||
selected: None,
|
selected: None,
|
||||||
extra,
|
extra,
|
||||||
option: Some(PrfOption {
|
option: Some(PrfOption {
|
||||||
|
user_agent: user_agent.clone(),
|
||||||
|
with_proxy: if with_proxy { Some(true) } else { None },
|
||||||
|
self_proxy: if self_proxy { Some(true) } else { None },
|
||||||
update_interval,
|
update_interval,
|
||||||
|
update_always,
|
||||||
|
timeout_seconds: Some(timeout),
|
||||||
|
danger_accept_invalid_certs: if accept_invalid_certs {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
merge,
|
merge,
|
||||||
script,
|
script,
|
||||||
rules,
|
rules,
|
||||||
proxies,
|
proxies,
|
||||||
groups,
|
groups,
|
||||||
|
use_hwid: Some(use_hwid),
|
||||||
..PrfOption::default()
|
..PrfOption::default()
|
||||||
}),
|
}),
|
||||||
home,
|
home,
|
||||||
|
support_url,
|
||||||
|
announce,
|
||||||
|
announce_url,
|
||||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||||
file_data: Some(data.into()),
|
file_data: Some(data.into()),
|
||||||
})
|
})
|
||||||
@@ -438,6 +554,9 @@ impl PrfItem {
|
|||||||
extra: None,
|
extra: None,
|
||||||
option: None,
|
option: None,
|
||||||
home: None,
|
home: None,
|
||||||
|
support_url: None,
|
||||||
|
announce: None,
|
||||||
|
announce_url: None,
|
||||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||||
file_data: Some(template),
|
file_data: Some(template),
|
||||||
})
|
})
|
||||||
@@ -460,6 +579,9 @@ impl PrfItem {
|
|||||||
file: Some(file),
|
file: Some(file),
|
||||||
url: None,
|
url: None,
|
||||||
home: None,
|
home: None,
|
||||||
|
support_url: None,
|
||||||
|
announce: None,
|
||||||
|
announce_url: None,
|
||||||
selected: None,
|
selected: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
option: None,
|
option: None,
|
||||||
@@ -481,6 +603,9 @@ impl PrfItem {
|
|||||||
file: Some(file),
|
file: Some(file),
|
||||||
url: None,
|
url: None,
|
||||||
home: None,
|
home: None,
|
||||||
|
support_url: None,
|
||||||
|
announce: None,
|
||||||
|
announce_url: None,
|
||||||
selected: None,
|
selected: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
option: None,
|
option: None,
|
||||||
@@ -502,6 +627,9 @@ impl PrfItem {
|
|||||||
file: Some(file),
|
file: Some(file),
|
||||||
url: None,
|
url: None,
|
||||||
home: None,
|
home: None,
|
||||||
|
support_url: None,
|
||||||
|
announce: None,
|
||||||
|
announce_url: None,
|
||||||
selected: None,
|
selected: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
option: None,
|
option: None,
|
||||||
@@ -523,6 +651,9 @@ impl PrfItem {
|
|||||||
file: Some(file),
|
file: Some(file),
|
||||||
url: None,
|
url: None,
|
||||||
home: None,
|
home: None,
|
||||||
|
support_url: None,
|
||||||
|
announce: None,
|
||||||
|
announce_url: None,
|
||||||
selected: None,
|
selected: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
option: None,
|
option: None,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ impl IProfiles {
|
|||||||
help::save_yaml(
|
help::save_yaml(
|
||||||
&dirs::profiles_path()?,
|
&dirs::profiles_path()?,
|
||||||
self,
|
self,
|
||||||
Some("# Profiles Config for Clash Verge"),
|
Some("# Profiles Config for Koala Clash"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,10 +136,9 @@ impl IProfiles {
|
|||||||
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.current.is_none()
|
if item.itype == Some("remote".to_string()) || item.itype == Some("local".to_string()) {
|
||||||
&& (item.itype == Some("remote".to_string()) || item.itype == Some("local".to_string()))
|
// Always switch current to the newly created remote/local profile
|
||||||
{
|
self.current = uid.clone();
|
||||||
self.current = uid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.items.is_none() {
|
if self.items.is_none() {
|
||||||
@@ -220,6 +219,11 @@ impl IProfiles {
|
|||||||
each.extra = item.extra;
|
each.extra = item.extra;
|
||||||
each.updated = item.updated;
|
each.updated = item.updated;
|
||||||
each.home = item.home;
|
each.home = item.home;
|
||||||
|
each.announce = item.announce;
|
||||||
|
each.announce_url = item.announce_url;
|
||||||
|
each.support_url = item.support_url;
|
||||||
|
each.name = item.name;
|
||||||
|
each.url = item.url;
|
||||||
each.option = PrfOption::merge(each.option.clone(), item.option);
|
each.option = PrfOption::merge(each.option.clone(), item.option);
|
||||||
// save the file data
|
// save the file data
|
||||||
// move the field value after save
|
// move the field value after save
|
||||||
@@ -531,7 +535,7 @@ impl IProfiles {
|
|||||||
if Self::is_profile_file(file_name) {
|
if Self::is_profile_file(file_name) {
|
||||||
// 检查是否为全局扩展文件
|
// 检查是否为全局扩展文件
|
||||||
if protected_files.contains(file_name) {
|
if protected_files.contains(file_name) {
|
||||||
log::debug!(target: "app", "保护全局扩展配置文件: {file_name}");
|
log::debug!(target: "app", "Protect global extension config file: {file_name}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,11 +544,11 @@ impl IProfiles {
|
|||||||
match std::fs::remove_file(&path) {
|
match std::fs::remove_file(&path) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
deleted_files.push(file_name.to_string());
|
deleted_files.push(file_name.to_string());
|
||||||
log::info!(target: "app", "已清理冗余文件: {file_name}");
|
log::info!(target: "app", "Cleaned up redundant file: {file_name}");
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
failed_deletions.push(format!("{file_name}: {e}"));
|
failed_deletions.push(format!("{file_name}: {e}"));
|
||||||
log::warn!(target: "app", "清理文件失败: {file_name} - {e}");
|
log::warn!(target: "app", "Failed to clean file: {file_name} - {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -674,14 +678,14 @@ impl IProfiles {
|
|||||||
if !result.deleted_files.is_empty() {
|
if !result.deleted_files.is_empty() {
|
||||||
log::info!(
|
log::info!(
|
||||||
target: "app",
|
target: "app",
|
||||||
"自动清理完成,删除了 {} 个冗余文件",
|
"Auto cleanup completed, deleted {} redundant files",
|
||||||
result.deleted_files.len()
|
result.deleted_files.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!(target: "app", "自动清理失败: {e}");
|
log::warn!(target: "app", "Auto cleanup failed: {e}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ pub struct IVerge {
|
|||||||
/// enable dns settings - this controls whether dns_config.yaml is applied
|
/// enable dns settings - this controls whether dns_config.yaml is applied
|
||||||
pub enable_dns_settings: Option<bool>,
|
pub enable_dns_settings: Option<bool>,
|
||||||
|
|
||||||
|
pub enable_send_hwid: Option<bool>,
|
||||||
|
|
||||||
|
pub primary_action: Option<String>,
|
||||||
|
|
||||||
/// always use default bypass
|
/// always use default bypass
|
||||||
pub use_default_bypass: Option<bool>,
|
pub use_default_bypass: Option<bool>,
|
||||||
|
|
||||||
@@ -234,7 +238,7 @@ pub struct IVergeTheme {
|
|||||||
|
|
||||||
impl IVerge {
|
impl IVerge {
|
||||||
/// 有效的clash核心名称
|
/// 有效的clash核心名称
|
||||||
pub const VALID_CLASH_CORES: &'static [&'static str] = &["verge-mihomo", "verge-mihomo-alpha"];
|
pub const VALID_CLASH_CORES: &'static [&'static str] = &["koala-mihomo", "koala-mihomo-alpha"];
|
||||||
|
|
||||||
/// 验证并修正配置文件中的clash_core值
|
/// 验证并修正配置文件中的clash_core值
|
||||||
pub fn validate_and_fix_config() -> Result<()> {
|
pub fn validate_and_fix_config() -> Result<()> {
|
||||||
@@ -253,10 +257,10 @@ impl IVerge {
|
|||||||
warn,
|
warn,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"启动时发现无效的clash_core配置: '{}', 将自动修正为 'verge-mihomo'",
|
"Invalid clash_core config detected at startup: '{}', auto-fixing to 'koala-mihomo'",
|
||||||
core
|
core
|
||||||
);
|
);
|
||||||
config.clash_core = Some("verge-mihomo".to_string());
|
config.clash_core = Some("koala-mihomo".to_string());
|
||||||
needs_fix = true;
|
needs_fix = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -264,21 +268,21 @@ impl IVerge {
|
|||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"启动时发现未配置clash_core, 将设置为默认值 'verge-mihomo'"
|
"clash_core not configured at startup; setting default to 'koala-mihomo'"
|
||||||
);
|
);
|
||||||
config.clash_core = Some("verge-mihomo".to_string());
|
config.clash_core = Some("koala-mihomo".to_string());
|
||||||
needs_fix = true;
|
needs_fix = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修正后保存配置
|
// 修正后保存配置
|
||||||
if needs_fix {
|
if needs_fix {
|
||||||
logging!(info, Type::Config, true, "正在保存修正后的配置文件...");
|
logging!(info, Type::Config, true, "Saving fixed configuration file...");
|
||||||
help::save_yaml(&config_path, &config, Some("# Clash Verge Config"))?;
|
help::save_yaml(&config_path, &config, Some("# Koala Clash Config"))?;
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"配置文件修正完成,需要重新加载配置"
|
"Configuration file fixed; reloading config required"
|
||||||
);
|
);
|
||||||
|
|
||||||
Self::reload_config_after_fix(config)?;
|
Self::reload_config_after_fix(config)?;
|
||||||
@@ -287,7 +291,7 @@ impl IVerge {
|
|||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"clash_core配置验证通过: {:?}",
|
"clash_core config validation passed: {:?}",
|
||||||
config.clash_core
|
config.clash_core
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -317,7 +321,7 @@ impl IVerge {
|
|||||||
pub fn get_valid_clash_core(&self) -> String {
|
pub fn get_valid_clash_core(&self) -> String {
|
||||||
self.clash_core
|
self.clash_core
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| "verge-mihomo".to_string())
|
.unwrap_or_else(|| "koala-mihomo".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_system_language() -> String {
|
fn get_system_language() -> String {
|
||||||
@@ -336,18 +340,17 @@ impl IVerge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
match dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)) {
|
dirs::verge_path()
|
||||||
Ok(config) => config,
|
.and_then(|path| help::read_yaml::<IVerge>(&path))
|
||||||
Err(err) => {
|
.unwrap_or_else(|err| {
|
||||||
log::error!(target: "app", "{err}");
|
log::error!(target: "app", "{err}");
|
||||||
Self::template()
|
Self::template()
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn template() -> Self {
|
pub fn template() -> Self {
|
||||||
Self {
|
Self {
|
||||||
clash_core: Some("verge-mihomo".into()),
|
clash_core: Some("koala-mihomo".into()),
|
||||||
language: Some(Self::get_system_language()),
|
language: Some(Self::get_system_language()),
|
||||||
theme_mode: Some("system".into()),
|
theme_mode: Some("system".into()),
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
@@ -401,6 +404,8 @@ impl IVerge {
|
|||||||
enable_auto_light_weight_mode: Some(false),
|
enable_auto_light_weight_mode: Some(false),
|
||||||
auto_light_weight_minutes: Some(10),
|
auto_light_weight_minutes: Some(10),
|
||||||
enable_dns_settings: Some(false),
|
enable_dns_settings: Some(false),
|
||||||
|
enable_send_hwid: Some(true),
|
||||||
|
primary_action: Some("tun-mode".into()),
|
||||||
home_cards: None,
|
home_cards: None,
|
||||||
service_state: None,
|
service_state: None,
|
||||||
..Self::default()
|
..Self::default()
|
||||||
@@ -409,7 +414,7 @@ impl IVerge {
|
|||||||
|
|
||||||
/// Save IVerge App Config
|
/// Save IVerge App Config
|
||||||
pub fn save_file(&self) -> Result<()> {
|
pub fn save_file(&self) -> Result<()> {
|
||||||
help::save_yaml(&dirs::verge_path()?, &self, Some("# Clash Verge Config"))
|
help::save_yaml(&dirs::verge_path()?, &self, Some("# Koala Clash Config"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// patch verge config
|
/// patch verge config
|
||||||
@@ -489,6 +494,8 @@ impl IVerge {
|
|||||||
patch!(enable_auto_light_weight_mode);
|
patch!(enable_auto_light_weight_mode);
|
||||||
patch!(auto_light_weight_minutes);
|
patch!(auto_light_weight_minutes);
|
||||||
patch!(enable_dns_settings);
|
patch!(enable_dns_settings);
|
||||||
|
patch!(enable_send_hwid);
|
||||||
|
patch!(primary_action);
|
||||||
patch!(home_cards);
|
patch!(home_cards);
|
||||||
patch!(service_state);
|
patch!(service_state);
|
||||||
}
|
}
|
||||||
@@ -584,6 +591,8 @@ pub struct IVergeResponse {
|
|||||||
pub enable_auto_light_weight_mode: Option<bool>,
|
pub enable_auto_light_weight_mode: Option<bool>,
|
||||||
pub auto_light_weight_minutes: Option<u64>,
|
pub auto_light_weight_minutes: Option<u64>,
|
||||||
pub enable_dns_settings: Option<bool>,
|
pub enable_dns_settings: Option<bool>,
|
||||||
|
pub enable_send_hwid: Option<bool>,
|
||||||
|
pub primary_action: Option<String>,
|
||||||
pub home_cards: Option<serde_json::Value>,
|
pub home_cards: Option<serde_json::Value>,
|
||||||
pub enable_hover_jump_navigator: Option<bool>,
|
pub enable_hover_jump_navigator: Option<bool>,
|
||||||
pub service_state: Option<crate::core::service::ServiceState>,
|
pub service_state: Option<crate::core::service::ServiceState>,
|
||||||
@@ -656,6 +665,8 @@ impl From<IVerge> for IVergeResponse {
|
|||||||
enable_auto_light_weight_mode: verge.enable_auto_light_weight_mode,
|
enable_auto_light_weight_mode: verge.enable_auto_light_weight_mode,
|
||||||
auto_light_weight_minutes: verge.auto_light_weight_minutes,
|
auto_light_weight_minutes: verge.auto_light_weight_minutes,
|
||||||
enable_dns_settings: verge.enable_dns_settings,
|
enable_dns_settings: verge.enable_dns_settings,
|
||||||
|
enable_send_hwid: verge.enable_send_hwid,
|
||||||
|
primary_action: verge.primary_action,
|
||||||
home_cards: verge.home_cards,
|
home_cards: verge.home_cards,
|
||||||
enable_hover_jump_navigator: verge.enable_hover_jump_navigator,
|
enable_hover_jump_navigator: verge.enable_hover_jump_navigator,
|
||||||
service_state: verge.service_state,
|
service_state: verge.service_state,
|
||||||
|
|||||||
@@ -39,15 +39,15 @@ impl AsyncProxyQuery {
|
|||||||
pub async fn get_auto_proxy() -> AsyncAutoproxy {
|
pub async fn get_auto_proxy() -> AsyncAutoproxy {
|
||||||
match timeout(Duration::from_secs(3), Self::get_auto_proxy_impl()).await {
|
match timeout(Duration::from_secs(3), Self::get_auto_proxy_impl()).await {
|
||||||
Ok(Ok(proxy)) => {
|
Ok(Ok(proxy)) => {
|
||||||
log::debug!(target: "app", "异步获取自动代理成功: enable={}, url={}", proxy.enable, proxy.url);
|
log::debug!(target: "app", "Async auto proxy fetch succeeded: enable={}, url={}", proxy.enable, proxy.url);
|
||||||
proxy
|
proxy
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
log::warn!(target: "app", "异步获取自动代理失败: {e}");
|
log::warn!(target: "app", "Async auto proxy fetch failed: {e}");
|
||||||
AsyncAutoproxy::default()
|
AsyncAutoproxy::default()
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::warn!(target: "app", "异步获取自动代理超时");
|
log::warn!(target: "app", "Async auto proxy fetch timed out");
|
||||||
AsyncAutoproxy::default()
|
AsyncAutoproxy::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,15 +57,15 @@ impl AsyncProxyQuery {
|
|||||||
pub async fn get_system_proxy() -> AsyncSysproxy {
|
pub async fn get_system_proxy() -> AsyncSysproxy {
|
||||||
match timeout(Duration::from_secs(3), Self::get_system_proxy_impl()).await {
|
match timeout(Duration::from_secs(3), Self::get_system_proxy_impl()).await {
|
||||||
Ok(Ok(proxy)) => {
|
Ok(Ok(proxy)) => {
|
||||||
log::debug!(target: "app", "异步获取系统代理成功: enable={}, {}:{}", proxy.enable, proxy.host, proxy.port);
|
log::debug!(target: "app", "Async system proxy fetch succeeded: enable={}, {}:{}", proxy.enable, proxy.host, proxy.port);
|
||||||
proxy
|
proxy
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
log::warn!(target: "app", "异步获取系统代理失败: {e}");
|
log::warn!(target: "app", "Async system proxy fetch failed: {e}");
|
||||||
AsyncSysproxy::default()
|
AsyncSysproxy::default()
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::warn!(target: "app", "异步获取系统代理超时");
|
log::warn!(target: "app", "Async system proxy fetch timed out");
|
||||||
AsyncSysproxy::default()
|
AsyncSysproxy::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ impl AsyncProxyQuery {
|
|||||||
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
|
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
|
||||||
|
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
log::debug!(target: "app", "无法打开注册表项");
|
log::debug!(target: "app", "Unable to open registry key");
|
||||||
return Ok(AsyncAutoproxy::default());
|
return Ok(AsyncAutoproxy::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ impl AsyncProxyQuery {
|
|||||||
.position(|&x| x == 0)
|
.position(|&x| x == 0)
|
||||||
.unwrap_or(url_buffer.len());
|
.unwrap_or(url_buffer.len());
|
||||||
pac_url = String::from_utf16_lossy(&url_buffer[..end_pos]);
|
pac_url = String::from_utf16_lossy(&url_buffer[..end_pos]);
|
||||||
log::debug!(target: "app", "从注册表读取到PAC URL: {}", pac_url);
|
log::debug!(target: "app", "Read PAC URL from registry: {}", pac_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 检查自动检测设置是否启用
|
// 2. 检查自动检测设置是否启用
|
||||||
@@ -148,7 +148,7 @@ impl AsyncProxyQuery {
|
|||||||
|| (detect_query_result == 0 && detect_value_type == REG_DWORD && auto_detect != 0);
|
|| (detect_query_result == 0 && detect_value_type == REG_DWORD && auto_detect != 0);
|
||||||
|
|
||||||
if pac_enabled {
|
if pac_enabled {
|
||||||
log::debug!(target: "app", "PAC配置启用: URL={}, AutoDetect={}", pac_url, auto_detect);
|
log::debug!(target: "app", "PAC configuration enabled: URL={}, AutoDetect={}", pac_url, auto_detect);
|
||||||
|
|
||||||
if pac_url.is_empty() && auto_detect != 0 {
|
if pac_url.is_empty() && auto_detect != 0 {
|
||||||
pac_url = "auto-detect".to_string();
|
pac_url = "auto-detect".to_string();
|
||||||
@@ -159,7 +159,7 @@ impl AsyncProxyQuery {
|
|||||||
url: pac_url,
|
url: pac_url,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
log::debug!(target: "app", "PAC配置未启用");
|
log::debug!(target: "app", "PAC configuration not enabled");
|
||||||
Ok(AsyncAutoproxy::default())
|
Ok(AsyncAutoproxy::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ impl AsyncProxyQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!(target: "app", "解析结果: pac_enabled={pac_enabled}, pac_url={pac_url}");
|
log::debug!(target: "app", "Parse result: pac_enabled={pac_enabled}, pac_url={pac_url}");
|
||||||
|
|
||||||
Ok(AsyncAutoproxy {
|
Ok(AsyncAutoproxy {
|
||||||
enable: pac_enabled && !pac_url.is_empty(),
|
enable: pac_enabled && !pac_url.is_empty(),
|
||||||
@@ -361,7 +361,7 @@ impl AsyncProxyQuery {
|
|||||||
(proxy_server, 8080)
|
(proxy_server, 8080)
|
||||||
};
|
};
|
||||||
|
|
||||||
log::debug!(target: "app", "从注册表读取到代理设置: {}:{}, bypass: {}", host, port, bypass_list);
|
log::debug!(target: "app", "Read proxy settings from registry: {}:{}, bypass: {}", host, port, bypass_list);
|
||||||
|
|
||||||
Ok(AsyncSysproxy {
|
Ok(AsyncSysproxy {
|
||||||
enable: true,
|
enable: true,
|
||||||
@@ -518,7 +518,7 @@ impl AsyncProxyQuery {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if host.is_empty() {
|
if host.is_empty() {
|
||||||
return Err(anyhow!("无效的代理URL"));
|
return Err(anyhow!("Invalid proxy URL"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(AsyncSysproxy {
|
Ok(AsyncSysproxy {
|
||||||
|
|||||||
@@ -108,11 +108,11 @@ impl WebDavClient {
|
|||||||
reqwest::Client::builder()
|
reqwest::Client::builder()
|
||||||
.danger_accept_invalid_certs(true)
|
.danger_accept_invalid_certs(true)
|
||||||
.timeout(Duration::from_secs(op.timeout()))
|
.timeout(Duration::from_secs(op.timeout()))
|
||||||
.user_agent(format!("clash-verge/{APP_VERSION} ({OS} WebDAV-Client)"))
|
.user_agent(format!("koala-clash/{APP_VERSION} ({OS} WebDAV-Client)"))
|
||||||
.redirect(reqwest::redirect::Policy::custom(|attempt| {
|
.redirect(reqwest::redirect::Policy::custom(|attempt| {
|
||||||
// 允许所有请求类型的重定向,包括PUT
|
// 允许所有请求类型的重定向,包括PUT
|
||||||
if attempt.previous().len() >= 5 {
|
if attempt.previous().len() >= 5 {
|
||||||
attempt.error("重定向次数过多")
|
attempt.error("Too many redirects")
|
||||||
} else {
|
} else {
|
||||||
attempt.follow()
|
attempt.follow()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ impl CoreManager {
|
|||||||
warn,
|
warn,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"无法读取文件以检测类型: {}, 错误: {}",
|
"Failed to read file to detect type: {}, error: {}",
|
||||||
path,
|
path,
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
@@ -130,7 +130,7 @@ impl CoreManager {
|
|||||||
debug,
|
debug,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"无法确定文件类型,默认当作YAML处理: {}",
|
"Unable to determine file type, defaulting to YAML handling: {}",
|
||||||
path
|
path
|
||||||
);
|
);
|
||||||
Ok(false)
|
Ok(false)
|
||||||
@@ -146,14 +146,19 @@ impl CoreManager {
|
|||||||
help::save_yaml(
|
help::save_yaml(
|
||||||
&runtime_path,
|
&runtime_path,
|
||||||
&Config::clash().latest().0,
|
&Config::clash().latest().0,
|
||||||
Some("# Clash Verge Runtime"),
|
Some("# Koala Clash Runtime"),
|
||||||
)?;
|
)?;
|
||||||
handle::Handle::notice_message(msg_type, msg_content);
|
handle::Handle::notice_message(msg_type, msg_content);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// 验证运行时配置
|
/// 验证运行时配置
|
||||||
pub async fn validate_config(&self) -> Result<(bool, String)> {
|
pub async fn validate_config(&self) -> Result<(bool, String)> {
|
||||||
logging!(info, Type::Config, true, "生成临时配置文件用于验证");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Generate temporary config file for validation"
|
||||||
|
);
|
||||||
let config_path = Config::generate_file(ConfigType::Check)?;
|
let config_path = Config::generate_file(ConfigType::Check)?;
|
||||||
let config_path = dirs::path_to_str(&config_path)?;
|
let config_path = dirs::path_to_str(&config_path)?;
|
||||||
self.validate_config_internal(config_path).await
|
self.validate_config_internal(config_path).await
|
||||||
@@ -166,7 +171,12 @@ impl CoreManager {
|
|||||||
) -> Result<(bool, String)> {
|
) -> Result<(bool, String)> {
|
||||||
// 检查程序是否正在退出,如果是则跳过验证
|
// 检查程序是否正在退出,如果是则跳过验证
|
||||||
if handle::Handle::global().is_exiting() {
|
if handle::Handle::global().is_exiting() {
|
||||||
logging!(info, Type::Core, true, "应用正在退出,跳过验证");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"App is exiting, skipping validation"
|
||||||
|
);
|
||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +193,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"检测到Merge文件,仅进行语法检查: {}",
|
"Detected merge file, performing syntax check only: {}",
|
||||||
config_path
|
config_path
|
||||||
);
|
);
|
||||||
return self.validate_file_syntax(config_path).await;
|
return self.validate_file_syntax(config_path).await;
|
||||||
@@ -201,7 +211,7 @@ impl CoreManager {
|
|||||||
warn,
|
warn,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"无法确定文件类型: {}, 错误: {}",
|
"Unable to determine file type: {}, error: {}",
|
||||||
config_path,
|
config_path,
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
@@ -215,7 +225,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"检测到脚本文件,使用JavaScript验证: {}",
|
"Detected script file, validating with JavaScript: {}",
|
||||||
config_path
|
config_path
|
||||||
);
|
);
|
||||||
return self.validate_script_file(config_path).await;
|
return self.validate_script_file(config_path).await;
|
||||||
@@ -226,7 +236,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"使用Clash内核验证配置文件: {}",
|
"Validating config file with Clash core: {}",
|
||||||
config_path
|
config_path
|
||||||
);
|
);
|
||||||
self.validate_config_internal(config_path).await
|
self.validate_config_internal(config_path).await
|
||||||
@@ -235,7 +245,12 @@ impl CoreManager {
|
|||||||
async fn validate_config_internal(&self, config_path: &str) -> Result<(bool, String)> {
|
async fn validate_config_internal(&self, config_path: &str) -> Result<(bool, String)> {
|
||||||
// 检查程序是否正在退出,如果是则跳过验证
|
// 检查程序是否正在退出,如果是则跳过验证
|
||||||
if handle::Handle::global().is_exiting() {
|
if handle::Handle::global().is_exiting() {
|
||||||
logging!(info, Type::Core, true, "应用正在退出,跳过验证");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"App is exiting, skipping validation"
|
||||||
|
);
|
||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,17 +258,23 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
true,
|
true,
|
||||||
"开始验证配置文件: {}",
|
"Starting validation for config file: {}",
|
||||||
config_path
|
config_path
|
||||||
);
|
);
|
||||||
|
|
||||||
let clash_core = Config::verge().latest().get_valid_clash_core();
|
let clash_core = Config::verge().latest().get_valid_clash_core();
|
||||||
logging!(info, Type::Config, true, "使用内核: {}", clash_core);
|
logging!(info, Type::Config, true, "Using core: {}", clash_core);
|
||||||
|
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
let app_dir = dirs::app_home_dir()?;
|
let app_dir = dirs::app_home_dir()?;
|
||||||
let app_dir_str = dirs::path_to_str(&app_dir)?;
|
let app_dir_str = dirs::path_to_str(&app_dir)?;
|
||||||
logging!(info, Type::Config, true, "验证目录: {}", app_dir_str);
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Validation directory: {}",
|
||||||
|
app_dir_str
|
||||||
|
);
|
||||||
|
|
||||||
// 使用子进程运行clash验证配置
|
// 使用子进程运行clash验证配置
|
||||||
let output = app_handle
|
let output = app_handle
|
||||||
@@ -271,56 +292,84 @@ impl CoreManager {
|
|||||||
let has_error =
|
let has_error =
|
||||||
!output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw));
|
!output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw));
|
||||||
|
|
||||||
logging!(info, Type::Config, true, "-------- 验证结果 --------");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"-------- Validation Result --------"
|
||||||
|
);
|
||||||
|
|
||||||
if !stderr.is_empty() {
|
if !stderr.is_empty() {
|
||||||
logging!(info, Type::Config, true, "stderr输出:\n{}", stderr);
|
logging!(info, Type::Config, true, "stderr output:\n{}", stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_error {
|
if has_error {
|
||||||
logging!(info, Type::Config, true, "发现错误,开始处理错误信息");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Errors found, processing error details"
|
||||||
|
);
|
||||||
let error_msg = if !stdout.is_empty() {
|
let error_msg = if !stdout.is_empty() {
|
||||||
stdout.to_string()
|
stdout.to_string()
|
||||||
} else if !stderr.is_empty() {
|
} else if !stderr.is_empty() {
|
||||||
stderr.to_string()
|
stderr.to_string()
|
||||||
} else if let Some(code) = output.status.code() {
|
} else if let Some(code) = output.status.code() {
|
||||||
format!("验证进程异常退出,退出码: {code}")
|
format!("Validation process exited abnormally, exit code: {code}")
|
||||||
} else {
|
} else {
|
||||||
"验证进程被终止".to_string()
|
"Validation process was terminated".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
logging!(info, Type::Config, true, "-------- 验证结束 --------");
|
logging!(info, Type::Config, true, "-------- Validation End --------");
|
||||||
Ok((false, error_msg)) // 返回错误消息给调用者处理
|
Ok((false, error_msg)) // 返回错误消息给调用者处理
|
||||||
} else {
|
} else {
|
||||||
logging!(info, Type::Config, true, "验证成功");
|
logging!(info, Type::Config, true, "Validation succeeded");
|
||||||
logging!(info, Type::Config, true, "-------- 验证结束 --------");
|
logging!(info, Type::Config, true, "-------- Validation End --------");
|
||||||
Ok((true, String::new()))
|
Ok((true, String::new()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// 只进行文件语法检查,不进行完整验证
|
/// 只进行文件语法检查,不进行完整验证
|
||||||
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
|
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
|
||||||
logging!(info, Type::Config, true, "开始检查文件: {}", config_path);
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Starting file check: {}",
|
||||||
|
config_path
|
||||||
|
);
|
||||||
|
|
||||||
// 读取文件内容
|
// 读取文件内容
|
||||||
let content = match std::fs::read_to_string(config_path) {
|
let content = match std::fs::read_to_string(config_path) {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error_msg = format!("Failed to read file: {err}");
|
let error_msg = format!("Failed to read file: {err}");
|
||||||
logging!(error, Type::Config, true, "无法读取文件: {}", error_msg);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Failed to read file: {}",
|
||||||
|
error_msg
|
||||||
|
);
|
||||||
return Ok((false, error_msg));
|
return Ok((false, error_msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 对YAML文件尝试解析,只检查语法正确性
|
// 对YAML文件尝试解析,只检查语法正确性
|
||||||
logging!(info, Type::Config, true, "进行YAML语法检查");
|
logging!(info, Type::Config, true, "Performing YAML syntax check");
|
||||||
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
|
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Config, true, "YAML语法检查通过");
|
logging!(info, Type::Config, true, "YAML syntax check passed");
|
||||||
Ok((true, String::new()))
|
Ok((true, String::new()))
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// 使用标准化的前缀,以便错误处理函数能正确识别
|
// 使用标准化的前缀,以便错误处理函数能正确识别
|
||||||
let error_msg = format!("YAML syntax error: {err}");
|
let error_msg = format!("YAML syntax error: {err}");
|
||||||
logging!(error, Type::Config, true, "YAML语法错误: {}", error_msg);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"YAML syntax error: {}",
|
||||||
|
error_msg
|
||||||
|
);
|
||||||
Ok((false, error_msg))
|
Ok((false, error_msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,13 +381,19 @@ impl CoreManager {
|
|||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error_msg = format!("Failed to read script file: {err}");
|
let error_msg = format!("Failed to read script file: {err}");
|
||||||
logging!(warn, Type::Config, true, "脚本语法错误: {}", err);
|
logging!(warn, Type::Config, true, "Script syntax error: {}", err);
|
||||||
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
|
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
|
||||||
return Ok((false, error_msg));
|
return Ok((false, error_msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
logging!(debug, Type::Config, true, "验证脚本文件: {}", path);
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Validating script file: {}",
|
||||||
|
path
|
||||||
|
);
|
||||||
|
|
||||||
// 使用boa引擎进行基本语法检查
|
// 使用boa引擎进行基本语法检查
|
||||||
use boa_engine::{Context, Source};
|
use boa_engine::{Context, Source};
|
||||||
@@ -348,7 +403,13 @@ impl CoreManager {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(debug, Type::Config, true, "脚本语法验证通过: {}", path);
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Script syntax validation passed: {}",
|
||||||
|
path
|
||||||
|
);
|
||||||
|
|
||||||
// 检查脚本是否包含main函数
|
// 检查脚本是否包含main函数
|
||||||
if !content.contains("function main")
|
if !content.contains("function main")
|
||||||
@@ -356,7 +417,13 @@ impl CoreManager {
|
|||||||
&& !content.contains("let main")
|
&& !content.contains("let main")
|
||||||
{
|
{
|
||||||
let error_msg = "Script must contain a main function";
|
let error_msg = "Script must contain a main function";
|
||||||
logging!(warn, Type::Config, true, "脚本缺少main函数: {}", path);
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Script missing main function: {}",
|
||||||
|
path
|
||||||
|
);
|
||||||
//handle::Handle::notice_message("config_validate::script_missing_main", error_msg);
|
//handle::Handle::notice_message("config_validate::script_missing_main", error_msg);
|
||||||
return Ok((false, error_msg.to_string()));
|
return Ok((false, error_msg.to_string()));
|
||||||
}
|
}
|
||||||
@@ -365,7 +432,7 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error_msg = format!("Script syntax error: {err}");
|
let error_msg = format!("Script syntax error: {err}");
|
||||||
logging!(warn, Type::Config, true, "脚本语法错误: {}", err);
|
logging!(warn, Type::Config, true, "Script syntax error: {}", err);
|
||||||
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
|
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
|
||||||
Ok((false, error_msg))
|
Ok((false, error_msg))
|
||||||
}
|
}
|
||||||
@@ -375,33 +442,55 @@ impl CoreManager {
|
|||||||
pub async fn update_config(&self) -> Result<(bool, String)> {
|
pub async fn update_config(&self) -> Result<(bool, String)> {
|
||||||
// 检查程序是否正在退出,如果是则跳过完整验证流程
|
// 检查程序是否正在退出,如果是则跳过完整验证流程
|
||||||
if handle::Handle::global().is_exiting() {
|
if handle::Handle::global().is_exiting() {
|
||||||
logging!(info, Type::Config, true, "应用正在退出,跳过验证");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"App is exiting, skipping validation"
|
||||||
|
);
|
||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(info, Type::Config, true, "开始更新配置");
|
logging!(info, Type::Config, true, "Starting config update");
|
||||||
|
|
||||||
// 1. 先生成新的配置内容
|
// 1. 先生成新的配置内容
|
||||||
logging!(info, Type::Config, true, "生成新的配置内容");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Generating new configuration content"
|
||||||
|
);
|
||||||
Config::generate().await?;
|
Config::generate().await?;
|
||||||
|
|
||||||
// 2. 验证配置
|
// 2. 验证配置
|
||||||
match self.validate_config().await {
|
match self.validate_config().await {
|
||||||
Ok((true, _)) => {
|
Ok((true, _)) => {
|
||||||
logging!(info, Type::Config, true, "配置验证通过");
|
logging!(info, Type::Config, true, "Configuration validation passed");
|
||||||
// 4. 验证通过后,生成正式的运行时配置
|
// 4. 验证通过后,生成正式的运行时配置
|
||||||
logging!(info, Type::Config, true, "生成运行时配置");
|
logging!(info, Type::Config, true, "Generating runtime configuration");
|
||||||
let run_path = Config::generate_file(ConfigType::Run)?;
|
let run_path = Config::generate_file(ConfigType::Run)?;
|
||||||
logging_error!(Type::Config, true, self.put_configs_force(run_path).await);
|
logging_error!(Type::Config, true, self.put_configs_force(run_path).await);
|
||||||
Ok((true, "something".into()))
|
Ok((true, "something".into()))
|
||||||
}
|
}
|
||||||
Ok((false, error_msg)) => {
|
Ok((false, error_msg)) => {
|
||||||
logging!(warn, Type::Config, true, "配置验证失败: {}", error_msg);
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Configuration validation failed: {}",
|
||||||
|
error_msg
|
||||||
|
);
|
||||||
Config::runtime().discard();
|
Config::runtime().discard();
|
||||||
Ok((false, error_msg))
|
Ok((false, error_msg))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(warn, Type::Config, true, "验证过程发生错误: {}", e);
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"Error occurred during validation: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
Config::runtime().discard();
|
Config::runtime().discard();
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
@@ -435,7 +524,12 @@ impl CoreManager {
|
|||||||
impl CoreManager {
|
impl CoreManager {
|
||||||
/// 清理多余的 mihomo 进程
|
/// 清理多余的 mihomo 进程
|
||||||
async fn cleanup_orphaned_mihomo_processes(&self) -> Result<()> {
|
async fn cleanup_orphaned_mihomo_processes(&self) -> Result<()> {
|
||||||
logging!(info, Type::Core, true, "开始清理多余的 mihomo 进程");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Starting cleanup of orphaned mihomo processes"
|
||||||
|
);
|
||||||
|
|
||||||
// 获取当前管理的进程 PID
|
// 获取当前管理的进程 PID
|
||||||
let current_pid = {
|
let current_pid = {
|
||||||
@@ -443,7 +537,7 @@ impl CoreManager {
|
|||||||
child_guard.as_ref().map(|child| child.pid())
|
child_guard.as_ref().map(|child| child.pid())
|
||||||
};
|
};
|
||||||
|
|
||||||
let target_processes = ["verge-mihomo", "verge-mihomo-alpha"];
|
let target_processes = ["koala-mihomo", "koala-mihomo-alpha"];
|
||||||
|
|
||||||
// 并行查找所有目标进程
|
// 并行查找所有目标进程
|
||||||
let mut process_futures = Vec::new();
|
let mut process_futures = Vec::new();
|
||||||
@@ -471,7 +565,7 @@ impl CoreManager {
|
|||||||
debug,
|
debug,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"跳过当前管理的进程: {} (PID: {})",
|
"Skipping currently managed process: {} (PID: {})",
|
||||||
process_name,
|
process_name,
|
||||||
pid
|
pid
|
||||||
);
|
);
|
||||||
@@ -482,13 +576,24 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(debug, Type::Core, true, "查找进程时发生错误: {}", e);
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Error occurred while finding processes: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pids_to_kill.is_empty() {
|
if pids_to_kill.is_empty() {
|
||||||
logging!(debug, Type::Core, true, "未发现多余的 mihomo 进程");
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"No orphaned mihomo processes found"
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,7 +611,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"清理完成,共终止了 {} 个多余的 mihomo 进程",
|
"Cleanup complete, a total of {} redundant mihomo processes terminated",
|
||||||
killed_count
|
killed_count
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -615,7 +720,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"尝试终止进程: {} (PID: {})",
|
"Attempt to terminate process: {} (PID: {})",
|
||||||
process_name,
|
process_name,
|
||||||
pid
|
pid
|
||||||
);
|
);
|
||||||
@@ -662,7 +767,7 @@ impl CoreManager {
|
|||||||
warn,
|
warn,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"进程 {} (PID: {}) 终止命令成功但进程仍在运行",
|
"Process {} (PID: {}) Termination command successful, but process still running",
|
||||||
process_name,
|
process_name,
|
||||||
pid
|
pid
|
||||||
);
|
);
|
||||||
@@ -672,7 +777,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"成功终止进程: {} (PID: {})",
|
"Successfully terminated process: {} (PID: {})",
|
||||||
process_name,
|
process_name,
|
||||||
pid
|
pid
|
||||||
);
|
);
|
||||||
@@ -683,7 +788,7 @@ impl CoreManager {
|
|||||||
warn,
|
warn,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"无法终止进程: {} (PID: {})",
|
"Unable to terminate process: {} (PID: {})",
|
||||||
process_name,
|
process_name,
|
||||||
pid
|
pid
|
||||||
);
|
);
|
||||||
@@ -837,19 +942,29 @@ impl CoreManager {
|
|||||||
// 当服务安装失败时的回退逻辑
|
// 当服务安装失败时的回退逻辑
|
||||||
async fn attempt_service_init(&self) -> Result<()> {
|
async fn attempt_service_init(&self) -> Result<()> {
|
||||||
if service::check_service_needs_reinstall().await {
|
if service::check_service_needs_reinstall().await {
|
||||||
logging!(info, Type::Core, true, "服务版本不匹配或状态异常,执行重装");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Service version mismatch or abnormal status, performing reinstallation"
|
||||||
|
);
|
||||||
if let Err(e) = service::reinstall_service().await {
|
if let Err(e) = service::reinstall_service().await {
|
||||||
logging!(
|
logging!(
|
||||||
warn,
|
warn,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"服务重装失败 during attempt_service_init: {}",
|
"Service reinstallation failed during attempt_service_init: {}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
// 如果重装成功,还需要尝试启动服务
|
// 如果重装成功,还需要尝试启动服务
|
||||||
logging!(info, Type::Core, true, "服务重装成功,尝试启动服务");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Service reinstalled successfully, attempting to start"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = self.start_core_by_service().await {
|
if let Err(e) = self.start_core_by_service().await {
|
||||||
@@ -857,20 +972,20 @@ impl CoreManager {
|
|||||||
warn,
|
warn,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"通过服务启动核心失败 during attempt_service_init: {}",
|
"Failed to start core via service during attempt_service_init: {}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
// 确保 prefer_sidecar 在 start_core_by_service 失败时也被设置
|
// 确保 prefer_sidecar 在 start_core_by_service 失败时也被设置
|
||||||
let mut state = service::ServiceState::get();
|
let mut state = service::ServiceState::get();
|
||||||
if !state.prefer_sidecar {
|
if !state.prefer_sidecar {
|
||||||
state.prefer_sidecar = true;
|
state.prefer_sidecar = true;
|
||||||
state.last_error = Some(format!("通过服务启动核心失败: {e}"));
|
state.last_error = Some(format!("Failed to start core via service: {e}"));
|
||||||
if let Err(save_err) = state.save() {
|
if let Err(save_err) = state.save() {
|
||||||
logging!(
|
logging!(
|
||||||
error,
|
error,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"保存ServiceState失败 (in attempt_service_init/start_core_by_service): {}",
|
"Failed to save ServiceState (in attempt_service_init/start_core_by_service): {}",
|
||||||
save_err
|
save_err
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -889,7 +1004,7 @@ impl CoreManager {
|
|||||||
warn,
|
warn,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"应用初始化时清理多余 mihomo 进程失败: {}",
|
"Failed to clean up unnecessary mihomo processes during application initialization: {}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -901,11 +1016,16 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"服务当前可用或看似可用,尝试通过服务模式启动/重装"
|
"Service currently available or appears available; attempting to start/reinstall via service mode"
|
||||||
);
|
);
|
||||||
match self.attempt_service_init().await {
|
match self.attempt_service_init().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Core, true, "服务模式成功启动核心");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Service mode successfully started core"
|
||||||
|
);
|
||||||
core_started_successfully = true;
|
core_started_successfully = true;
|
||||||
}
|
}
|
||||||
Err(_err) => {
|
Err(_err) => {
|
||||||
@@ -913,7 +1033,7 @@ impl CoreManager {
|
|||||||
warn,
|
warn,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"服务模式启动或重装失败。将尝试Sidecar模式回退。"
|
"Service mode start or reinstall failed. Will attempt Sidecar fallback."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -922,7 +1042,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"服务初始不可用 (is_service_available 调用失败)"
|
"Service initially unavailable (is_service_available call failed)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,7 +1051,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"核心未通过服务模式启动,执行Sidecar回退或首次安装逻辑"
|
"Core not started via service mode; performing Sidecar fallback or first-time install logic"
|
||||||
);
|
);
|
||||||
|
|
||||||
let service_state = service::ServiceState::get();
|
let service_state = service::ServiceState::get();
|
||||||
@@ -941,7 +1061,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"用户偏好Sidecar模式或先前服务启动失败,使用Sidecar模式启动"
|
"User prefers Sidecar mode or previous service start failed; starting with Sidecar mode"
|
||||||
);
|
);
|
||||||
self.start_core_by_sidecar().await?;
|
self.start_core_by_sidecar().await?;
|
||||||
// 如果 sidecar 启动成功,我们可以认为核心初始化流程到此结束
|
// 如果 sidecar 启动成功,我们可以认为核心初始化流程到此结束
|
||||||
@@ -953,26 +1073,41 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"无服务安装记录 (首次运行或状态重置),尝试安装服务"
|
"No service installation record (first run or state reset); attempting to install service"
|
||||||
);
|
);
|
||||||
match service::install_service().await {
|
match service::install_service().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Core, true, "服务安装成功(首次尝试)");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Service installed successfully (first attempt)"
|
||||||
|
);
|
||||||
let mut new_state = service::ServiceState::default();
|
let mut new_state = service::ServiceState::default();
|
||||||
new_state.record_install();
|
new_state.record_install();
|
||||||
new_state.prefer_sidecar = false;
|
new_state.prefer_sidecar = false;
|
||||||
new_state.save()?;
|
new_state.save()?;
|
||||||
|
|
||||||
if service::is_service_available().await.is_ok() {
|
if service::is_service_available().await.is_ok() {
|
||||||
logging!(info, Type::Core, true, "新安装的服务可用,尝试启动");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Newly installed service available; attempting to start"
|
||||||
|
);
|
||||||
if self.start_core_by_service().await.is_ok() {
|
if self.start_core_by_service().await.is_ok() {
|
||||||
logging!(info, Type::Core, true, "新安装的服务启动成功");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Newly installed service started successfully"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
logging!(
|
logging!(
|
||||||
warn,
|
warn,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"新安装的服务启动失败,回退到Sidecar模式"
|
"Newly installed service failed to start; falling back to Sidecar mode"
|
||||||
);
|
);
|
||||||
let mut final_state = service::ServiceState::get();
|
let mut final_state = service::ServiceState::get();
|
||||||
final_state.prefer_sidecar = true;
|
final_state.prefer_sidecar = true;
|
||||||
@@ -986,7 +1121,7 @@ impl CoreManager {
|
|||||||
warn,
|
warn,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"服务安装成功但未能连接/立即可用,回退到Sidecar模式"
|
"Service installed successfully but not connectable/immediately available; falling back to Sidecar mode"
|
||||||
);
|
);
|
||||||
let mut final_state = service::ServiceState::get();
|
let mut final_state = service::ServiceState::get();
|
||||||
final_state.prefer_sidecar = true;
|
final_state.prefer_sidecar = true;
|
||||||
@@ -999,7 +1134,13 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(warn, Type::Core, true, "服务首次安装失败: {}", err);
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Service first-time installation failed: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
let new_state = service::ServiceState {
|
let new_state = service::ServiceState {
|
||||||
last_error: Some(err.to_string()),
|
last_error: Some(err.to_string()),
|
||||||
prefer_sidecar: true,
|
prefer_sidecar: true,
|
||||||
@@ -1017,7 +1158,7 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"有服务安装记录但服务不可用/未启动,强制切换到Sidecar模式"
|
"There is a service installation record, but the service is unavailable/not started. Force switch to Sidecar mode"
|
||||||
);
|
);
|
||||||
let mut final_state = service::ServiceState::get();
|
let mut final_state = service::ServiceState::get();
|
||||||
if !final_state.prefer_sidecar {
|
if !final_state.prefer_sidecar {
|
||||||
@@ -1025,7 +1166,7 @@ impl CoreManager {
|
|||||||
warn,
|
warn,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"prefer_sidecar 为 false,因服务启动失败或不可用而强制设置为 true"
|
"prefer_sidecar is false, but is forced to true due to service startup failure or unavailability"
|
||||||
);
|
);
|
||||||
final_state.prefer_sidecar = true;
|
final_state.prefer_sidecar = true;
|
||||||
final_state.last_error =
|
final_state.last_error =
|
||||||
@@ -1062,7 +1203,12 @@ impl CoreManager {
|
|||||||
if service::check_service_needs_reinstall().await {
|
if service::check_service_needs_reinstall().await {
|
||||||
service::reinstall_service().await?;
|
service::reinstall_service().await?;
|
||||||
}
|
}
|
||||||
logging!(info, Type::Core, true, "服务可用,使用服务模式启动");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Service available; starting in service mode"
|
||||||
|
);
|
||||||
self.start_core_by_service().await?;
|
self.start_core_by_service().await?;
|
||||||
} else {
|
} else {
|
||||||
// 服务不可用,检查用户偏好
|
// 服务不可用,检查用户偏好
|
||||||
@@ -1072,11 +1218,16 @@ impl CoreManager {
|
|||||||
info,
|
info,
|
||||||
Type::Core,
|
Type::Core,
|
||||||
true,
|
true,
|
||||||
"服务不可用,根据用户偏好使用Sidecar模式"
|
"Service unavailable; starting in Sidecar mode per user preference"
|
||||||
);
|
);
|
||||||
self.start_core_by_sidecar().await?;
|
self.start_core_by_sidecar().await?;
|
||||||
} else {
|
} else {
|
||||||
logging!(info, Type::Core, true, "服务不可用,使用Sidecar模式");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
true,
|
||||||
|
"Service unavailable; starting in Sidecar mode"
|
||||||
|
);
|
||||||
self.start_core_by_sidecar().await?;
|
self.start_core_by_sidecar().await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use tokio::sync::{mpsc, oneshot};
|
|||||||
use tokio::time::{sleep, timeout, Duration};
|
use tokio::time::{sleep, timeout, Duration};
|
||||||
|
|
||||||
use crate::config::{Config, IVerge};
|
use crate::config::{Config, IVerge};
|
||||||
use crate::core::async_proxy_query::AsyncProxyQuery;
|
use crate::core::{async_proxy_query::AsyncProxyQuery, handle};
|
||||||
use crate::logging_error;
|
use crate::logging_error;
|
||||||
use crate::utils::logging::Type;
|
use crate::utils::logging::Type;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
@@ -78,7 +78,7 @@ struct QueryRequest {
|
|||||||
response_tx: oneshot::Sender<Autoproxy>,
|
response_tx: oneshot::Sender<Autoproxy>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置结构体移到外部
|
// Configuration structure moved to external
|
||||||
struct ProxyConfig {
|
struct ProxyConfig {
|
||||||
sys_enabled: bool,
|
sys_enabled: bool,
|
||||||
pac_enabled: bool,
|
pac_enabled: bool,
|
||||||
@@ -106,59 +106,59 @@ impl EventDrivenProxyManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取自动代理配置(缓存)
|
/// Get automatic proxy configuration (cached)
|
||||||
pub fn get_auto_proxy_cached(&self) -> Autoproxy {
|
pub fn get_auto_proxy_cached(&self) -> Autoproxy {
|
||||||
self.state.read().auto_proxy.clone()
|
self.state.read().auto_proxy.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 异步获取最新的自动代理配置
|
/// Asynchronously get the latest automatic proxy configuration
|
||||||
pub async fn get_auto_proxy_async(&self) -> Autoproxy {
|
pub async fn get_auto_proxy_async(&self) -> Autoproxy {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
let query = QueryRequest { response_tx: tx };
|
let query = QueryRequest { response_tx: tx };
|
||||||
|
|
||||||
if self.query_sender.send(query).is_err() {
|
if self.query_sender.send(query).is_err() {
|
||||||
log::error!(target: "app", "发送查询请求失败,返回缓存数据");
|
log::error!(target: "app", "Failed to send query request, returning cached data");
|
||||||
return self.get_auto_proxy_cached();
|
return self.get_auto_proxy_cached();
|
||||||
}
|
}
|
||||||
|
|
||||||
match timeout(Duration::from_secs(5), rx).await {
|
match timeout(Duration::from_secs(5), rx).await {
|
||||||
Ok(Ok(result)) => result,
|
Ok(Ok(result)) => result,
|
||||||
_ => {
|
_ => {
|
||||||
log::warn!(target: "app", "查询超时,返回缓存数据");
|
log::warn!(target: "app", "Query timed out, returning cached data");
|
||||||
self.get_auto_proxy_cached()
|
self.get_auto_proxy_cached()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 通知配置变更
|
/// Notify configuration changed
|
||||||
pub fn notify_config_changed(&self) {
|
pub fn notify_config_changed(&self) {
|
||||||
self.send_event(ProxyEvent::ConfigChanged);
|
self.send_event(ProxyEvent::ConfigChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 通知应用启动
|
/// Notify application started
|
||||||
pub fn notify_app_started(&self) {
|
pub fn notify_app_started(&self) {
|
||||||
self.send_event(ProxyEvent::AppStarted);
|
self.send_event(ProxyEvent::AppStarted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 通知应用即将关闭
|
/// Notify application stopping
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn notify_app_stopping(&self) {
|
pub fn notify_app_stopping(&self) {
|
||||||
self.send_event(ProxyEvent::AppStopping);
|
self.send_event(ProxyEvent::AppStopping);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 启用系统代理
|
/// Enable system proxy
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn enable_proxy(&self) {
|
pub fn enable_proxy(&self) {
|
||||||
self.send_event(ProxyEvent::EnableProxy);
|
self.send_event(ProxyEvent::EnableProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 禁用系统代理
|
/// Disable system proxy
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn disable_proxy(&self) {
|
pub fn disable_proxy(&self) {
|
||||||
self.send_event(ProxyEvent::DisableProxy);
|
self.send_event(ProxyEvent::DisableProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 强制检查代理状态
|
/// Force check proxy status
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn force_check(&self) {
|
pub fn force_check(&self) {
|
||||||
self.send_event(ProxyEvent::ForceCheck);
|
self.send_event(ProxyEvent::ForceCheck);
|
||||||
@@ -166,7 +166,7 @@ impl EventDrivenProxyManager {
|
|||||||
|
|
||||||
fn send_event(&self, event: ProxyEvent) {
|
fn send_event(&self, event: ProxyEvent) {
|
||||||
if let Err(e) = self.event_sender.send(event) {
|
if let Err(e) = self.event_sender.send(event) {
|
||||||
log::error!(target: "app", "发送代理事件失败: {e}");
|
log::error!(target: "app", "Failed to send proxy event: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,18 +176,18 @@ impl EventDrivenProxyManager {
|
|||||||
mut query_rx: mpsc::UnboundedReceiver<QueryRequest>,
|
mut query_rx: mpsc::UnboundedReceiver<QueryRequest>,
|
||||||
) {
|
) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
log::info!(target: "app", "事件驱动代理管理器启动");
|
log::info!(target: "app", "Event-driven proxy manager started");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
event = event_rx.recv() => {
|
event = event_rx.recv() => {
|
||||||
match event {
|
match event {
|
||||||
Some(event) => {
|
Some(event) => {
|
||||||
log::debug!(target: "app", "处理代理事件: {event:?}");
|
log::debug!(target: "app", "Handling proxy event: {event:?}");
|
||||||
Self::handle_event(&state, event).await;
|
Self::handle_event(&state, event).await;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::info!(target: "app", "事件通道关闭,代理管理器停止");
|
log::info!(target: "app", "Event channel closed, proxy manager stopped");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ impl EventDrivenProxyManager {
|
|||||||
let _ = query.response_tx.send(result);
|
let _ = query.response_tx.send(result);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::info!(target: "app", "查询通道关闭");
|
log::info!(target: "app", "Query channel closed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,12 @@ impl EventDrivenProxyManager {
|
|||||||
Self::initialize_proxy_state(state).await;
|
Self::initialize_proxy_state(state).await;
|
||||||
}
|
}
|
||||||
ProxyEvent::AppStopping => {
|
ProxyEvent::AppStopping => {
|
||||||
log::info!(target: "app", "清理代理状态");
|
log::info!(target: "app", "Cleaning up proxy state");
|
||||||
|
Self::update_state_timestamp(state, |s| {
|
||||||
|
s.sys_enabled = false;
|
||||||
|
s.pac_enabled = false;
|
||||||
|
s.is_healthy = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +251,7 @@ impl EventDrivenProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn initialize_proxy_state(state: &Arc<RwLock<ProxyState>>) {
|
async fn initialize_proxy_state(state: &Arc<RwLock<ProxyState>>) {
|
||||||
log::info!(target: "app", "初始化代理状态");
|
log::info!(target: "app", "Initializing proxy state");
|
||||||
|
|
||||||
let config = Self::get_proxy_config();
|
let config = Self::get_proxy_config();
|
||||||
let auto_proxy = Self::get_auto_proxy_with_timeout().await;
|
let auto_proxy = Self::get_auto_proxy_with_timeout().await;
|
||||||
@@ -260,11 +265,11 @@ impl EventDrivenProxyManager {
|
|||||||
s.is_healthy = true;
|
s.is_healthy = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
log::info!(target: "app", "代理状态初始化完成: sys={}, pac={}", config.sys_enabled, config.pac_enabled);
|
log::info!(target: "app", "Proxy state initialized: sys={}, pac={}", config.sys_enabled, config.pac_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_proxy_config(state: &Arc<RwLock<ProxyState>>) {
|
async fn update_proxy_config(state: &Arc<RwLock<ProxyState>>) {
|
||||||
log::debug!(target: "app", "更新代理配置");
|
log::debug!(target: "app", "Updating proxy configuration");
|
||||||
|
|
||||||
let config = Self::get_proxy_config();
|
let config = Self::get_proxy_config();
|
||||||
|
|
||||||
@@ -279,6 +284,10 @@ impl EventDrivenProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn check_and_restore_proxy(state: &Arc<RwLock<ProxyState>>) {
|
async fn check_and_restore_proxy(state: &Arc<RwLock<ProxyState>>) {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip system proxy guard check");
|
||||||
|
return;
|
||||||
|
}
|
||||||
let (sys_enabled, pac_enabled) = {
|
let (sys_enabled, pac_enabled) = {
|
||||||
let s = state.read();
|
let s = state.read();
|
||||||
(s.sys_enabled, s.pac_enabled)
|
(s.sys_enabled, s.pac_enabled)
|
||||||
@@ -288,7 +297,7 @@ impl EventDrivenProxyManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!(target: "app", "检查代理状态");
|
log::debug!(target: "app", "Checking proxy status");
|
||||||
|
|
||||||
if pac_enabled {
|
if pac_enabled {
|
||||||
Self::check_and_restore_pac_proxy(state).await;
|
Self::check_and_restore_pac_proxy(state).await;
|
||||||
@@ -298,6 +307,11 @@ impl EventDrivenProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn check_and_restore_pac_proxy(state: &Arc<RwLock<ProxyState>>) {
|
async fn check_and_restore_pac_proxy(state: &Arc<RwLock<ProxyState>>) {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip PAC proxy restore check");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let current = Self::get_auto_proxy_with_timeout().await;
|
let current = Self::get_auto_proxy_with_timeout().await;
|
||||||
let expected = Self::get_expected_pac_config();
|
let expected = Self::get_expected_pac_config();
|
||||||
|
|
||||||
@@ -306,7 +320,7 @@ impl EventDrivenProxyManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if !current.enable || current.url != expected.url {
|
if !current.enable || current.url != expected.url {
|
||||||
log::info!(target: "app", "PAC代理设置异常,正在恢复...");
|
log::info!(target: "app", "PAC proxy setting abnormal, recovering...");
|
||||||
Self::restore_pac_proxy(&expected.url).await;
|
Self::restore_pac_proxy(&expected.url).await;
|
||||||
|
|
||||||
sleep(Duration::from_millis(500)).await;
|
sleep(Duration::from_millis(500)).await;
|
||||||
@@ -320,6 +334,11 @@ impl EventDrivenProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn check_and_restore_sys_proxy(state: &Arc<RwLock<ProxyState>>) {
|
async fn check_and_restore_sys_proxy(state: &Arc<RwLock<ProxyState>>) {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip system proxy restore check");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let current = Self::get_sys_proxy_with_timeout().await;
|
let current = Self::get_sys_proxy_with_timeout().await;
|
||||||
let expected = Self::get_expected_sys_proxy();
|
let expected = Self::get_expected_sys_proxy();
|
||||||
|
|
||||||
@@ -328,7 +347,7 @@ impl EventDrivenProxyManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if !current.enable || current.host != expected.host || current.port != expected.port {
|
if !current.enable || current.host != expected.host || current.port != expected.port {
|
||||||
log::info!(target: "app", "系统代理设置异常,正在恢复...");
|
log::info!(target: "app", "System proxy setting abnormal, recovering...");
|
||||||
Self::restore_sys_proxy(&expected).await;
|
Self::restore_sys_proxy(&expected).await;
|
||||||
|
|
||||||
sleep(Duration::from_millis(500)).await;
|
sleep(Duration::from_millis(500)).await;
|
||||||
@@ -344,7 +363,12 @@ impl EventDrivenProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn enable_system_proxy(state: &Arc<RwLock<ProxyState>>) {
|
async fn enable_system_proxy(state: &Arc<RwLock<ProxyState>>) {
|
||||||
log::info!(target: "app", "启用系统代理");
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip enabling system proxy");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!(target: "app", "Enabling system proxy");
|
||||||
|
|
||||||
let pac_enabled = state.read().pac_enabled;
|
let pac_enabled = state.read().pac_enabled;
|
||||||
|
|
||||||
@@ -360,7 +384,7 @@ impl EventDrivenProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn disable_system_proxy(_state: &Arc<RwLock<ProxyState>>) {
|
async fn disable_system_proxy(_state: &Arc<RwLock<ProxyState>>) {
|
||||||
log::info!(target: "app", "禁用系统代理");
|
log::info!(target: "app", "Disabling system proxy");
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
@@ -373,7 +397,12 @@ impl EventDrivenProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn switch_proxy_mode(state: &Arc<RwLock<ProxyState>>, to_pac: bool) {
|
async fn switch_proxy_mode(state: &Arc<RwLock<ProxyState>>, to_pac: bool) {
|
||||||
log::info!(target: "app", "切换到{}模式", if to_pac { "PAC" } else { "HTTP代理" });
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip proxy mode switch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!(target: "app", "Switching to {} mode", if to_pac { "PAC" } else { "HTTP Proxy" });
|
||||||
|
|
||||||
if to_pac {
|
if to_pac {
|
||||||
let disabled_sys = Sysproxy::default();
|
let disabled_sys = Sysproxy::default();
|
||||||
@@ -396,7 +425,7 @@ impl EventDrivenProxyManager {
|
|||||||
async fn get_auto_proxy_with_timeout() -> Autoproxy {
|
async fn get_auto_proxy_with_timeout() -> Autoproxy {
|
||||||
let async_proxy = AsyncProxyQuery::get_auto_proxy().await;
|
let async_proxy = AsyncProxyQuery::get_auto_proxy().await;
|
||||||
|
|
||||||
// 转换为兼容的结构
|
// Convert to compatible structure
|
||||||
Autoproxy {
|
Autoproxy {
|
||||||
enable: async_proxy.enable,
|
enable: async_proxy.enable,
|
||||||
url: async_proxy.url,
|
url: async_proxy.url,
|
||||||
@@ -406,7 +435,7 @@ impl EventDrivenProxyManager {
|
|||||||
async fn get_sys_proxy_with_timeout() -> Sysproxy {
|
async fn get_sys_proxy_with_timeout() -> Sysproxy {
|
||||||
let async_proxy = AsyncProxyQuery::get_system_proxy().await;
|
let async_proxy = AsyncProxyQuery::get_system_proxy().await;
|
||||||
|
|
||||||
// 转换为兼容的结构
|
// Convert to compatible structure
|
||||||
Sysproxy {
|
Sysproxy {
|
||||||
enable: async_proxy.enable,
|
enable: async_proxy.enable,
|
||||||
host: async_proxy.host,
|
host: async_proxy.host,
|
||||||
@@ -415,7 +444,7 @@ impl EventDrivenProxyManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统一的状态更新方法
|
// Unified state update method
|
||||||
fn update_state_timestamp<F>(state: &Arc<RwLock<ProxyState>>, update_fn: F)
|
fn update_state_timestamp<F>(state: &Arc<RwLock<ProxyState>>, update_fn: F)
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ProxyState),
|
F: FnOnce(&mut ProxyState),
|
||||||
@@ -507,6 +536,10 @@ impl EventDrivenProxyManager {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip PAC proxy restore");
|
||||||
|
return;
|
||||||
|
}
|
||||||
Self::execute_sysproxy_command(&["pac", expected_url]).await;
|
Self::execute_sysproxy_command(&["pac", expected_url]).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,6 +552,10 @@ impl EventDrivenProxyManager {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip system proxy restore");
|
||||||
|
return;
|
||||||
|
}
|
||||||
let address = format!("{}:{}", expected.host, expected.port);
|
let address = format!("{}:{}", expected.host, expected.port);
|
||||||
Self::execute_sysproxy_command(&["global", &address, &expected.bypass]).await;
|
Self::execute_sysproxy_command(&["global", &address, &expected.bypass]).await;
|
||||||
}
|
}
|
||||||
@@ -526,6 +563,15 @@ impl EventDrivenProxyManager {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
async fn execute_sysproxy_command(args: &[&str]) {
|
async fn execute_sysproxy_command(args: &[&str]) {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(
|
||||||
|
target: "app",
|
||||||
|
"Application is exiting, cancel calling sysproxy.exe, args: {:?}",
|
||||||
|
args
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
use crate::utils::dirs;
|
use crate::utils::dirs;
|
||||||
#[allow(unused_imports)] // creation_flags必须
|
#[allow(unused_imports)] // creation_flags必须
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
@@ -534,14 +580,14 @@ impl EventDrivenProxyManager {
|
|||||||
let binary_path = match dirs::service_path() {
|
let binary_path = match dirs::service_path() {
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!(target: "app", "获取服务路径失败: {}", e);
|
log::error!(target: "app", "Failed to get service path: {}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let sysproxy_exe = binary_path.with_file_name("sysproxy.exe");
|
let sysproxy_exe = binary_path.with_file_name("sysproxy.exe");
|
||||||
if !sysproxy_exe.exists() {
|
if !sysproxy_exe.exists() {
|
||||||
log::error!(target: "app", "sysproxy.exe 不存在");
|
log::error!(target: "app", "sysproxy.exe does not exist");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,17 +600,17 @@ impl EventDrivenProxyManager {
|
|||||||
match output {
|
match output {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
log::error!(target: "app", "执行sysproxy命令失败: {:?}", args);
|
log::error!(target: "app", "Failed to execute sysproxy command: {:?}", args);
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
if !stderr.is_empty() {
|
if !stderr.is_empty() {
|
||||||
log::error!(target: "app", "sysproxy错误输出: {}", stderr);
|
log::error!(target: "app", "sysproxy stderr: {}", stderr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::debug!(target: "app", "成功执行sysproxy命令: {:?}", args);
|
log::debug!(target: "app", "Successfully executed sysproxy command: {:?}", args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!(target: "app", "执行sysproxy命令出错: {}", e);
|
log::error!(target: "app", "Error executing sysproxy command: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,6 +258,8 @@ pub struct Handle {
|
|||||||
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
|
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
|
||||||
startup_completed: Arc<RwLock<bool>>,
|
startup_completed: Arc<RwLock<bool>>,
|
||||||
notification_system: Arc<RwLock<Option<NotificationSystem>>>,
|
notification_system: Arc<RwLock<Option<NotificationSystem>>>,
|
||||||
|
/// Messages that should be emitted only after UI is really ready
|
||||||
|
ui_pending_messages: Arc<RwLock<Vec<ErrorMessage>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Handle {
|
impl Default for Handle {
|
||||||
@@ -268,6 +270,7 @@ impl Default for Handle {
|
|||||||
startup_errors: Arc::new(RwLock::new(Vec::new())),
|
startup_errors: Arc::new(RwLock::new(Vec::new())),
|
||||||
startup_completed: Arc::new(RwLock::new(false)),
|
startup_completed: Arc::new(RwLock::new(false)),
|
||||||
notification_system: Arc::new(RwLock::new(Some(NotificationSystem::new()))),
|
notification_system: Arc::new(RwLock::new(Some(NotificationSystem::new()))),
|
||||||
|
ui_pending_messages: Arc::new(RwLock::new(Vec::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,6 +298,10 @@ impl Handle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_window(&self) -> Option<WebviewWindow> {
|
pub fn get_window(&self) -> Option<WebviewWindow> {
|
||||||
|
// If we are in lightweight mode, treat as no window (webview may be destroyed)
|
||||||
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let app_handle = self.app_handle()?;
|
let app_handle = self.app_handle()?;
|
||||||
let window: Option<WebviewWindow> = app_handle.get_webview_window("main");
|
let window: Option<WebviewWindow> = app_handle.get_webview_window("main");
|
||||||
if window.is_none() {
|
if window.is_none() {
|
||||||
@@ -411,12 +418,13 @@ impl Handle {
|
|||||||
let status_str = status.into();
|
let status_str = status.into();
|
||||||
let msg_str = msg.into();
|
let msg_str = msg.into();
|
||||||
|
|
||||||
|
// If startup not completed, buffer messages (existing behavior)
|
||||||
if !*handle.startup_completed.read() {
|
if !*handle.startup_completed.read() {
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Frontend,
|
Type::Frontend,
|
||||||
true,
|
true,
|
||||||
"启动过程中发现错误,加入消息队列: {} - {}",
|
"Error found during startup; queued: {} - {}",
|
||||||
status_str,
|
status_str,
|
||||||
msg_str
|
msg_str
|
||||||
);
|
);
|
||||||
@@ -429,6 +437,23 @@ impl Handle {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If UI is not yet ready (e.g., window re-created from tray or lightweight mode),
|
||||||
|
// buffer messages to emit after UI signals readiness.
|
||||||
|
if !crate::utils::resolve::is_ui_ready() {
|
||||||
|
log::debug!(
|
||||||
|
target: "app",
|
||||||
|
"UI not ready, queue notice message: {} - {}",
|
||||||
|
status_str,
|
||||||
|
msg_str
|
||||||
|
);
|
||||||
|
let mut pendings = handle.ui_pending_messages.write();
|
||||||
|
pendings.push(ErrorMessage {
|
||||||
|
status: status_str,
|
||||||
|
message: msg_str,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if handle.is_exiting() {
|
if handle.is_exiting() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -442,6 +467,34 @@ impl Handle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flush messages buffered while UI was not ready
|
||||||
|
pub fn flush_ui_pending_messages(&self) {
|
||||||
|
let pending = {
|
||||||
|
let mut msgs = self.ui_pending_messages.write();
|
||||||
|
std::mem::take(&mut *msgs)
|
||||||
|
};
|
||||||
|
|
||||||
|
if pending.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_exiting() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let system_opt = self.notification_system.read();
|
||||||
|
if let Some(system) = system_opt.as_ref() {
|
||||||
|
for msg in pending {
|
||||||
|
system.send_event(FrontendEvent::NoticeMessage {
|
||||||
|
status: msg.status,
|
||||||
|
message: msg.message,
|
||||||
|
});
|
||||||
|
// small pacing to avoid flooding immediately on resume
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mark_startup_completed(&self) {
|
pub fn mark_startup_completed(&self) {
|
||||||
{
|
{
|
||||||
let mut completed = self.startup_completed.write();
|
let mut completed = self.startup_completed.write();
|
||||||
@@ -466,7 +519,7 @@ impl Handle {
|
|||||||
info,
|
info,
|
||||||
Type::Frontend,
|
Type::Frontend,
|
||||||
true,
|
true,
|
||||||
"发送{}条启动时累积的错误消息",
|
"Sending {} accumulated startup error messages",
|
||||||
errors.len()
|
errors.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ pub async fn reinstall_service() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error = format!("failed to install service: {}", err);
|
let error = format!("failed to install service: {err}");
|
||||||
service_state.last_error = Some(error.clone());
|
service_state.last_error = Some(error.clone());
|
||||||
service_state.prefer_sidecar = true;
|
service_state.prefer_sidecar = true;
|
||||||
service_state.save()?;
|
service_state.save()?;
|
||||||
@@ -477,7 +477,12 @@ pub async fn reinstall_service() -> Result<()> {
|
|||||||
|
|
||||||
/// 检查服务状态 - 使用IPC通信
|
/// 检查服务状态 - 使用IPC通信
|
||||||
pub async fn check_ipc_service_status() -> Result<JsonResponse> {
|
pub async fn check_ipc_service_status() -> Result<JsonResponse> {
|
||||||
logging!(info, Type::Service, true, "开始检查服务状态 (IPC)");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Starting service status check (IPC)"
|
||||||
|
);
|
||||||
|
|
||||||
// 使用IPC通信
|
// 使用IPC通信
|
||||||
let payload = serde_json::json!({});
|
let payload = serde_json::json!({});
|
||||||
@@ -495,8 +500,16 @@ pub async fn check_ipc_service_status() -> Result<JsonResponse> {
|
|||||||
); */
|
); */
|
||||||
|
|
||||||
if !response.success {
|
if !response.success {
|
||||||
let err_msg = response.error.unwrap_or_else(|| "未知服务错误".to_string());
|
let err_msg = response
|
||||||
logging!(error, Type::Service, true, "服务响应错误: {}", err_msg);
|
.error
|
||||||
|
.unwrap_or_else(|| "Unknown service error".to_string());
|
||||||
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Service response error: {}",
|
||||||
|
err_msg
|
||||||
|
);
|
||||||
bail!(err_msg);
|
bail!(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,7 +529,7 @@ pub async fn check_ipc_service_status() -> Result<JsonResponse> {
|
|||||||
warn,
|
warn,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"解析嵌套的ResponseBody失败: {}; 尝试其他方式",
|
"Failed to parse nested ResponseBody: {}; trying alternative",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
None
|
None
|
||||||
@@ -536,7 +549,7 @@ pub async fn check_ipc_service_status() -> Result<JsonResponse> {
|
|||||||
info,
|
info,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"服务检测成功: code={}, msg={}, data存在={}",
|
"Service check succeeded: code={}, msg={}, data_present={}",
|
||||||
json_response.code,
|
json_response.code,
|
||||||
json_response.msg,
|
json_response.msg,
|
||||||
json_response.data.is_some()
|
json_response.data.is_some()
|
||||||
@@ -550,7 +563,7 @@ pub async fn check_ipc_service_status() -> Result<JsonResponse> {
|
|||||||
info,
|
info,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"服务检测成功: code={}, msg={}",
|
"Service check succeeded: code={}, msg={}",
|
||||||
json_response.code,
|
json_response.code,
|
||||||
json_response.msg
|
json_response.msg
|
||||||
);
|
);
|
||||||
@@ -561,31 +574,42 @@ pub async fn check_ipc_service_status() -> Result<JsonResponse> {
|
|||||||
error,
|
error,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"解析服务响应失败: {}; 原始数据: {:?}",
|
"Failed to parse service response: {}; raw data: {:?}",
|
||||||
e,
|
e,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
bail!("无法解析服务响应数据: {}", e)
|
bail!("Unable to parse service response data: {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
logging!(error, Type::Service, true, "服务响应中没有数据");
|
logging!(error, Type::Service, true, "No data in service response");
|
||||||
bail!("服务响应中没有数据")
|
bail!("No data in service response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "IPC通信失败: {}", e);
|
logging!(
|
||||||
bail!("无法连接到Clash Verge Service: {}", e)
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"IPC communication failed: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
bail!("Unable to connect to Koala Clash Service: {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查服务版本 - 使用IPC通信
|
/// 检查服务版本 - 使用IPC通信
|
||||||
pub async fn check_service_version() -> Result<String> {
|
pub async fn check_service_version() -> Result<String> {
|
||||||
logging!(info, Type::Service, true, "开始检查服务版本 (IPC)");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Starting service version check (IPC)"
|
||||||
|
);
|
||||||
|
|
||||||
let payload = serde_json::json!({});
|
let payload = serde_json::json!({});
|
||||||
// logging!(debug, Type::Service, true, "发送GetVersion请求");
|
// logging!(debug, Type::Service, true, "发送GetVersion请求");
|
||||||
@@ -604,8 +628,14 @@ pub async fn check_service_version() -> Result<String> {
|
|||||||
if !response.success {
|
if !response.success {
|
||||||
let err_msg = response
|
let err_msg = response
|
||||||
.error
|
.error
|
||||||
.unwrap_or_else(|| "获取服务版本失败".to_string());
|
.unwrap_or_else(|| "Failed to get service version".to_string());
|
||||||
logging!(error, Type::Service, true, "获取版本错误: {}", err_msg);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to get service version: {}",
|
||||||
|
err_msg
|
||||||
|
);
|
||||||
bail!(err_msg);
|
bail!(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,7 +648,7 @@ pub async fn check_service_version() -> Result<String> {
|
|||||||
info,
|
info,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"获取到服务版本: {}",
|
"Service version: {}",
|
||||||
version_str
|
version_str
|
||||||
);
|
);
|
||||||
return Ok(version_str.to_string());
|
return Ok(version_str.to_string());
|
||||||
@@ -628,7 +658,7 @@ pub async fn check_service_version() -> Result<String> {
|
|||||||
error,
|
error,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"嵌套数据中没有version字段: {:?}",
|
"Nested data does not contain version field: {:?}",
|
||||||
nested_data
|
nested_data
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -639,7 +669,7 @@ pub async fn check_service_version() -> Result<String> {
|
|||||||
info,
|
info,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"获取到服务版本: {}",
|
"Received service version: {}",
|
||||||
version_response.version
|
version_response.version
|
||||||
);
|
);
|
||||||
return Ok(version_response.version);
|
return Ok(version_response.version);
|
||||||
@@ -649,44 +679,55 @@ pub async fn check_service_version() -> Result<String> {
|
|||||||
error,
|
error,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"解析版本响应失败: {}; 原始数据: {:?}",
|
"Failed to parse version response: {}; raw data: {:?}",
|
||||||
e,
|
e,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
bail!("无法解析服务版本数据: {}", e)
|
bail!("Unable to parse service version data: {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bail!("响应中未找到有效的版本信息")
|
bail!("No valid version information found in response")
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
logging!(error, Type::Service, true, "版本响应中没有数据");
|
logging!(error, Type::Service, true, "No data in version response");
|
||||||
bail!("服务版本响应中没有数据")
|
bail!("No data in service version response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "IPC通信失败: {}", e);
|
logging!(
|
||||||
bail!("无法连接到Clash Verge Service: {}", e)
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"IPC communication failed: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
bail!("Unable to connect to Koala Clash Service: {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查服务是否需要重装
|
/// 检查服务是否需要重装
|
||||||
pub async fn check_service_needs_reinstall() -> bool {
|
pub async fn check_service_needs_reinstall() -> bool {
|
||||||
logging!(info, Type::Service, true, "开始检查服务是否需要重装");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Checking whether service needs reinstallation"
|
||||||
|
);
|
||||||
|
|
||||||
let service_state = ServiceState::get();
|
let service_state = ServiceState::get();
|
||||||
|
|
||||||
if !service_state.can_reinstall() {
|
if !service_state.can_reinstall() {
|
||||||
log::info!(target: "app", "服务重装检查: 处于冷却期或已达最大尝试次数");
|
log::info!(target: "app", "Service reinstall check: in cooldown period or max attempts reached");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查版本和可用性
|
// 检查版本和可用性
|
||||||
match check_service_version().await {
|
match check_service_version().await {
|
||||||
Ok(version) => {
|
Ok(version) => {
|
||||||
log::info!(target: "app", "服务版本检测:当前={version}, 要求={REQUIRED_SERVICE_VERSION}");
|
log::info!(target: "app", "Service version check: current={version}, required={REQUIRED_SERVICE_VERSION}");
|
||||||
/* logging!(
|
/* logging!(
|
||||||
info,
|
info,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
@@ -698,25 +739,36 @@ pub async fn check_service_needs_reinstall() -> bool {
|
|||||||
|
|
||||||
let needs_reinstall = version != REQUIRED_SERVICE_VERSION;
|
let needs_reinstall = version != REQUIRED_SERVICE_VERSION;
|
||||||
if needs_reinstall {
|
if needs_reinstall {
|
||||||
log::warn!(target: "app", "发现服务版本不匹配,需要重装! 当前={version}, 要求={REQUIRED_SERVICE_VERSION}");
|
log::warn!(target: "app", "Service version mismatch detected, reinstallation required! current={version}, required={REQUIRED_SERVICE_VERSION}");
|
||||||
logging!(warn, Type::Service, true, "服务版本不匹配,需要重装");
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Service version mismatch, reinstallation required"
|
||||||
|
);
|
||||||
|
|
||||||
// log::debug!(target: "app", "当前版本字节: {:?}", version.as_bytes());
|
// log::debug!(target: "app", "当前版本字节: {:?}", version.as_bytes());
|
||||||
// log::debug!(target: "app", "要求版本字节: {:?}", REQUIRED_SERVICE_VERSION.as_bytes());
|
// log::debug!(target: "app", "要求版本字节: {:?}", REQUIRED_SERVICE_VERSION.as_bytes());
|
||||||
} else {
|
} else {
|
||||||
log::info!(target: "app", "服务版本匹配,无需重装");
|
log::info!(target: "app", "Service version matches, no reinstallation needed");
|
||||||
// logging!(info, Type::Service, true, "服务版本匹配,无需重装");
|
// logging!(info, Type::Service, true, "服务版本匹配,无需重装");
|
||||||
}
|
}
|
||||||
|
|
||||||
needs_reinstall
|
needs_reinstall
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(error, Type::Service, true, "检查服务版本失败: {}", err);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to check service version: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
|
||||||
// 检查服务是否可用
|
// 检查服务是否可用
|
||||||
match is_service_available().await {
|
match is_service_available().await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
log::info!(target: "app", "服务正在运行但版本检查失败: {err}");
|
log::info!(target: "app", "Service is running but version check failed: {err}");
|
||||||
/* logging!(
|
/* logging!(
|
||||||
info,
|
info,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
@@ -727,7 +779,7 @@ pub async fn check_service_needs_reinstall() -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::info!(target: "app", "服务不可用或未运行,需要重装");
|
log::info!(target: "app", "Service unavailable or not running, reinstallation needed");
|
||||||
// logging!(info, Type::Service, true, "服务不可用或未运行,需要重装");
|
// logging!(info, Type::Service, true, "服务不可用或未运行,需要重装");
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -738,7 +790,7 @@ pub async fn check_service_needs_reinstall() -> bool {
|
|||||||
|
|
||||||
/// 尝试使用服务启动core
|
/// 尝试使用服务启动core
|
||||||
pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result<()> {
|
pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result<()> {
|
||||||
log::info!(target:"app", "尝试使用现有服务启动核心 (IPC)");
|
log::info!(target:"app", "Attempting to start core with existing service (IPC)");
|
||||||
// logging!(info, Type::Service, true, "尝试使用现有服务启动核心");
|
// logging!(info, Type::Service, true, "尝试使用现有服务启动核心");
|
||||||
|
|
||||||
let clash_core = Config::verge().latest().get_valid_clash_core();
|
let clash_core = Config::verge().latest().get_valid_clash_core();
|
||||||
@@ -781,8 +833,16 @@ pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result
|
|||||||
); */
|
); */
|
||||||
|
|
||||||
if !response.success {
|
if !response.success {
|
||||||
let err_msg = response.error.unwrap_or_else(|| "启动核心失败".to_string());
|
let err_msg = response
|
||||||
logging!(error, Type::Service, true, "启动核心失败: {}", err_msg);
|
.error
|
||||||
|
.unwrap_or_else(|| "Failed to start core".to_string());
|
||||||
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to start core: {}",
|
||||||
|
err_msg
|
||||||
|
);
|
||||||
bail!(err_msg);
|
bail!(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -793,127 +853,140 @@ pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result
|
|||||||
let msg = data
|
let msg = data
|
||||||
.get("msg")
|
.get("msg")
|
||||||
.and_then(|m| m.as_str())
|
.and_then(|m| m.as_str())
|
||||||
.unwrap_or("未知错误");
|
.unwrap_or("Unknown error");
|
||||||
|
|
||||||
if code_value != 0 {
|
if code_value != 0 {
|
||||||
logging!(
|
logging!(
|
||||||
error,
|
error,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"启动核心返回错误: code={}, msg={}",
|
"Start core returned error: code={}, msg={}",
|
||||||
code_value,
|
code_value,
|
||||||
msg
|
msg
|
||||||
);
|
);
|
||||||
bail!("启动核心失败: {}", msg);
|
bail!("Failed to start core: {}", msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(info, Type::Service, true, "服务成功启动核心");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Service successfully started core"
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "启动核心IPC通信失败: {}", e);
|
logging!(
|
||||||
bail!("无法连接到Clash Verge Service: {}", e)
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to start core via IPC: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
bail!("Unable to connect to Koala Clash Service: {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以服务启动core
|
// 以服务启动core
|
||||||
pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
|
pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
|
||||||
log::info!(target: "app", "正在尝试通过服务启动核心");
|
log::info!(target: "app", "Attempting to start core via service");
|
||||||
|
|
||||||
// 先检查服务版本,不受冷却期限制
|
// 先检查服务版本,不受冷却期限制
|
||||||
let version_check = match check_service_version().await {
|
let version_check = match check_service_version().await {
|
||||||
Ok(version) => {
|
Ok(version) => {
|
||||||
log::info!(target: "app", "检测到服务版本: {version}, 要求版本: {REQUIRED_SERVICE_VERSION}");
|
log::info!(target: "app", "Detected service version: {version}, required: {REQUIRED_SERVICE_VERSION}");
|
||||||
|
|
||||||
if version.as_bytes() != REQUIRED_SERVICE_VERSION.as_bytes() {
|
if version.as_bytes() != REQUIRED_SERVICE_VERSION.as_bytes() {
|
||||||
log::warn!(target: "app", "服务版本不匹配,需要重装");
|
log::warn!(target: "app", "Service version mismatch, reinstallation required");
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
log::info!(target: "app", "服务版本匹配");
|
log::info!(target: "app", "Service version matches");
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!(target: "app", "无法获取服务版本: {err}");
|
log::warn!(target: "app", "Failed to get service version: {err}");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if version_check && is_service_available().await.is_ok() {
|
if version_check && is_service_available().await.is_ok() {
|
||||||
log::info!(target: "app", "服务已在运行且版本匹配,尝试使用");
|
log::info!(target: "app", "Service is running and version matches, attempting to use it");
|
||||||
return start_with_existing_service(config_file).await;
|
return start_with_existing_service(config_file).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !version_check {
|
if !version_check {
|
||||||
log::info!(target: "app", "服务版本不匹配,尝试重装");
|
log::info!(target: "app", "Service version mismatch, attempting reinstallation");
|
||||||
|
|
||||||
let service_state = ServiceState::get();
|
let service_state = ServiceState::get();
|
||||||
if !service_state.can_reinstall() {
|
if !service_state.can_reinstall() {
|
||||||
log::warn!(target: "app", "由于限制无法重装服务");
|
log::warn!(target: "app", "Cannot reinstall service due to limitations");
|
||||||
if let Ok(()) = start_with_existing_service(config_file).await {
|
if let Ok(()) = start_with_existing_service(config_file).await {
|
||||||
log::info!(target: "app", "尽管版本不匹配,但成功启动了服务");
|
log::info!(target: "app", "Service started successfully despite version mismatch");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
bail!("服务版本不匹配且无法重装,启动失败");
|
bail!("Service version mismatch and cannot reinstall; startup failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(target: "app", "开始重装服务");
|
log::info!(target: "app", "Starting service reinstallation");
|
||||||
if let Err(err) = reinstall_service().await {
|
if let Err(err) = reinstall_service().await {
|
||||||
log::warn!(target: "app", "服务重装失败: {err}");
|
log::warn!(target: "app", "Service reinstallation failed: {err}");
|
||||||
|
|
||||||
log::info!(target: "app", "尝试使用现有服务");
|
log::info!(target: "app", "Attempting to use existing service");
|
||||||
return start_with_existing_service(config_file).await;
|
return start_with_existing_service(config_file).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(target: "app", "服务重装成功,尝试启动");
|
log::info!(target: "app", "Service reinstalled successfully, attempting to start");
|
||||||
return start_with_existing_service(config_file).await;
|
return start_with_existing_service(config_file).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查服务状态
|
// Check service status
|
||||||
match check_ipc_service_status().await {
|
match check_ipc_service_status().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!(target: "app", "服务可用但未运行核心,尝试启动");
|
log::info!(target: "app", "Service available but core not running, attempting to start");
|
||||||
if let Ok(()) = start_with_existing_service(config_file).await {
|
if let Ok(()) = start_with_existing_service(config_file).await {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!(target: "app", "服务检查失败: {err}");
|
log::warn!(target: "app", "Service check failed: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 服务不可用或启动失败,检查是否需要重装
|
// Service unavailable or startup failed, check if reinstallation is needed
|
||||||
if check_service_needs_reinstall().await {
|
if check_service_needs_reinstall().await {
|
||||||
log::info!(target: "app", "服务需要重装");
|
log::info!(target: "app", "Service needs reinstallation");
|
||||||
|
|
||||||
if let Err(err) = reinstall_service().await {
|
if let Err(err) = reinstall_service().await {
|
||||||
log::warn!(target: "app", "服务重装失败: {err}");
|
log::warn!(target: "app", "Service reinstallation failed: {err}");
|
||||||
bail!("Failed to reinstall service: {}", err);
|
bail!("Failed to reinstall service: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(target: "app", "服务重装完成,尝试启动核心");
|
log::info!(target: "app", "Service reinstallation completed, attempting to start core");
|
||||||
start_with_existing_service(config_file).await
|
start_with_existing_service(config_file).await
|
||||||
} else {
|
} else {
|
||||||
log::warn!(target: "app", "服务不可用且无法重装");
|
log::warn!(target: "app", "Service unavailable and cannot be reinstalled");
|
||||||
bail!("Service is not available and cannot be reinstalled at this time")
|
bail!("Service is not available and cannot be reinstalled at this time")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 通过服务停止core
|
/// 通过服务停止core
|
||||||
pub(super) async fn stop_core_by_service() -> Result<()> {
|
pub(super) async fn stop_core_by_service() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "通过服务停止核心 (IPC)");
|
logging!(info, Type::Service, true, "Stopping core via service (IPC)");
|
||||||
|
|
||||||
let payload = serde_json::json!({});
|
let payload = serde_json::json!({});
|
||||||
let response = send_ipc_request(IpcCommand::StopClash, payload)
|
let response = send_ipc_request(IpcCommand::StopClash, payload)
|
||||||
.await
|
.await
|
||||||
.context("无法连接到Clash Verge Service")?;
|
.context("Unable to connect to Koala Clash Service")?;
|
||||||
|
|
||||||
if !response.success {
|
if !response.success {
|
||||||
bail!(response.error.unwrap_or_else(|| "停止核心失败".to_string()));
|
bail!(response
|
||||||
|
.error
|
||||||
|
.unwrap_or_else(|| "Failed to stop core".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(data) = &response.data {
|
if let Some(data) = &response.data {
|
||||||
@@ -922,18 +995,18 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
|
|||||||
let msg = data
|
let msg = data
|
||||||
.get("msg")
|
.get("msg")
|
||||||
.and_then(|m| m.as_str())
|
.and_then(|m| m.as_str())
|
||||||
.unwrap_or("未知错误");
|
.unwrap_or("Unknown error");
|
||||||
|
|
||||||
if code_value != 0 {
|
if code_value != 0 {
|
||||||
logging!(
|
logging!(
|
||||||
error,
|
error,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"停止核心返回错误: code={}, msg={}",
|
"Stop core returned error: code={}, msg={}",
|
||||||
code_value,
|
code_value,
|
||||||
msg
|
msg
|
||||||
);
|
);
|
||||||
bail!("停止核心失败: {}", msg);
|
bail!("Failed to stop core: {}", msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -943,19 +1016,24 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
|
|||||||
|
|
||||||
/// 检查服务是否正在运行
|
/// 检查服务是否正在运行
|
||||||
pub async fn is_service_available() -> Result<()> {
|
pub async fn is_service_available() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "开始检查服务是否正在运行");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Checking whether service is running"
|
||||||
|
);
|
||||||
|
|
||||||
match check_ipc_service_status().await {
|
match check_ipc_service_status().await {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
|
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
|
||||||
logging!(info, Type::Service, true, "服务正在运行");
|
logging!(info, Type::Service, true, "Service is running");
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
logging!(
|
logging!(
|
||||||
warn,
|
warn,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"服务未正常运行: code={}, msg={}",
|
"Service not running normally: code={}, msg={}",
|
||||||
resp.code,
|
resp.code,
|
||||||
resp.msg
|
resp.msg
|
||||||
);
|
);
|
||||||
@@ -963,7 +1041,13 @@ pub async fn is_service_available() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(error, Type::Service, true, "检查服务运行状态失败: {}", err);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to check service running status: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -971,21 +1055,21 @@ pub async fn is_service_available() -> Result<()> {
|
|||||||
|
|
||||||
/// 强制重装服务(UI修复按钮)
|
/// 强制重装服务(UI修复按钮)
|
||||||
pub async fn force_reinstall_service() -> Result<()> {
|
pub async fn force_reinstall_service() -> Result<()> {
|
||||||
log::info!(target: "app", "用户请求强制重装服务");
|
log::info!(target: "app", "User requested forced service reinstallation");
|
||||||
|
|
||||||
let service_state = ServiceState::default();
|
let service_state = ServiceState::default();
|
||||||
service_state.save()?;
|
service_state.save()?;
|
||||||
|
|
||||||
log::info!(target: "app", "已重置服务状态,开始执行重装");
|
log::info!(target: "app", "Service state reset, starting reinstallation");
|
||||||
|
|
||||||
match reinstall_service().await {
|
match reinstall_service().await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
log::info!(target: "app", "服务重装成功");
|
log::info!(target: "app", "Service reinstalled successfully");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!(target: "app", "强制重装服务失败: {err}");
|
log::error!(target: "app", "Forced service reinstallation failed: {err}");
|
||||||
bail!("强制重装服务失败: {}", err)
|
bail!("Forced service reinstallation failed: {}", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use sha2::{Digest, Sha256};
|
|||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
const IPC_SOCKET_NAME: &str = if cfg!(windows) {
|
const IPC_SOCKET_NAME: &str = if cfg!(windows) {
|
||||||
r"\\.\pipe\clash-verge-service"
|
r"\\.\pipe\koala-clash-service"
|
||||||
} else {
|
} else {
|
||||||
"/tmp/clash-verge-service.sock"
|
"/tmp/koala-clash-service.sock"
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定义命令类型
|
// 定义命令类型
|
||||||
@@ -43,7 +43,7 @@ pub struct IpcResponse {
|
|||||||
fn derive_secret_key() -> Vec<u8> {
|
fn derive_secret_key() -> Vec<u8> {
|
||||||
// to do
|
// to do
|
||||||
// 从系统安全存储中获取或从程序安装时生成的密钥文件中读取
|
// 从系统安全存储中获取或从程序安装时生成的密钥文件中读取
|
||||||
let unique_app_id = "clash-verge-app-secret-fuck-me-until-daylight";
|
let unique_app_id = "koala-clash-app-secret-fuck-me-until-daylight";
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(unique_app_id.as_bytes());
|
hasher.update(unique_app_id.as_bytes());
|
||||||
hasher.finalize().to_vec()
|
hasher.finalize().to_vec()
|
||||||
@@ -85,7 +85,7 @@ fn sign_message(message: &str) -> Result<String> {
|
|||||||
type HmacSha256 = Hmac<Sha256>;
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
let secret_key = derive_secret_key();
|
let secret_key = derive_secret_key();
|
||||||
let mut mac = HmacSha256::new_from_slice(&secret_key).context("HMAC初始化失败")?;
|
let mut mac = HmacSha256::new_from_slice(&secret_key).context("Failed to initialize HMAC")?;
|
||||||
|
|
||||||
mac.update(message.as_bytes());
|
mac.update(message.as_bytes());
|
||||||
let result = mac.finalize();
|
let result = mac.finalize();
|
||||||
@@ -129,14 +129,25 @@ pub async fn send_ipc_request(
|
|||||||
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE},
|
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE},
|
||||||
};
|
};
|
||||||
|
|
||||||
logging!(info, Type::Service, true, "正在连接服务 (Windows)...");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Connecting to service (Windows)..."
|
||||||
|
);
|
||||||
|
|
||||||
let command_type = format!("{:?}", command);
|
let command_type = format!("{:?}", command);
|
||||||
|
|
||||||
let request = match create_signed_request(command, payload) {
|
let request = match create_signed_request(command, payload) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "创建签名请求失败: {}", e);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to create signed request: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -147,8 +158,14 @@ pub async fn send_ipc_request(
|
|||||||
let c_pipe_name = match CString::new(IPC_SOCKET_NAME) {
|
let c_pipe_name = match CString::new(IPC_SOCKET_NAME) {
|
||||||
Ok(name) => name,
|
Ok(name) => name,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "创建CString失败: {}", e);
|
logging!(
|
||||||
return Err(anyhow::anyhow!("创建CString失败: {}", e));
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to create CString: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!("Failed to create CString: {}", e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,64 +187,110 @@ pub async fn send_ipc_request(
|
|||||||
error,
|
error,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"连接到服务命名管道失败: {}",
|
"Failed to connect to service named pipe: {}",
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
return Err(anyhow::anyhow!("无法连接到服务命名管道: {}", error));
|
return Err(anyhow::anyhow!("Unable to connect to service named pipe: {}", error));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pipe = unsafe { File::from_raw_handle(handle as RawHandle) };
|
let mut pipe = unsafe { File::from_raw_handle(handle as RawHandle) };
|
||||||
logging!(info, Type::Service, true, "服务连接成功 (Windows)");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Service connection successful (Windows)"
|
||||||
|
);
|
||||||
|
|
||||||
let request_bytes = request_json.as_bytes();
|
let request_bytes = request_json.as_bytes();
|
||||||
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
|
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
|
||||||
|
|
||||||
if let Err(e) = pipe.write_all(&len_bytes) {
|
if let Err(e) = pipe.write_all(&len_bytes) {
|
||||||
logging!(error, Type::Service, true, "写入请求长度失败: {}", e);
|
logging!(
|
||||||
return Err(anyhow::anyhow!("写入请求长度失败: {}", e));
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to write request length: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!("Failed to write request length: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = pipe.write_all(request_bytes) {
|
if let Err(e) = pipe.write_all(request_bytes) {
|
||||||
logging!(error, Type::Service, true, "写入请求内容失败: {}", e);
|
logging!(
|
||||||
return Err(anyhow::anyhow!("写入请求内容失败: {}", e));
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to write request body: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!("Failed to write request body: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = pipe.flush() {
|
if let Err(e) = pipe.flush() {
|
||||||
logging!(error, Type::Service, true, "刷新管道失败: {}", e);
|
logging!(error, Type::Service, true, "Failed to flush pipe: {}", e);
|
||||||
return Err(anyhow::anyhow!("刷新管道失败: {}", e));
|
return Err(anyhow::anyhow!("Failed to flush pipe: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response_len_bytes = [0u8; 4];
|
let mut response_len_bytes = [0u8; 4];
|
||||||
if let Err(e) = pipe.read_exact(&mut response_len_bytes) {
|
if let Err(e) = pipe.read_exact(&mut response_len_bytes) {
|
||||||
logging!(error, Type::Service, true, "读取响应长度失败: {}", e);
|
logging!(
|
||||||
return Err(anyhow::anyhow!("读取响应长度失败: {}", e));
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to read response length: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!("Failed to read response length: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
|
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
|
||||||
|
|
||||||
let mut response_bytes = vec![0u8; response_len];
|
let mut response_bytes = vec![0u8; response_len];
|
||||||
if let Err(e) = pipe.read_exact(&mut response_bytes) {
|
if let Err(e) = pipe.read_exact(&mut response_bytes) {
|
||||||
logging!(error, Type::Service, true, "读取响应内容失败: {}", e);
|
logging!(
|
||||||
return Err(anyhow::anyhow!("读取响应内容失败: {}", e));
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to read response body: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!("Failed to read response body: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: IpcResponse = match serde_json::from_slice::<IpcResponse>(&response_bytes) {
|
let response: IpcResponse = match serde_json::from_slice::<IpcResponse>(&response_bytes) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "服务响应解析失败: {}", e);
|
logging!(
|
||||||
return Err(anyhow::anyhow!("解析响应失败: {}", e));
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to parse service response: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!("Failed to parse response: {}", e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match verify_response_signature(&response) {
|
match verify_response_signature(&response) {
|
||||||
Ok(valid) => {
|
Ok(valid) => {
|
||||||
if !valid {
|
if !valid {
|
||||||
logging!(error, Type::Service, true, "服务响应签名验证失败");
|
logging!(
|
||||||
bail!("服务响应签名验证失败");
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Service response signature verification failed"
|
||||||
|
);
|
||||||
|
bail!("Service response signature verification failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "验证响应签名时出错: {}", e);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Error verifying response signature: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,7 +299,7 @@ pub async fn send_ipc_request(
|
|||||||
info,
|
info,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"IPC请求完成: 命令={}, 成功={}",
|
"IPC request completed: command={}, success={}",
|
||||||
command_type,
|
command_type,
|
||||||
response.success
|
response.success
|
||||||
);
|
);
|
||||||
@@ -255,14 +318,14 @@ pub async fn send_ipc_request(
|
|||||||
) -> Result<IpcResponse> {
|
) -> Result<IpcResponse> {
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
||||||
logging!(info, Type::Service, true, "正在连接服务 (Unix)...");
|
logging!(info, Type::Service, true, "Connecting to service (Unix)...");
|
||||||
|
|
||||||
let command_type = format!("{command:?}");
|
let command_type = format!("{command:?}");
|
||||||
|
|
||||||
let request = match create_signed_request(command, payload) {
|
let request = match create_signed_request(command, payload) {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "创建签名请求失败: {}", e);
|
logging!(error, Type::Service, true, "Failed to create signed request: {}", e);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -271,12 +334,23 @@ pub async fn send_ipc_request(
|
|||||||
|
|
||||||
let mut stream = match UnixStream::connect(IPC_SOCKET_NAME) {
|
let mut stream = match UnixStream::connect(IPC_SOCKET_NAME) {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
logging!(info, Type::Service, true, "服务连接成功 (Unix)");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Service connection successful (Unix)"
|
||||||
|
);
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "连接到Unix套接字失败: {}", e);
|
logging!(
|
||||||
return Err(anyhow::anyhow!("无法连接到服务Unix套接字: {}", e));
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to connect to Unix socket: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!("Unable to connect to service Unix socket: {}", e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -284,46 +358,58 @@ pub async fn send_ipc_request(
|
|||||||
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
|
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
|
||||||
|
|
||||||
if let Err(e) = std::io::Write::write_all(&mut stream, &len_bytes) {
|
if let Err(e) = std::io::Write::write_all(&mut stream, &len_bytes) {
|
||||||
logging!(error, Type::Service, true, "写入请求长度失败: {}", e);
|
logging!(error, Type::Service, true, "Failed to write request length: {}", e);
|
||||||
return Err(anyhow::anyhow!("写入请求长度失败: {}", e));
|
return Err(anyhow::anyhow!("Failed to write request length: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = std::io::Write::write_all(&mut stream, request_bytes) {
|
if let Err(e) = std::io::Write::write_all(&mut stream, request_bytes) {
|
||||||
logging!(error, Type::Service, true, "写入请求内容失败: {}", e);
|
logging!(error, Type::Service, true, "Failed to write request body: {}", e);
|
||||||
return Err(anyhow::anyhow!("写入请求内容失败: {}", e));
|
return Err(anyhow::anyhow!("Failed to write request body: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response_len_bytes = [0u8; 4];
|
let mut response_len_bytes = [0u8; 4];
|
||||||
if let Err(e) = std::io::Read::read_exact(&mut stream, &mut response_len_bytes) {
|
if let Err(e) = std::io::Read::read_exact(&mut stream, &mut response_len_bytes) {
|
||||||
logging!(error, Type::Service, true, "读取响应长度失败: {}", e);
|
logging!(error, Type::Service, true, "Failed to read response length: {}", e);
|
||||||
return Err(anyhow::anyhow!("读取响应长度失败: {}", e));
|
return Err(anyhow::anyhow!("Failed to read response length: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
|
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
|
||||||
|
|
||||||
let mut response_bytes = vec![0u8; response_len];
|
let mut response_bytes = vec![0u8; response_len];
|
||||||
if let Err(e) = std::io::Read::read_exact(&mut stream, &mut response_bytes) {
|
if let Err(e) = std::io::Read::read_exact(&mut stream, &mut response_bytes) {
|
||||||
logging!(error, Type::Service, true, "读取响应内容失败: {}", e);
|
logging!(error, Type::Service, true, "Failed to read response body: {}", e);
|
||||||
return Err(anyhow::anyhow!("读取响应内容失败: {}", e));
|
return Err(anyhow::anyhow!("Failed to read response body: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: IpcResponse = match serde_json::from_slice::<IpcResponse>(&response_bytes) {
|
let response: IpcResponse = match serde_json::from_slice::<IpcResponse>(&response_bytes) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "服务响应解析失败: {}", e,);
|
logging!(
|
||||||
return Err(anyhow::anyhow!("解析响应失败: {}", e));
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Failed to parse service response: {}",
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!("Failed to parse response: {}", e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match verify_response_signature(&response) {
|
match verify_response_signature(&response) {
|
||||||
Ok(valid) => {
|
Ok(valid) => {
|
||||||
if !valid {
|
if !valid {
|
||||||
logging!(error, Type::Service, true, "服务响应签名验证失败");
|
logging!(error, Type::Service, true, "Service response signature verification failed");
|
||||||
bail!("服务响应签名验证失败");
|
bail!("Service response signature verification failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "验证响应签名时出错: {}", e);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"Error verifying response signature: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,7 +418,7 @@ pub async fn send_ipc_request(
|
|||||||
info,
|
info,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"IPC请求完成: 命令={}, 成功={}",
|
"IPC request completed: command={}, success={}",
|
||||||
command_type,
|
command_type,
|
||||||
response.success
|
response.success
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -63,12 +63,16 @@ impl Sysopt {
|
|||||||
let proxy_manager = EventDrivenProxyManager::global();
|
let proxy_manager = EventDrivenProxyManager::global();
|
||||||
proxy_manager.notify_app_started();
|
proxy_manager.notify_app_started();
|
||||||
|
|
||||||
log::info!(target: "app", "已启用事件驱动代理守卫");
|
log::info!(target: "app", "Event-driven proxy guard enabled");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// init the sysproxy
|
/// init the sysproxy
|
||||||
pub async fn update_sysproxy(&self) -> Result<()> {
|
pub async fn update_sysproxy(&self) -> Result<()> {
|
||||||
|
if Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip updating sysproxy");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let _lock = self.update_sysproxy.lock().await;
|
let _lock = self.update_sysproxy.lock().await;
|
||||||
|
|
||||||
let port = Config::verge()
|
let port = Config::verge()
|
||||||
@@ -185,6 +189,10 @@ impl Sysopt {
|
|||||||
|
|
||||||
/// reset the sysproxy
|
/// reset the sysproxy
|
||||||
pub async fn reset_sysproxy(&self) -> Result<()> {
|
pub async fn reset_sysproxy(&self) -> Result<()> {
|
||||||
|
if Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip resetting sysproxy");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let _lock = self.reset_sysproxy.lock().await;
|
let _lock = self.reset_sysproxy.lock().await;
|
||||||
//直接关闭所有代理
|
//直接关闭所有代理
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
@@ -193,7 +201,7 @@ impl Sysopt {
|
|||||||
let mut autoproxy = match Autoproxy::get_auto_proxy() {
|
let mut autoproxy = match Autoproxy::get_auto_proxy() {
|
||||||
Ok(ap) => ap,
|
Ok(ap) => ap,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!(target: "app", "重置代理时获取自动代理配置失败: {e}, 使用默认配置");
|
log::warn!(target: "app", "Failed to get auto proxy config while resetting: {e}, using default config");
|
||||||
Autoproxy {
|
Autoproxy {
|
||||||
enable: false,
|
enable: false,
|
||||||
url: "".to_string(),
|
url: "".to_string(),
|
||||||
@@ -248,14 +256,14 @@ impl Sysopt {
|
|||||||
{
|
{
|
||||||
if is_enable {
|
if is_enable {
|
||||||
if let Err(e) = startup_shortcut::create_shortcut() {
|
if let Err(e) = startup_shortcut::create_shortcut() {
|
||||||
log::error!(target: "app", "创建启动快捷方式失败: {}", e);
|
log::error!(target: "app", "Failed to create startup shortcut: {}", e);
|
||||||
// 如果快捷方式创建失败,回退到原来的方法
|
// 如果快捷方式创建失败,回退到原来的方法
|
||||||
self.try_original_autostart_method(is_enable);
|
self.try_original_autostart_method(is_enable);
|
||||||
} else {
|
} else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
} else if let Err(e) = startup_shortcut::remove_shortcut() {
|
} else if let Err(e) = startup_shortcut::remove_shortcut() {
|
||||||
log::error!(target: "app", "删除启动快捷方式失败: {}", e);
|
log::error!(target: "app", "Failed to remove startup shortcut: {}", e);
|
||||||
self.try_original_autostart_method(is_enable);
|
self.try_original_autostart_method(is_enable);
|
||||||
} else {
|
} else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -290,11 +298,11 @@ impl Sysopt {
|
|||||||
{
|
{
|
||||||
match startup_shortcut::is_shortcut_enabled() {
|
match startup_shortcut::is_shortcut_enabled() {
|
||||||
Ok(enabled) => {
|
Ok(enabled) => {
|
||||||
log::info!(target: "app", "快捷方式自启动状态: {}", enabled);
|
log::info!(target: "app", "Shortcut auto-launch state: {}", enabled);
|
||||||
return Ok(enabled);
|
return Ok(enabled);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!(target: "app", "检查快捷方式失败,尝试原来的方法: {}", e);
|
log::error!(target: "app", "Failed to check shortcut, falling back to original method: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"已注册的定时任务数量: {}",
|
"Registered timer task count: {}",
|
||||||
timer_map.len()
|
timer_map.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"注册了定时任务 - uid={}, interval={}min, task_id={}",
|
"Registered timer task - uid={}, interval={}min, task_id={}",
|
||||||
uid,
|
uid,
|
||||||
task.interval_minutes,
|
task.interval_minutes,
|
||||||
task.task_id
|
task.task_id
|
||||||
@@ -100,7 +100,12 @@ impl Timer {
|
|||||||
let uid = item.uid.as_ref()?;
|
let uid = item.uid.as_ref()?;
|
||||||
|
|
||||||
if interval > 0 && cur_timestamp - updated >= interval * 60 {
|
if interval > 0 && cur_timestamp - updated >= interval * 60 {
|
||||||
logging!(info, Type::Timer, "需要立即更新的配置: uid={}", uid);
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Timer,
|
||||||
|
"Profile requires immediate update: uid={}",
|
||||||
|
uid
|
||||||
|
);
|
||||||
Some(uid.clone())
|
Some(uid.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -116,7 +121,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"需要立即更新的配置数量: {}",
|
"Number of profiles requiring immediate update: {}",
|
||||||
profiles_to_update.len()
|
profiles_to_update.len()
|
||||||
);
|
);
|
||||||
let timer_map = self.timer_map.read();
|
let timer_map = self.timer_map.read();
|
||||||
@@ -124,7 +129,7 @@ impl Timer {
|
|||||||
|
|
||||||
for uid in profiles_to_update {
|
for uid in profiles_to_update {
|
||||||
if let Some(task) = timer_map.get(&uid) {
|
if let Some(task) = timer_map.get(&uid) {
|
||||||
logging!(info, Type::Timer, "立即执行任务: uid={}", uid);
|
logging!(info, Type::Timer, "Executing task immediately: uid={}", uid);
|
||||||
if let Err(e) = delay_timer.advance_task(task.task_id) {
|
if let Err(e) = delay_timer.advance_task(task.task_id) {
|
||||||
logging!(warn, Type::Timer, "Failed to advance task {}: {}", uid, e);
|
logging!(warn, Type::Timer, "Failed to advance task {}: {}", uid, e);
|
||||||
}
|
}
|
||||||
@@ -237,7 +242,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"找到定时更新配置: uid={}, interval={}min",
|
"Found scheduled update config: uid={}, interval={}min",
|
||||||
uid,
|
uid,
|
||||||
interval
|
interval
|
||||||
);
|
);
|
||||||
@@ -251,7 +256,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"生成的定时更新配置数量: {}",
|
"Generated scheduled update config count: {}",
|
||||||
new_map.len()
|
new_map.len()
|
||||||
);
|
);
|
||||||
new_map
|
new_map
|
||||||
@@ -267,7 +272,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"当前 timer_map 大小: {}",
|
"Current timer_map size: {}",
|
||||||
timer_map.len()
|
timer_map.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -279,7 +284,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"定时任务间隔变更: uid={}, 旧={}, 新={}",
|
"Timer task interval changed: uid={}, old={}, new={}",
|
||||||
uid,
|
uid,
|
||||||
task.interval_minutes,
|
task.interval_minutes,
|
||||||
interval
|
interval
|
||||||
@@ -288,12 +293,12 @@ impl Timer {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Task no longer needed
|
// Task no longer needed
|
||||||
logging!(debug, Type::Timer, "定时任务已删除: uid={}", uid);
|
logging!(debug, Type::Timer, "Timer task removed: uid={}", uid);
|
||||||
diff_map.insert(uid.clone(), DiffFlag::Del(task.task_id));
|
diff_map.insert(uid.clone(), DiffFlag::Del(task.task_id));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Task exists with same interval, no change needed
|
// Task exists with same interval, no change needed
|
||||||
logging!(debug, Type::Timer, "定时任务保持不变: uid={}", uid);
|
logging!(debug, Type::Timer, "Timer task unchanged: uid={}", uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,7 +311,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"新增定时任务: uid={}, interval={}min",
|
"Added timer task: uid={}, interval={}min",
|
||||||
uid,
|
uid,
|
||||||
interval
|
interval
|
||||||
);
|
);
|
||||||
@@ -320,7 +325,13 @@ impl Timer {
|
|||||||
*self.timer_count.lock() = next_id;
|
*self.timer_count.lock() = next_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(debug, Type::Timer, "定时任务变更数量: {}", diff_map.len());
|
logging!(debug, Type::Timer, "Number of scheduled task changes: {}", diff_map.len());
|
||||||
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Timer,
|
||||||
|
"Number of timer task changes: {}",
|
||||||
|
diff_map.len()
|
||||||
|
);
|
||||||
diff_map
|
diff_map
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,13 +374,18 @@ impl Timer {
|
|||||||
|
|
||||||
/// Get next update time for a profile
|
/// Get next update time for a profile
|
||||||
pub fn get_next_update_time(&self, uid: &str) -> Option<i64> {
|
pub fn get_next_update_time(&self, uid: &str) -> Option<i64> {
|
||||||
logging!(info, Type::Timer, "获取下次更新时间,uid={}", uid);
|
logging!(info, Type::Timer, "Getting next update time, uid={}", uid);
|
||||||
|
|
||||||
let timer_map = self.timer_map.read();
|
let timer_map = self.timer_map.read();
|
||||||
let task = match timer_map.get(uid) {
|
let task = match timer_map.get(uid) {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => {
|
None => {
|
||||||
logging!(warn, Type::Timer, "找不到对应的定时任务,uid={}", uid);
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Timer,
|
||||||
|
"Corresponding timer task not found, uid={}",
|
||||||
|
uid
|
||||||
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -380,7 +396,7 @@ impl Timer {
|
|||||||
let items = match profiles.get_items() {
|
let items = match profiles.get_items() {
|
||||||
Some(i) => i,
|
Some(i) => i,
|
||||||
None => {
|
None => {
|
||||||
logging!(warn, Type::Timer, "获取配置列表失败");
|
logging!(warn, Type::Timer, "Failed to get profile list");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -388,7 +404,12 @@ impl Timer {
|
|||||||
let profile = match items.iter().find(|item| item.uid.as_deref() == Some(uid)) {
|
let profile = match items.iter().find(|item| item.uid.as_deref() == Some(uid)) {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
None => {
|
None => {
|
||||||
logging!(warn, Type::Timer, "找不到对应的配置,uid={}", uid);
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Timer,
|
||||||
|
"Corresponding profile not found, uid={}",
|
||||||
|
uid
|
||||||
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -401,7 +422,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"计算得到下次更新时间: {}, uid={}",
|
"Calculated next update time: {}, uid={}",
|
||||||
next_time,
|
next_time,
|
||||||
uid
|
uid
|
||||||
);
|
);
|
||||||
@@ -410,7 +431,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
warn,
|
warn,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"更新时间或间隔无效,updated={}, interval={}",
|
"Invalid update time or interval, updated={}, interval={}",
|
||||||
updated,
|
updated,
|
||||||
task.interval_minutes
|
task.interval_minutes
|
||||||
);
|
);
|
||||||
@@ -442,7 +463,7 @@ impl Timer {
|
|||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
"配置 {} 是否为当前激活配置: {}",
|
"Is profile {} currently active: {}",
|
||||||
uid,
|
uid,
|
||||||
is_current
|
is_current
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use tauri::tray::TrayIconBuilder;
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub mod speed_rate;
|
pub mod speed_rate;
|
||||||
use crate::{
|
use crate::{
|
||||||
cmd,
|
|
||||||
config::Config,
|
config::Config,
|
||||||
feat, logging,
|
feat, logging,
|
||||||
module::{lightweight::is_in_lightweight_mode, mihomo::Rate},
|
module::{lightweight::is_in_lightweight_mode, mihomo::Rate},
|
||||||
@@ -46,7 +45,7 @@ fn should_handle_tray_click() -> bool {
|
|||||||
*last_click = now;
|
*last_click = now;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
log::debug!(target: "app", "托盘点击被防抖机制忽略,距离上次点击 {:?}ms",
|
log::debug!(target: "app", "Tray click ignored by debounce; time since last click: {:?}ms",
|
||||||
now.duration_since(*last_click).as_millis());
|
now.duration_since(*last_click).as_millis());
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -185,11 +184,19 @@ impl Tray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&self) -> Result<()> {
|
pub fn init(&self) -> Result<()> {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray initialization");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 更新托盘点击行为
|
/// 更新托盘点击行为
|
||||||
pub fn update_click_behavior(&self) -> Result<()> {
|
pub fn update_click_behavior(&self) -> Result<()> {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray click behavior update");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
let tray_event = { Config::verge().latest().tray_event.clone() };
|
let tray_event = { Config::verge().latest().tray_event.clone() };
|
||||||
let tray_event: String = tray_event.unwrap_or("main_window".into());
|
let tray_event: String = tray_event.unwrap_or("main_window".into());
|
||||||
@@ -203,6 +210,10 @@ impl Tray {
|
|||||||
|
|
||||||
/// 更新托盘菜单
|
/// 更新托盘菜单
|
||||||
pub fn update_menu(&self) -> Result<()> {
|
pub fn update_menu(&self) -> Result<()> {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray menu update");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
// 调整最小更新间隔,确保状态及时刷新
|
// 调整最小更新间隔,确保状态及时刷新
|
||||||
const MIN_UPDATE_INTERVAL: Duration = Duration::from_millis(100);
|
const MIN_UPDATE_INTERVAL: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
@@ -231,7 +242,7 @@ impl Tray {
|
|||||||
let app_handle = match handle::Handle::global().app_handle() {
|
let app_handle = match handle::Handle::global().app_handle() {
|
||||||
Some(handle) => handle,
|
Some(handle) => handle,
|
||||||
None => {
|
None => {
|
||||||
log::warn!(target: "app", "更新托盘菜单失败: app_handle不存在");
|
log::warn!(target: "app", "Failed to update tray menu: app_handle not found");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -279,11 +290,11 @@ impl Tray {
|
|||||||
profile_uid_and_name,
|
profile_uid_and_name,
|
||||||
is_lightweight_mode,
|
is_lightweight_mode,
|
||||||
)?));
|
)?));
|
||||||
log::debug!(target: "app", "托盘菜单更新成功");
|
log::debug!(target: "app", "Tray menu updated successfully");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::warn!(target: "app", "更新托盘菜单失败: 托盘不存在");
|
log::warn!(target: "app", "Failed to update tray menu: tray not found");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,10 +303,14 @@ impl Tray {
|
|||||||
/// 更新托盘图标
|
/// 更新托盘图标
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn update_icon(&self, _rate: Option<Rate>) -> Result<()> {
|
pub fn update_icon(&self, _rate: Option<Rate>) -> Result<()> {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray icon update");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let app_handle = match handle::Handle::global().app_handle() {
|
let app_handle = match handle::Handle::global().app_handle() {
|
||||||
Some(handle) => handle,
|
Some(handle) => handle,
|
||||||
None => {
|
None => {
|
||||||
log::warn!(target: "app", "更新托盘图标失败: app_handle不存在");
|
log::warn!(target: "app", "Failed to update tray icon: app_handle not found");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -303,7 +318,7 @@ impl Tray {
|
|||||||
let tray = match app_handle.tray_by_id("main") {
|
let tray = match app_handle.tray_by_id("main") {
|
||||||
Some(tray) => tray,
|
Some(tray) => tray,
|
||||||
None => {
|
None => {
|
||||||
log::warn!(target: "app", "更新托盘图标失败: 托盘不存在");
|
log::warn!(target: "app", "Failed to update tray icon: tray not found");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -329,10 +344,14 @@ impl Tray {
|
|||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
pub fn update_icon(&self, _rate: Option<Rate>) -> Result<()> {
|
pub fn update_icon(&self, _rate: Option<Rate>) -> Result<()> {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray icon update");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let app_handle = match handle::Handle::global().app_handle() {
|
let app_handle = match handle::Handle::global().app_handle() {
|
||||||
Some(handle) => handle,
|
Some(handle) => handle,
|
||||||
None => {
|
None => {
|
||||||
log::warn!(target: "app", "更新托盘图标失败: app_handle不存在");
|
log::warn!(target: "app", "Failed to update tray icon: app_handle not found");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -340,7 +359,7 @@ impl Tray {
|
|||||||
let tray = match app_handle.tray_by_id("main") {
|
let tray = match app_handle.tray_by_id("main") {
|
||||||
Some(tray) => tray,
|
Some(tray) => tray,
|
||||||
None => {
|
None => {
|
||||||
log::warn!(target: "app", "更新托盘图标失败: 托盘不存在");
|
log::warn!(target: "app", "Failed to update tray icon: tray not found");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -362,6 +381,10 @@ impl Tray {
|
|||||||
|
|
||||||
/// 更新托盘显示状态的函数
|
/// 更新托盘显示状态的函数
|
||||||
pub fn update_tray_display(&self) -> Result<()> {
|
pub fn update_tray_display(&self) -> Result<()> {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray display update");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
let _tray = app_handle.tray_by_id("main").unwrap();
|
let _tray = app_handle.tray_by_id("main").unwrap();
|
||||||
|
|
||||||
@@ -373,10 +396,14 @@ impl Tray {
|
|||||||
|
|
||||||
/// 更新托盘提示
|
/// 更新托盘提示
|
||||||
pub fn update_tooltip(&self) -> Result<()> {
|
pub fn update_tooltip(&self) -> Result<()> {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray tooltip update");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let app_handle = match handle::Handle::global().app_handle() {
|
let app_handle = match handle::Handle::global().app_handle() {
|
||||||
Some(handle) => handle,
|
Some(handle) => handle,
|
||||||
None => {
|
None => {
|
||||||
log::warn!(target: "app", "更新托盘提示失败: app_handle不存在");
|
log::warn!(target: "app", "Failed to update tray tooltip: app_handle not found");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -384,7 +411,7 @@ impl Tray {
|
|||||||
let version = match VERSION.get() {
|
let version = match VERSION.get() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => {
|
None => {
|
||||||
log::warn!(target: "app", "更新托盘提示失败: 版本信息不存在");
|
log::warn!(target: "app", "Failed to update tray tooltip: version info not found");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -414,7 +441,7 @@ impl Tray {
|
|||||||
|
|
||||||
if let Some(tray) = app_handle.tray_by_id("main") {
|
if let Some(tray) = app_handle.tray_by_id("main") {
|
||||||
let _ = tray.set_tooltip(Some(&format!(
|
let _ = tray.set_tooltip(Some(&format!(
|
||||||
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
|
"Koala Clash {version}\n{}: {}\n{}: {}\n{}: {}",
|
||||||
t("SysProxy"),
|
t("SysProxy"),
|
||||||
switch_map[system_proxy],
|
switch_map[system_proxy],
|
||||||
t("TUN"),
|
t("TUN"),
|
||||||
@@ -423,13 +450,17 @@ impl Tray {
|
|||||||
current_profile_name
|
current_profile_name
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
log::warn!(target: "app", "更新托盘提示失败: 托盘不存在");
|
log::warn!(target: "app", "Failed to update tray tooltip: tray not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_part(&self) -> Result<()> {
|
pub fn update_part(&self) -> Result<()> {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray partial update");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
self.update_menu()?;
|
self.update_menu()?;
|
||||||
self.update_icon(None)?;
|
self.update_icon(None)?;
|
||||||
self.update_tooltip()?;
|
self.update_tooltip()?;
|
||||||
@@ -443,7 +474,11 @@ impl Tray {
|
|||||||
pub fn unsubscribe_traffic(&self) {}
|
pub fn unsubscribe_traffic(&self) {}
|
||||||
|
|
||||||
pub fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> {
|
pub fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> {
|
||||||
log::info!(target: "app", "正在从AppHandle创建系统托盘");
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray creation");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
log::info!(target: "app", "Creating system tray from AppHandle");
|
||||||
|
|
||||||
// 获取图标
|
// 获取图标
|
||||||
let icon_bytes = TrayState::get_common_tray_icon().1;
|
let icon_bytes = TrayState::get_common_tray_icon().1;
|
||||||
@@ -491,25 +526,29 @@ impl Tray {
|
|||||||
"tun_mode" => feat::toggle_tun_mode(None),
|
"tun_mode" => feat::toggle_tun_mode(None),
|
||||||
"main_window" => {
|
"main_window" => {
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
log::info!(target: "app", "Tray点击事件: 显示主窗口");
|
log::info!(target: "app", "Tray click: show main window");
|
||||||
if crate::module::lightweight::is_in_lightweight_mode() {
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
log::info!(target: "app", "当前在轻量模式,正在退出轻量模式");
|
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
|
||||||
crate::module::lightweight::exit_lightweight_mode();
|
crate::module::lightweight::exit_lightweight_mode();
|
||||||
}
|
}
|
||||||
let result = WindowManager::show_main_window();
|
let result = WindowManager::show_main_window();
|
||||||
log::info!(target: "app", "窗口显示结果: {result:?}");
|
log::info!(target: "app", "Window show result: {result:?}");
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tray.on_menu_event(on_menu_event);
|
tray.on_menu_event(on_menu_event);
|
||||||
log::info!(target: "app", "系统托盘创建成功");
|
log::info!(target: "app", "System tray created successfully");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 托盘统一的状态更新函数
|
// 托盘统一的状态更新函数
|
||||||
pub fn update_all_states(&self) -> Result<()> {
|
pub fn update_all_states(&self) -> Result<()> {
|
||||||
|
if handle::Handle::global().is_exiting() {
|
||||||
|
log::debug!(target: "app", "Application is exiting, skip tray state update");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
// 确保所有状态更新完成
|
// 确保所有状态更新完成
|
||||||
self.update_menu()?;
|
self.update_menu()?;
|
||||||
self.update_icon(None)?;
|
self.update_icon(None)?;
|
||||||
@@ -601,16 +640,6 @@ fn create_tray_menu(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let direct_mode = &CheckMenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"direct_mode",
|
|
||||||
t("Direct Mode"),
|
|
||||||
true,
|
|
||||||
mode == "direct",
|
|
||||||
hotkeys.get("clash_mode_direct").map(|s| s.as_str()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let profiles = &Submenu::with_id_and_items(
|
let profiles = &Submenu::with_id_and_items(
|
||||||
app_handle,
|
app_handle,
|
||||||
"profiles",
|
"profiles",
|
||||||
@@ -650,45 +679,6 @@ fn create_tray_menu(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let copy_env =
|
|
||||||
&MenuItem::with_id(app_handle, "copy_env", t("Copy Env"), true, None::<&str>).unwrap();
|
|
||||||
|
|
||||||
let open_app_dir = &MenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"open_app_dir",
|
|
||||||
t("Conf Dir"),
|
|
||||||
true,
|
|
||||||
None::<&str>,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let open_core_dir = &MenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"open_core_dir",
|
|
||||||
t("Core Dir"),
|
|
||||||
true,
|
|
||||||
None::<&str>,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let open_logs_dir = &MenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"open_logs_dir",
|
|
||||||
t("Logs Dir"),
|
|
||||||
true,
|
|
||||||
None::<&str>,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let open_dir = &Submenu::with_id_and_items(
|
|
||||||
app_handle,
|
|
||||||
"open_dir",
|
|
||||||
t("Open Dir"),
|
|
||||||
true,
|
|
||||||
&[open_app_dir, open_core_dir, open_logs_dir],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let restart_clash = &MenuItem::with_id(
|
let restart_clash = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"restart_clash",
|
"restart_clash",
|
||||||
@@ -736,7 +726,6 @@ fn create_tray_menu(
|
|||||||
separator,
|
separator,
|
||||||
rule_mode,
|
rule_mode,
|
||||||
global_mode,
|
global_mode,
|
||||||
direct_mode,
|
|
||||||
separator,
|
separator,
|
||||||
profiles,
|
profiles,
|
||||||
separator,
|
separator,
|
||||||
@@ -744,8 +733,6 @@ fn create_tray_menu(
|
|||||||
tun_mode,
|
tun_mode,
|
||||||
separator,
|
separator,
|
||||||
lighteweight_mode,
|
lighteweight_mode,
|
||||||
copy_env,
|
|
||||||
open_dir,
|
|
||||||
more,
|
more,
|
||||||
separator,
|
separator,
|
||||||
quit,
|
quit,
|
||||||
@@ -770,18 +757,18 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
}
|
}
|
||||||
"open_window" => {
|
"open_window" => {
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
log::info!(target: "app", "托盘菜单点击: 打开窗口");
|
log::info!(target: "app", "Tray menu click: open window");
|
||||||
|
|
||||||
if !should_handle_tray_click() {
|
if !should_handle_tray_click() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if crate::module::lightweight::is_in_lightweight_mode() {
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
log::info!(target: "app", "当前在轻量模式,正在退出");
|
log::info!(target: "app", "Currently in lightweight mode, exiting");
|
||||||
crate::module::lightweight::exit_lightweight_mode();
|
crate::module::lightweight::exit_lightweight_mode();
|
||||||
}
|
}
|
||||||
let result = WindowManager::show_main_window();
|
let result = WindowManager::show_main_window();
|
||||||
log::info!(target: "app", "窗口显示结果: {result:?}");
|
log::info!(target: "app", "Window show result: {result:?}");
|
||||||
}
|
}
|
||||||
"system_proxy" => {
|
"system_proxy" => {
|
||||||
feat::toggle_system_proxy();
|
feat::toggle_system_proxy();
|
||||||
@@ -789,16 +776,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
"tun_mode" => {
|
"tun_mode" => {
|
||||||
feat::toggle_tun_mode(None);
|
feat::toggle_tun_mode(None);
|
||||||
}
|
}
|
||||||
"copy_env" => feat::copy_clash_env(),
|
|
||||||
"open_app_dir" => {
|
|
||||||
let _ = cmd::open_app_dir();
|
|
||||||
}
|
|
||||||
"open_core_dir" => {
|
|
||||||
let _ = cmd::open_core_dir();
|
|
||||||
}
|
|
||||||
"open_logs_dir" => {
|
|
||||||
let _ = cmd::open_logs_dir();
|
|
||||||
}
|
|
||||||
"restart_clash" => feat::restart_clash_core(),
|
"restart_clash" => feat::restart_clash_core(),
|
||||||
"restart_app" => feat::restart_app(),
|
"restart_app" => feat::restart_app(),
|
||||||
"entry_lightweight_mode" => {
|
"entry_lightweight_mode" => {
|
||||||
@@ -816,7 +793,7 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
if was_lightweight {
|
if was_lightweight {
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
let result = WindowManager::show_main_window();
|
let result = WindowManager::show_main_window();
|
||||||
log::info!(target: "app", "退出轻量模式后显示主窗口: {result:?}");
|
log::info!(target: "app", "Show main window after exiting lightweight mode: {result:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"quit" => {
|
"quit" => {
|
||||||
@@ -830,6 +807,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = Tray::global().update_all_states() {
|
if let Err(e) = Tray::global().update_all_states() {
|
||||||
log::warn!(target: "app", "更新托盘状态失败: {e}");
|
log::warn!(target: "app", "Failed to update tray state: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ impl ChainSupport {
|
|||||||
(self, core.as_str()),
|
(self, core.as_str()),
|
||||||
(ChainSupport::All, _)
|
(ChainSupport::All, _)
|
||||||
| (ChainSupport::Clash, "clash")
|
| (ChainSupport::Clash, "clash")
|
||||||
| (ChainSupport::ClashMeta, "verge-mihomo")
|
| (ChainSupport::ClashMeta, "koala-mihomo")
|
||||||
| (ChainSupport::ClashMetaAlpha, "verge-mihomo-alpha")
|
| (ChainSupport::ClashMetaAlpha, "koala-mihomo-alpha")
|
||||||
),
|
),
|
||||||
None => true,
|
None => true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,8 +202,10 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
});
|
});
|
||||||
let patch_tun = value.as_mapping().cloned().unwrap_or(Mapping::new());
|
let patch_tun = value.as_mapping().cloned().unwrap_or(Mapping::new());
|
||||||
for (key, value) in patch_tun.into_iter() {
|
for (key, value) in patch_tun.into_iter() {
|
||||||
|
if !tun.contains_key(&key) {
|
||||||
tun.insert(key, value);
|
tun.insert(key, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
config.insert("tun".into(), tun.into());
|
config.insert("tun".into(), tun.into());
|
||||||
} else {
|
} else {
|
||||||
if key.as_str() == Some("socks-port") && !socks_enabled {
|
if key.as_str() == Some("socks-port") && !socks_enabled {
|
||||||
@@ -239,7 +241,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
.filter(|(s, _)| s.is_support(clash_core.as_ref()))
|
.filter(|(s, _)| s.is_support(clash_core.as_ref()))
|
||||||
.map(|(_, c)| c)
|
.map(|(_, c)| c)
|
||||||
.for_each(|item| {
|
.for_each(|item| {
|
||||||
log::debug!(target: "app", "run builtin script {}", item.uid);
|
log::debug!(target: "app", "run builtin script {0}", item.uid);
|
||||||
if let ChainType::Script(script) = item.data {
|
if let ChainType::Script(script) = item.data {
|
||||||
match use_script(script, config.to_owned(), "".to_string()) {
|
match use_script(script, config.to_owned(), "".to_string()) {
|
||||||
Ok((res_config, _)) => {
|
Ok((res_config, _)) => {
|
||||||
|
|||||||
@@ -141,8 +141,8 @@ fn test_script() {
|
|||||||
fn test_escape_unescape() {
|
fn test_escape_unescape() {
|
||||||
let test_string = r#"Hello "World"!\nThis is a test with \u00A9 copyright symbol."#;
|
let test_string = r#"Hello "World"!\nThis is a test with \u00A9 copyright symbol."#;
|
||||||
let escaped = escape_js_string_for_single_quote(test_string);
|
let escaped = escape_js_string_for_single_quote(test_string);
|
||||||
println!("Original: {}", test_string);
|
println!("Original: {test_string}");
|
||||||
println!("Escaped: {}", escaped);
|
println!("Escaped: {escaped}");
|
||||||
|
|
||||||
let json_str = r#"{"key":"value","nested":{"key":"value"}}"#;
|
let json_str = r#"{"key":"value","nested":{"key":"value"}}"#;
|
||||||
let parsed = parse_json_safely(json_str).unwrap();
|
let parsed = parse_json_safely(json_str).unwrap();
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
crate::utils::resolve::restore_public_dns().await;
|
crate::utils::resolve::restore_public_dns().await;
|
||||||
crate::utils::resolve::set_public_dns("223.6.6.6".to_string()).await;
|
crate::utils::resolve::set_public_dns("8.8.8.8".to_string()).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ pub async fn test_delay(url: String) -> anyhow::Result<u32> {
|
|||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
let response = NetworkManager::global()
|
let response = NetworkManager::global()
|
||||||
.get_with_interrupt(&url, proxy_type, Some(10), user_agent, false)
|
.get_with_interrupt(&url, proxy_type, Some(10), user_agent, false, false)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
|
|||||||
@@ -31,7 +31,13 @@ pub async fn update_profile(
|
|||||||
option: Option<PrfOption>,
|
option: Option<PrfOption>,
|
||||||
auto_refresh: Option<bool>,
|
auto_refresh: Option<bool>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
logging!(info, Type::Config, true, "[订阅更新] 开始更新订阅 {}", uid);
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"[Subscription Update] Start updating subscription {}",
|
||||||
|
uid
|
||||||
|
);
|
||||||
let auto_refresh = auto_refresh.unwrap_or(true); // 默认为true,保持兼容性
|
let auto_refresh = auto_refresh.unwrap_or(true); // 默认为true,保持兼容性
|
||||||
|
|
||||||
let url_opt = {
|
let url_opt = {
|
||||||
@@ -41,14 +47,14 @@ pub async fn update_profile(
|
|||||||
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
|
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
|
||||||
|
|
||||||
if !is_remote {
|
if !is_remote {
|
||||||
log::info!(target: "app", "[订阅更新] {uid} 不是远程订阅,跳过更新");
|
log::info!(target: "app", "[Subscription Update] {uid} is not a remote subscription, skipping update");
|
||||||
None // 非远程订阅直接更新
|
None // 非远程订阅直接更新
|
||||||
} else if item.url.is_none() {
|
} else if item.url.is_none() {
|
||||||
log::warn!(target: "app", "[订阅更新] {uid} 缺少URL,无法更新");
|
log::warn!(target: "app", "[Subscription Update] {uid} is missing URL, cannot update");
|
||||||
bail!("failed to get the profile item url");
|
bail!("failed to get the profile item url");
|
||||||
} else {
|
} else {
|
||||||
log::info!(target: "app",
|
log::info!(target: "app",
|
||||||
"[订阅更新] {} 是远程订阅,URL: {}",
|
"[Subscription Update] {} is a remote subscription, URL: {}",
|
||||||
uid,
|
uid,
|
||||||
item.url.clone().unwrap()
|
item.url.clone().unwrap()
|
||||||
);
|
);
|
||||||
@@ -58,24 +64,24 @@ pub async fn update_profile(
|
|||||||
|
|
||||||
let should_update = match url_opt {
|
let should_update = match url_opt {
|
||||||
Some((url, opt)) => {
|
Some((url, opt)) => {
|
||||||
log::info!(target: "app", "[订阅更新] 开始下载新的订阅内容");
|
log::info!(target: "app", "[Subscription Update] Start downloading new subscription content");
|
||||||
let merged_opt = PrfOption::merge(opt.clone(), option.clone());
|
let merged_opt = PrfOption::merge(opt.clone(), option.clone());
|
||||||
|
|
||||||
// 尝试使用正常设置更新
|
// 尝试使用正常设置更新
|
||||||
match PrfItem::from_url(&url, None, None, merged_opt.clone()).await {
|
match PrfItem::from_url(&url, None, None, merged_opt.clone()).await {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
log::info!(target: "app", "[订阅更新] 更新订阅配置成功");
|
log::info!(target: "app", "[Subscription Update] Subscription config updated successfully");
|
||||||
let profiles = Config::profiles();
|
let profiles = Config::profiles();
|
||||||
let mut profiles = profiles.latest();
|
let mut profiles = profiles.latest();
|
||||||
profiles.update_item(uid.clone(), item)?;
|
profiles.update_item(uid.clone(), item)?;
|
||||||
|
|
||||||
let is_current = Some(uid.clone()) == profiles.get_current();
|
let is_current = Some(uid.clone()) == profiles.get_current();
|
||||||
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
|
log::info!(target: "app", "[Subscription Update] Is current active subscription: {is_current}");
|
||||||
is_current && auto_refresh
|
is_current && auto_refresh
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// 首次更新失败,尝试使用Clash代理
|
// 首次更新失败,尝试使用Clash代理
|
||||||
log::warn!(target: "app", "[订阅更新] 正常更新失败: {err},尝试使用Clash代理更新");
|
log::warn!(target: "app", "[Subscription Update] Normal update failed: {err}, trying to update via Clash proxy");
|
||||||
|
|
||||||
// 发送通知
|
// 发送通知
|
||||||
handle::Handle::notice_message("update_retry_with_clash", uid.clone());
|
handle::Handle::notice_message("update_retry_with_clash", uid.clone());
|
||||||
@@ -92,7 +98,7 @@ pub async fn update_profile(
|
|||||||
// 使用Clash代理重试
|
// 使用Clash代理重试
|
||||||
match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await {
|
match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await {
|
||||||
Ok(mut item) => {
|
Ok(mut item) => {
|
||||||
log::info!(target: "app", "[订阅更新] 使用Clash代理更新成功");
|
log::info!(target: "app", "[Subscription Update] Update via Clash proxy succeeded");
|
||||||
|
|
||||||
// 恢复原始代理设置到item
|
// 恢复原始代理设置到item
|
||||||
if let Some(option) = item.option.as_mut() {
|
if let Some(option) = item.option.as_mut() {
|
||||||
@@ -112,11 +118,11 @@ pub async fn update_profile(
|
|||||||
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
||||||
|
|
||||||
let is_current = Some(uid.clone()) == profiles.get_current();
|
let is_current = Some(uid.clone()) == profiles.get_current();
|
||||||
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
|
log::info!(target: "app", "[Subscription Update] Is current active subscription: {is_current}");
|
||||||
is_current && auto_refresh
|
is_current && auto_refresh
|
||||||
}
|
}
|
||||||
Err(retry_err) => {
|
Err(retry_err) => {
|
||||||
log::error!(target: "app", "[订阅更新] 使用Clash代理更新仍然失败: {retry_err}");
|
log::error!(target: "app", "[Subscription Update] Update via Clash proxy still failed: {retry_err}");
|
||||||
handle::Handle::notice_message(
|
handle::Handle::notice_message(
|
||||||
"update_failed_even_with_clash",
|
"update_failed_even_with_clash",
|
||||||
format!("{retry_err}"),
|
format!("{retry_err}"),
|
||||||
@@ -131,14 +137,30 @@ pub async fn update_profile(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if should_update {
|
if should_update {
|
||||||
logging!(info, Type::Config, true, "[订阅更新] 更新内核配置");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"[Subscription Update] Update core configuration"
|
||||||
|
);
|
||||||
match CoreManager::global().update_config().await {
|
match CoreManager::global().update_config().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Config, true, "[订阅更新] 更新成功");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"[Subscription Update] Update succeeded"
|
||||||
|
);
|
||||||
handle::Handle::refresh_clash();
|
handle::Handle::refresh_clash();
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(error, Type::Config, true, "[订阅更新] 更新失败: {}", err);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"[Subscription Update] Update failed: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
handle::Handle::notice_message("update_failed", format!("{err}"));
|
handle::Handle::notice_message("update_failed", format!("{err}"));
|
||||||
log::error!(target: "app", "{err}");
|
log::error!(target: "app", "{err}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
use crate::AppHandleManager;
|
use crate::AppHandleManager;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
core::{handle, sysopt, CoreManager},
|
core::{event_driven_proxy::EventDrivenProxyManager, handle, sysopt, CoreManager},
|
||||||
logging,
|
logging,
|
||||||
module::mihomo::MihomoManager,
|
module::mihomo::MihomoManager,
|
||||||
utils::logging::Type,
|
utils::logging::Type,
|
||||||
@@ -25,14 +25,14 @@ fn open_or_close_dashboard_internal(bypass_debounce: bool) {
|
|||||||
use crate::process::AsyncHandler;
|
use crate::process::AsyncHandler;
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
|
|
||||||
log::info!(target: "app", "Attempting to open/close dashboard (绕过防抖: {bypass_debounce})");
|
log::info!(target: "app", "Attempting to open/close dashboard (bypass debounce: {bypass_debounce})");
|
||||||
|
|
||||||
// 热键调用调度到主线程执行,避免 WebView 创建死锁
|
// 热键调用调度到主线程执行,避免 WebView 创建死锁
|
||||||
if bypass_debounce {
|
if bypass_debounce {
|
||||||
log::info!(target: "app", "热键调用,调度到主线程执行窗口操作");
|
log::info!(target: "app", "Hotkey invoked, dispatching window operation to main thread");
|
||||||
|
|
||||||
AsyncHandler::spawn(move || async move {
|
AsyncHandler::spawn(move || async move {
|
||||||
log::info!(target: "app", "主线程中执行热键窗口操作");
|
log::info!(target: "app", "Executing hotkey window operation on main thread");
|
||||||
|
|
||||||
if crate::module::lightweight::is_in_lightweight_mode() {
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
|
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
|
||||||
@@ -64,28 +64,34 @@ fn open_or_close_dashboard_internal(bypass_debounce: bool) {
|
|||||||
/// 异步优化的应用退出函数
|
/// 异步优化的应用退出函数
|
||||||
pub fn quit() {
|
pub fn quit() {
|
||||||
use crate::process::AsyncHandler;
|
use crate::process::AsyncHandler;
|
||||||
logging!(debug, Type::System, true, "启动退出流程");
|
logging!(debug, Type::System, true, "Start exit process");
|
||||||
|
|
||||||
// 获取应用句柄并设置退出标志
|
// 获取应用句柄并设置退出标志
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
handle::Handle::global().set_is_exiting();
|
handle::Handle::global().set_is_exiting();
|
||||||
|
EventDrivenProxyManager::global().notify_app_stopping();
|
||||||
|
|
||||||
// 优先关闭窗口,提供立即反馈
|
// 优先关闭窗口,提供立即反馈
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
if let Some(window) = handle::Handle::global().get_window() {
|
||||||
let _ = window.hide();
|
let _ = window.hide();
|
||||||
log::info!(target: "app", "窗口已隐藏");
|
log::info!(target: "app", "Window hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用异步任务处理资源清理,避免阻塞
|
// 使用异步任务处理资源清理,避免阻塞
|
||||||
AsyncHandler::spawn(move || async move {
|
AsyncHandler::spawn(move || async move {
|
||||||
logging!(info, Type::System, true, "开始异步清理资源");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::System,
|
||||||
|
true,
|
||||||
|
"Start asynchronous resource cleanup"
|
||||||
|
);
|
||||||
let cleanup_result = clean_async().await;
|
let cleanup_result = clean_async().await;
|
||||||
|
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::System,
|
Type::System,
|
||||||
true,
|
true,
|
||||||
"资源清理完成,退出代码: {}",
|
"Resource cleanup completed, exit code: {}",
|
||||||
if cleanup_result { 0 } else { 1 }
|
if cleanup_result { 0 } else { 1 }
|
||||||
);
|
);
|
||||||
app_handle.exit(if cleanup_result { 0 } else { 1 });
|
app_handle.exit(if cleanup_result { 0 } else { 1 });
|
||||||
@@ -95,7 +101,12 @@ pub fn quit() {
|
|||||||
async fn clean_async() -> bool {
|
async fn clean_async() -> bool {
|
||||||
use tokio::time::{timeout, Duration};
|
use tokio::time::{timeout, Duration};
|
||||||
|
|
||||||
logging!(info, Type::System, true, "开始执行异步清理操作...");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::System,
|
||||||
|
true,
|
||||||
|
"Start executing asynchronous cleanup..."
|
||||||
|
);
|
||||||
|
|
||||||
// 1. 处理TUN模式
|
// 1. 处理TUN模式
|
||||||
let tun_task = async {
|
let tun_task = async {
|
||||||
@@ -112,11 +123,11 @@ async fn clean_async() -> bool {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!(target: "app", "TUN模式已禁用");
|
log::info!(target: "app", "TUN mode disabled");
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::warn!(target: "app", "禁用TUN模式超时");
|
log::warn!(target: "app", "Timeout disabling TUN mode");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,11 +145,11 @@ async fn clean_async() -> bool {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!(target: "app", "系统代理已重置");
|
log::info!(target: "app", "System proxy reset");
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::warn!(target: "app", "重置系统代理超时");
|
log::warn!(target: "app", "Timeout resetting system proxy");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,11 +159,11 @@ async fn clean_async() -> bool {
|
|||||||
let core_task = async {
|
let core_task = async {
|
||||||
match timeout(Duration::from_secs(3), CoreManager::global().stop_core()).await {
|
match timeout(Duration::from_secs(3), CoreManager::global().stop_core()).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!(target: "app", "核心服务已停止");
|
log::info!(target: "app", "Core service stopped");
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::warn!(target: "app", "停止核心服务超时");
|
log::warn!(target: "app", "Timeout stopping core service");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,11 +179,11 @@ async fn clean_async() -> bool {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!(target: "app", "DNS设置已恢复");
|
log::info!(target: "app", "DNS settings restored");
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::warn!(target: "app", "恢复DNS设置超时");
|
log::warn!(target: "app", "Timeout restoring DNS settings");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,7 +203,7 @@ async fn clean_async() -> bool {
|
|||||||
info,
|
info,
|
||||||
Type::System,
|
Type::System,
|
||||||
true,
|
true,
|
||||||
"异步清理操作完成 - TUN: {}, 代理: {}, 核心: {}, DNS: {}, 总体: {}",
|
"Asynchronous cleanup completed - TUN: {}, Proxy: {}, Core: {}, DNS: {}, Overall: {}",
|
||||||
tun_success,
|
tun_success,
|
||||||
proxy_success,
|
proxy_success,
|
||||||
core_success,
|
core_success,
|
||||||
@@ -209,7 +220,7 @@ pub fn clean() -> bool {
|
|||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
AsyncHandler::spawn(move || async move {
|
AsyncHandler::spawn(move || async move {
|
||||||
logging!(info, Type::System, true, "开始执行清理操作...");
|
logging!(info, Type::System, true, "Start executing cleanup...");
|
||||||
|
|
||||||
// 使用已有的异步清理函数
|
// 使用已有的异步清理函数
|
||||||
let cleanup_result = clean_async().await;
|
let cleanup_result = clean_async().await;
|
||||||
@@ -220,7 +231,13 @@ pub fn clean() -> bool {
|
|||||||
|
|
||||||
match rx.recv_timeout(std::time::Duration::from_secs(8)) {
|
match rx.recv_timeout(std::time::Duration::from_secs(8)) {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
logging!(info, Type::System, true, "清理操作完成,结果: {}", result);
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::System,
|
||||||
|
true,
|
||||||
|
"Cleanup completed, result: {}",
|
||||||
|
result
|
||||||
|
);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -228,7 +245,7 @@ pub fn clean() -> bool {
|
|||||||
warn,
|
warn,
|
||||||
Type::System,
|
Type::System,
|
||||||
true,
|
true,
|
||||||
"清理操作超时,返回成功状态避免阻塞"
|
"Cleanup timed out, returning success to avoid blocking"
|
||||||
);
|
);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ mod process;
|
|||||||
mod state;
|
mod state;
|
||||||
mod utils;
|
mod utils;
|
||||||
use crate::{
|
use crate::{
|
||||||
core::hotkey,
|
core::{event_driven_proxy::EventDrivenProxyManager, hotkey},
|
||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::{resolve, resolve::resolve_scheme, server},
|
utils::resolve,
|
||||||
};
|
};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use std::sync::{Mutex, Once};
|
use std::sync::{Mutex, Once};
|
||||||
@@ -86,37 +86,13 @@ impl AppHandleManager {
|
|||||||
|
|
||||||
#[allow(clippy::panic)]
|
#[allow(clippy::panic)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
// Capture early deep link before any async setup (cold start on macOS)
|
||||||
|
utils::resolve::capture_early_deep_link_from_args();
|
||||||
|
|
||||||
utils::network::NetworkManager::global().init();
|
utils::network::NetworkManager::global().init();
|
||||||
|
|
||||||
let _ = utils::dirs::init_portable_flag();
|
let _ = utils::dirs::init_portable_flag();
|
||||||
|
|
||||||
// 异步单例检测
|
|
||||||
AsyncHandler::spawn(move || async move {
|
|
||||||
logging!(info, Type::Setup, true, "开始检查单例实例...");
|
|
||||||
match timeout(Duration::from_secs(3), server::check_singleton()).await {
|
|
||||||
Ok(result) => {
|
|
||||||
if result.is_err() {
|
|
||||||
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
|
|
||||||
if let Some(app_handle) = AppHandleManager::global().get() {
|
|
||||||
app_handle.exit(0);
|
|
||||||
} else {
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logging!(info, Type::Setup, true, "未检测到其他应用实例");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Setup,
|
|
||||||
true,
|
|
||||||
"单例检查超时,假定没有其他实例运行"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||||
|
|
||||||
@@ -125,6 +101,37 @@ pub fn run() {
|
|||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| {
|
||||||
|
// When a second instance is invoked, always show the window
|
||||||
|
AsyncHandler::spawn(move || async move {
|
||||||
|
// Exit lightweight mode if active
|
||||||
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
|
logging!(info, Type::System, true, "Second instance detected: exiting lightweight mode");
|
||||||
|
crate::module::lightweight::exit_lightweight_mode();
|
||||||
|
// Wait for lightweight mode to fully exit
|
||||||
|
for _ in 0..50 {
|
||||||
|
if !crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(20)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the main window
|
||||||
|
logging!(info, Type::System, true, "Second instance detected: showing main window");
|
||||||
|
let _ = crate::utils::window_manager::WindowManager::show_main_window();
|
||||||
|
|
||||||
|
// Handle deep link if present
|
||||||
|
if let Some(url) = argv
|
||||||
|
.iter()
|
||||||
|
.find(|a| a.starts_with("clash://") || a.starts_with("koala-clash://"))
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
logging!(info, Type::System, true, "Second instance with deep link: {}", url);
|
||||||
|
resolve::schedule_handle_deep_link(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}))
|
||||||
.plugin(tauri_plugin_notification::init())
|
.plugin(tauri_plugin_notification::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
@@ -135,7 +142,17 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
logging!(info, Type::Setup, true, "开始应用初始化...");
|
logging!(info, Type::Setup, true, "Starting app initialization...");
|
||||||
|
|
||||||
|
// Register deep link handler as early as possible to not miss cold-start events (macOS)
|
||||||
|
app.deep_link().on_open_url(|event| {
|
||||||
|
let urls: Vec<String> = event.urls().iter().map(|u| u.to_string()).collect();
|
||||||
|
logging!(info, Type::Setup, true, "on_open_url received: {:?}", urls);
|
||||||
|
if let Some(url) = urls.first().cloned() {
|
||||||
|
resolve::schedule_handle_deep_link(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut auto_start_plugin_builder = tauri_plugin_autostart::Builder::new();
|
let mut auto_start_plugin_builder = tauri_plugin_autostart::Builder::new();
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
@@ -145,26 +162,19 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
let _ = app.handle().plugin(auto_start_plugin_builder.build());
|
let _ = app.handle().plugin(auto_start_plugin_builder.build());
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
// Ensure URL schemes are registered with the OS (all platforms)
|
||||||
{
|
logging!(info, Type::Setup, true, "Registering deep links with OS...");
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
|
||||||
logging!(info, Type::Setup, true, "注册深层链接...");
|
|
||||||
logging_error!(Type::System, true, app.deep_link().register_all());
|
logging_error!(Type::System, true, app.deep_link().register_all());
|
||||||
}
|
|
||||||
|
|
||||||
app.deep_link().on_open_url(|event| {
|
// Deep link handler will be registered AFTER core handle init to ensure window creation works
|
||||||
AsyncHandler::spawn(move || {
|
|
||||||
let url = event.urls().first().map(|u| u.to_string());
|
|
||||||
async move {
|
|
||||||
if let Some(url) = url {
|
|
||||||
logging_error!(Type::Setup, true, resolve_scheme(url).await);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 窗口管理
|
// 窗口管理
|
||||||
logging!(info, Type::Setup, true, "初始化窗口状态管理...");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
true,
|
||||||
|
"Initializing window state management..."
|
||||||
|
);
|
||||||
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
||||||
.with_filename("window_state.json")
|
.with_filename("window_state.json")
|
||||||
.with_state_flags(tauri_plugin_window_state::StateFlags::default())
|
.with_state_flags(tauri_plugin_window_state::StateFlags::default())
|
||||||
@@ -174,7 +184,12 @@ pub fn run() {
|
|||||||
// 异步处理
|
// 异步处理
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
AsyncHandler::spawn(move || async move {
|
AsyncHandler::spawn(move || async move {
|
||||||
logging!(info, Type::Setup, true, "异步执行应用设置...");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
true,
|
||||||
|
"Executing app setup asynchronously..."
|
||||||
|
);
|
||||||
match timeout(
|
match timeout(
|
||||||
Duration::from_secs(30),
|
Duration::from_secs(30),
|
||||||
resolve::resolve_setup_async(&app_handle),
|
resolve::resolve_setup_async(&app_handle),
|
||||||
@@ -182,41 +197,81 @@ pub fn run() {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Setup, true, "应用设置成功完成");
|
logging!(info, Type::Setup, true, "App setup completed successfully");
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
logging!(
|
logging!(
|
||||||
error,
|
error,
|
||||||
Type::Setup,
|
Type::Setup,
|
||||||
true,
|
true,
|
||||||
"应用设置超时(30秒),继续执行后续流程"
|
"App setup timed out (30s), continuing with subsequent steps"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
logging!(info, Type::Setup, true, "执行主要设置操作...");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
true,
|
||||||
|
"Executing main setup operations..."
|
||||||
|
);
|
||||||
|
|
||||||
logging!(info, Type::Setup, true, "初始化AppHandleManager...");
|
logging!(info, Type::Setup, true, "Initializing AppHandleManager...");
|
||||||
AppHandleManager::global().init(app.handle().clone());
|
AppHandleManager::global().init(app.handle().clone());
|
||||||
|
|
||||||
logging!(info, Type::Setup, true, "初始化核心句柄...");
|
logging!(info, Type::Setup, true, "Initializing core handle...");
|
||||||
core::handle::Handle::global().init(app.handle());
|
core::handle::Handle::global().init(app.handle());
|
||||||
|
|
||||||
logging!(info, Type::Setup, true, "初始化配置...");
|
logging!(info, Type::Setup, true, "Initializing config...");
|
||||||
if let Err(e) = utils::init::init_config() {
|
if let Err(e) = utils::init::init_config() {
|
||||||
logging!(error, Type::Setup, true, "初始化配置失败: {}", e);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Setup,
|
||||||
|
true,
|
||||||
|
"Failed to initialize config: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(info, Type::Setup, true, "初始化资源...");
|
logging!(info, Type::Setup, true, "Initializing resources...");
|
||||||
if let Err(e) = utils::init::init_resources() {
|
if let Err(e) = utils::init::init_resources() {
|
||||||
logging!(error, Type::Setup, true, "初始化资源失败: {}", e);
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Setup,
|
||||||
|
true,
|
||||||
|
"Failed to initialize resources: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.manage(Mutex::new(state::proxy::CmdProxyState::default()));
|
app.manage(Mutex::new(state::proxy::CmdProxyState::default()));
|
||||||
app.manage(Mutex::new(state::lightweight::LightWeightState::default()));
|
app.manage(Mutex::new(state::lightweight::LightWeightState::default()));
|
||||||
|
|
||||||
logging!(info, Type::Setup, true, "初始化完成,继续执行");
|
// If an early deep link was captured from argv, schedule it now (after core and window can be created)
|
||||||
|
utils::resolve::replay_early_deep_link();
|
||||||
|
|
||||||
|
// (deep link handler already registered above)
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async {
|
||||||
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Cmd,
|
||||||
|
true,
|
||||||
|
"Running profile updates at startup..."
|
||||||
|
);
|
||||||
|
if let Err(e) = crate::cmd::update_profiles_on_startup().await {
|
||||||
|
log::error!("Failed to update profiles on startup: {e}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
true,
|
||||||
|
"Initialization completed, continuing"
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
@@ -295,6 +350,8 @@ pub fn run() {
|
|||||||
cmd::read_profile_file,
|
cmd::read_profile_file,
|
||||||
cmd::save_profile_file,
|
cmd::save_profile_file,
|
||||||
cmd::get_next_update_time,
|
cmd::get_next_update_time,
|
||||||
|
cmd::update_profiles_on_startup,
|
||||||
|
cmd::create_profile_from_share_link,
|
||||||
// script validation
|
// script validation
|
||||||
cmd::script_validate_notice,
|
cmd::script_validate_notice,
|
||||||
cmd::validate_script_file,
|
cmd::validate_script_file,
|
||||||
@@ -334,7 +391,7 @@ pub fn run() {
|
|||||||
|
|
||||||
app.run(|app_handle, e| match e {
|
app.run(|app_handle, e| match e {
|
||||||
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
|
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
|
||||||
logging!(info, Type::System, true, "应用就绪或恢复");
|
logging!(info, Type::System, true, "App ready or resumed");
|
||||||
AppHandleManager::global().init(app_handle.clone());
|
AppHandleManager::global().init(app_handle.clone());
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
@@ -342,8 +399,8 @@ pub fn run() {
|
|||||||
.get_handle()
|
.get_handle()
|
||||||
.get_webview_window("main")
|
.get_webview_window("main")
|
||||||
{
|
{
|
||||||
logging!(info, Type::Window, true, "设置macOS窗口标题");
|
logging!(info, Type::Window, true, "Setting macOS window title");
|
||||||
let _ = window.set_title("Clash Verge");
|
let _ = window.set_title("Koala Clash");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,6 +411,10 @@ pub fn run() {
|
|||||||
} => {
|
} => {
|
||||||
if !has_visible_windows {
|
if !has_visible_windows {
|
||||||
AppHandleManager::global().set_activation_policy_regular();
|
AppHandleManager::global().set_activation_policy_regular();
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AppHandleManager::global().init(app_handle.clone());
|
AppHandleManager::global().init(app_handle.clone());
|
||||||
}
|
}
|
||||||
@@ -363,12 +424,21 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tauri::RunEvent::Exit => {
|
tauri::RunEvent::Exit => {
|
||||||
// avoid duplicate cleanup
|
let handle = core::handle::Handle::global();
|
||||||
if core::handle::Handle::global().is_exiting() {
|
|
||||||
return;
|
if handle.is_exiting() {
|
||||||
}
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::System,
|
||||||
|
"Exit event triggered, but exit flow already executed, skip duplicate cleanup"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logging!(debug, Type::System, "Exit event triggered, executing cleanup flow");
|
||||||
|
handle.set_is_exiting();
|
||||||
|
EventDrivenProxyManager::global().notify_app_stopping();
|
||||||
feat::clean();
|
feat::clean();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
||||||
if label == "main" {
|
if label == "main" {
|
||||||
match event {
|
match event {
|
||||||
@@ -383,7 +453,12 @@ pub fn run() {
|
|||||||
if let Some(window) = core::handle::Handle::global().get_window() {
|
if let Some(window) = core::handle::Handle::global().get_window() {
|
||||||
let _ = window.hide();
|
let _ = window.hide();
|
||||||
} else {
|
} else {
|
||||||
logging!(warn, Type::Window, true, "尝试隐藏窗口但窗口不存在");
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"Tried to hide window but it does not exist"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tauri::WindowEvent::Focused(true) => {
|
tauri::WindowEvent::Focused(true) => {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub fn run_once_auto_lightweight() {
|
|||||||
info,
|
info,
|
||||||
Type::Lightweight,
|
Type::Lightweight,
|
||||||
true,
|
true,
|
||||||
"在静默启动的情况下,创建窗口再添加自动进入轻量模式窗口监听器"
|
"Silent start detected: create window, then attach auto lightweight-mode listener"
|
||||||
);
|
);
|
||||||
set_lightweight_mode(false);
|
set_lightweight_mode(false);
|
||||||
enable_auto_light_weight_mode();
|
enable_auto_light_weight_mode();
|
||||||
@@ -70,7 +70,7 @@ pub fn auto_lightweight_mode_init() {
|
|||||||
info,
|
info,
|
||||||
Type::Lightweight,
|
Type::Lightweight,
|
||||||
true,
|
true,
|
||||||
"非静默启动直接挂载自动进入轻量模式监听器!"
|
"Non-silent start: directly attach auto lightweight-mode listener"
|
||||||
);
|
);
|
||||||
set_lightweight_mode(true);
|
set_lightweight_mode(true);
|
||||||
enable_auto_light_weight_mode();
|
enable_auto_light_weight_mode();
|
||||||
@@ -102,26 +102,26 @@ pub fn set_lightweight_mode(value: bool) {
|
|||||||
|
|
||||||
pub fn enable_auto_light_weight_mode() {
|
pub fn enable_auto_light_weight_mode() {
|
||||||
Timer::global().init().unwrap();
|
Timer::global().init().unwrap();
|
||||||
logging!(info, Type::Lightweight, true, "开启自动轻量模式");
|
logging!(info, Type::Lightweight, true, "Enable auto lightweight mode");
|
||||||
setup_window_close_listener();
|
setup_window_close_listener();
|
||||||
setup_webview_focus_listener();
|
setup_webview_focus_listener();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_auto_light_weight_mode() {
|
pub fn disable_auto_light_weight_mode() {
|
||||||
logging!(info, Type::Lightweight, true, "关闭自动轻量模式");
|
logging!(info, Type::Lightweight, true, "Disable auto lightweight mode");
|
||||||
let _ = cancel_light_weight_timer();
|
let _ = cancel_light_weight_timer();
|
||||||
cancel_window_close_listener();
|
cancel_window_close_listener();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entry_lightweight_mode() {
|
pub fn entry_lightweight_mode() {
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
|
crate::utils::resolve::reset_ui_ready();
|
||||||
let result = WindowManager::hide_main_window();
|
let result = WindowManager::hide_main_window();
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Lightweight,
|
Type::Lightweight,
|
||||||
true,
|
true,
|
||||||
"轻量模式隐藏窗口结果: {:?}",
|
"Lightweight mode window hide result: {:?}",
|
||||||
result
|
result
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ pub fn exit_lightweight_mode() {
|
|||||||
info,
|
info,
|
||||||
Type::Lightweight,
|
Type::Lightweight,
|
||||||
true,
|
true,
|
||||||
"轻量模式退出操作已在进行中,跳过重复调用"
|
"Lightweight mode exit already in progress; skipping duplicate call"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -162,7 +162,7 @@ pub fn exit_lightweight_mode() {
|
|||||||
|
|
||||||
// 确保当前确实处于轻量模式才执行退出操作
|
// 确保当前确实处于轻量模式才执行退出操作
|
||||||
if !is_in_lightweight_mode() {
|
if !is_in_lightweight_mode() {
|
||||||
logging!(info, Type::Lightweight, true, "当前不在轻量模式,无需退出");
|
logging!(info, Type::Lightweight, true, "Not in lightweight mode; skip exit");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ fn setup_window_close_listener() -> u32 {
|
|||||||
info,
|
info,
|
||||||
Type::Lightweight,
|
Type::Lightweight,
|
||||||
true,
|
true,
|
||||||
"监听到关闭请求,开始轻量模式计时"
|
"Close requested; starting lightweight-mode timer"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return handler;
|
return handler;
|
||||||
@@ -207,7 +207,7 @@ fn setup_webview_focus_listener() -> u32 {
|
|||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Lightweight,
|
Type::Lightweight,
|
||||||
"监听到窗口获得焦点,取消轻量模式计时"
|
"Window focused; cancel lightweight-mode timer"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return handler;
|
return handler;
|
||||||
@@ -218,7 +218,7 @@ fn setup_webview_focus_listener() -> u32 {
|
|||||||
fn cancel_window_close_listener() {
|
fn cancel_window_close_listener() {
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
if let Some(window) = handle::Handle::global().get_window() {
|
||||||
window.unlisten(setup_window_close_listener());
|
window.unlisten(setup_window_close_listener());
|
||||||
logging!(info, Type::Lightweight, true, "取消了窗口关闭监听");
|
logging!(info, Type::Lightweight, true, "Removed window close listener");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ fn setup_light_weight_timer() -> Result<()> {
|
|||||||
.set_maximum_parallel_runnable_num(1)
|
.set_maximum_parallel_runnable_num(1)
|
||||||
.set_frequency_once_by_minutes(once_by_minutes)
|
.set_frequency_once_by_minutes(once_by_minutes)
|
||||||
.spawn_async_routine(move || async move {
|
.spawn_async_routine(move || async move {
|
||||||
logging!(info, Type::Timer, true, "计时器到期,开始进入轻量模式");
|
logging!(info, Type::Timer, true, "Timer expired; entering lightweight mode");
|
||||||
entry_lightweight_mode();
|
entry_lightweight_mode();
|
||||||
})
|
})
|
||||||
.context("failed to create timer task")?;
|
.context("failed to create timer task")?;
|
||||||
@@ -271,7 +271,7 @@ fn setup_light_weight_timer() -> Result<()> {
|
|||||||
info,
|
info,
|
||||||
Type::Timer,
|
Type::Timer,
|
||||||
true,
|
true,
|
||||||
"计时器已设置,{} 分钟后将自动进入轻量模式",
|
"Timer set; will auto-enter lightweight mode after {} minute(s)",
|
||||||
once_by_minutes
|
once_by_minutes
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ fn cancel_light_weight_timer() -> Result<()> {
|
|||||||
delay_timer
|
delay_timer
|
||||||
.remove_task(task.task_id)
|
.remove_task(task.task_id)
|
||||||
.context("failed to remove timer task")?;
|
.context("failed to remove timer task")?;
|
||||||
logging!(info, Type::Timer, true, "计时器已取消");
|
logging!(info, Type::Timer, true, "Timer canceled");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ impl LightWeightState {
|
|||||||
pub fn set_lightweight_mode(&mut self, value: bool) -> &Self {
|
pub fn set_lightweight_mode(&mut self, value: bool) -> &Self {
|
||||||
self.is_lightweight = value;
|
self.is_lightweight = value;
|
||||||
if value {
|
if value {
|
||||||
logging!(info, Type::Lightweight, true, "轻量模式已开启");
|
logging!(info, Type::Lightweight, true, "Lightweight mode enabled");
|
||||||
} else {
|
} else {
|
||||||
logging!(info, Type::Lightweight, true, "轻量模式已关闭");
|
logging!(info, Type::Lightweight, true, "Lightweight mode disabled");
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::{fs, os::windows::process::CommandExt, path::Path, path::PathBuf};
|
|||||||
/// Windows 下的开机启动文件夹路径
|
/// Windows 下的开机启动文件夹路径
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn get_startup_dir() -> Result<PathBuf> {
|
pub fn get_startup_dir() -> Result<PathBuf> {
|
||||||
let appdata = std::env::var("APPDATA").map_err(|_| anyhow!("无法获取 APPDATA 环境变量"))?;
|
let appdata = std::env::var("APPDATA").map_err(|_| anyhow!("Unable to obtain APPDATA environment variable"))?;
|
||||||
|
|
||||||
let startup_dir = Path::new(&appdata)
|
let startup_dir = Path::new(&appdata)
|
||||||
.join("Microsoft")
|
.join("Microsoft")
|
||||||
@@ -19,7 +19,7 @@ pub fn get_startup_dir() -> Result<PathBuf> {
|
|||||||
.join("Startup");
|
.join("Startup");
|
||||||
|
|
||||||
if !startup_dir.exists() {
|
if !startup_dir.exists() {
|
||||||
return Err(anyhow!("Startup 目录不存在: {:?}", startup_dir));
|
return Err(anyhow!("Startup directory does not exist: {:?}", startup_dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(startup_dir)
|
Ok(startup_dir)
|
||||||
@@ -29,7 +29,7 @@ pub fn get_startup_dir() -> Result<PathBuf> {
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn get_exe_path() -> Result<PathBuf> {
|
pub fn get_exe_path() -> Result<PathBuf> {
|
||||||
let exe_path =
|
let exe_path =
|
||||||
std::env::current_exe().map_err(|e| anyhow!("无法获取当前可执行文件路径: {}", e))?;
|
std::env::current_exe().map_err(|e| anyhow!("Unable to obtain the path of the current executable file: {}", e))?;
|
||||||
|
|
||||||
Ok(exe_path)
|
Ok(exe_path)
|
||||||
}
|
}
|
||||||
@@ -39,11 +39,11 @@ pub fn get_exe_path() -> Result<PathBuf> {
|
|||||||
pub fn create_shortcut() -> Result<()> {
|
pub fn create_shortcut() -> Result<()> {
|
||||||
let exe_path = get_exe_path()?;
|
let exe_path = get_exe_path()?;
|
||||||
let startup_dir = get_startup_dir()?;
|
let startup_dir = get_startup_dir()?;
|
||||||
let shortcut_path = startup_dir.join("Clash-Verge.lnk");
|
let shortcut_path = startup_dir.join("Koala-Clash.lnk");
|
||||||
|
|
||||||
// 如果快捷方式已存在,直接返回成功
|
// If the shortcut already exists, return success directly
|
||||||
if shortcut_path.exists() {
|
if shortcut_path.exists() {
|
||||||
info!(target: "app", "启动快捷方式已存在");
|
info!(target: "app", "Startup shortcut already exists");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,36 +59,36 @@ pub fn create_shortcut() -> Result<()> {
|
|||||||
|
|
||||||
let output = std::process::Command::new("powershell")
|
let output = std::process::Command::new("powershell")
|
||||||
.args(["-Command", &powershell_command])
|
.args(["-Command", &powershell_command])
|
||||||
// 隐藏 PowerShell 窗口
|
// Hide the PowerShell window
|
||||||
.creation_flags(0x08000000) // CREATE_NO_WINDOW
|
.creation_flags(0x08000000) // CREATE_NO_WINDOW
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| anyhow!("执行 PowerShell 命令失败: {}", e))?;
|
.map_err(|e| anyhow!("Failed to execute PowerShell command: {}", e))?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let error_msg = String::from_utf8_lossy(&output.stderr);
|
let error_msg = String::from_utf8_lossy(&output.stderr);
|
||||||
return Err(anyhow!("创建快捷方式失败: {}", error_msg));
|
return Err(anyhow!("Failed to create shortcut: {}", error_msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(target: "app", "成功创建启动快捷方式");
|
info!(target: "app", "Successfully created startup shortcut");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 删除快捷方式
|
/// Remove the shortcut
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn remove_shortcut() -> Result<()> {
|
pub fn remove_shortcut() -> Result<()> {
|
||||||
let startup_dir = get_startup_dir()?;
|
let startup_dir = get_startup_dir()?;
|
||||||
let shortcut_path = startup_dir.join("Clash-Verge.lnk");
|
let shortcut_path = startup_dir.join("Koala-Clash.lnk");
|
||||||
|
|
||||||
// 如果快捷方式不存在,直接返回成功
|
// If the shortcut does not exist, return success directly
|
||||||
if !shortcut_path.exists() {
|
if !shortcut_path.exists() {
|
||||||
info!(target: "app", "启动快捷方式不存在,无需删除");
|
info!(target: "app", "Startup shortcut does not exist, nothing to remove");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除快捷方式
|
// Delete the shortcut
|
||||||
fs::remove_file(&shortcut_path).map_err(|e| anyhow!("删除快捷方式失败: {}", e))?;
|
fs::remove_file(&shortcut_path).map_err(|e| anyhow!("Failed to delete shortcut: {}", e))?;
|
||||||
|
|
||||||
info!(target: "app", "成功删除启动快捷方式");
|
info!(target: "app", "Successfully removed startup shortcut");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ pub fn remove_shortcut() -> Result<()> {
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn is_shortcut_enabled() -> Result<bool> {
|
pub fn is_shortcut_enabled() -> Result<bool> {
|
||||||
let startup_dir = get_startup_dir()?;
|
let startup_dir = get_startup_dir()?;
|
||||||
let shortcut_path = startup_dir.join("Clash-Verge.lnk");
|
let shortcut_path = startup_dir.join("Koala-Clash.lnk");
|
||||||
|
|
||||||
Ok(shortcut_path.exists())
|
Ok(shortcut_path.exists())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ use std::{fs, path::PathBuf};
|
|||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
|
||||||
#[cfg(not(feature = "verge-dev"))]
|
#[cfg(not(feature = "verge-dev"))]
|
||||||
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev";
|
pub static APP_ID: &str = "io.github.koala-clash";
|
||||||
#[cfg(not(feature = "verge-dev"))]
|
#[cfg(not(feature = "verge-dev"))]
|
||||||
pub static BACKUP_DIR: &str = "clash-verge-rev-backup";
|
pub static BACKUP_DIR: &str = "io.github.koala-clash-backup";
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
#[cfg(feature = "verge-dev")]
|
||||||
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev";
|
pub static APP_ID: &str = "io.github.koala-clash.dev";
|
||||||
#[cfg(feature = "verge-dev")]
|
#[cfg(feature = "verge-dev")]
|
||||||
pub static BACKUP_DIR: &str = "clash-verge-rev-backup-dev";
|
pub static BACKUP_DIR: &str = "io.github.koala-clash-backup-dev";
|
||||||
|
|
||||||
pub static PORTABLE_FLAG: OnceCell<bool> = OnceCell::new();
|
pub static PORTABLE_FLAG: OnceCell<bool> = OnceCell::new();
|
||||||
|
|
||||||
@@ -188,13 +188,13 @@ pub fn profiles_path() -> Result<PathBuf> {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn service_path() -> Result<PathBuf> {
|
pub fn service_path() -> Result<PathBuf> {
|
||||||
let res_dir = app_resources_dir()?;
|
let res_dir = app_resources_dir()?;
|
||||||
Ok(res_dir.join("clash-verge-service"))
|
Ok(res_dir.join("koala-clash-service"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn service_path() -> Result<PathBuf> {
|
pub fn service_path() -> Result<PathBuf> {
|
||||||
let res_dir = app_resources_dir()?;
|
let res_dir = app_resources_dir()?;
|
||||||
Ok(res_dir.join("clash-verge-service.exe"))
|
Ok(res_dir.join("koala-clash-service.exe"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn service_log_file() -> Result<PathBuf> {
|
pub fn service_log_file() -> Result<PathBuf> {
|
||||||
|
|||||||
@@ -178,9 +178,8 @@ fn init_dns_config() -> Result<()> {
|
|||||||
"default-nameserver".into(),
|
"default-nameserver".into(),
|
||||||
Value::Sequence(vec![
|
Value::Sequence(vec![
|
||||||
Value::String("system".into()),
|
Value::String("system".into()),
|
||||||
Value::String("223.6.6.6".into()),
|
|
||||||
Value::String("8.8.8.8".into()),
|
Value::String("8.8.8.8".into()),
|
||||||
Value::String("2400:3200::1".into()),
|
Value::String("1.1.1.1".into()),
|
||||||
Value::String("2001:4860:4860::8888".into()),
|
Value::String("2001:4860:4860::8888".into()),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
@@ -189,7 +188,8 @@ fn init_dns_config() -> Result<()> {
|
|||||||
Value::Sequence(vec![
|
Value::Sequence(vec![
|
||||||
Value::String("8.8.8.8".into()),
|
Value::String("8.8.8.8".into()),
|
||||||
Value::String("https://doh.pub/dns-query".into()),
|
Value::String("https://doh.pub/dns-query".into()),
|
||||||
Value::String("https://dns.alidns.com/dns-query".into()),
|
Value::String("https://dns.google/dns-query".into()),
|
||||||
|
Value::String("https://cloudflare-dns.com/dns-query".into()),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
("fallback".into(), Value::Sequence(vec![])),
|
("fallback".into(), Value::Sequence(vec![])),
|
||||||
@@ -201,8 +201,9 @@ fn init_dns_config() -> Result<()> {
|
|||||||
"proxy-server-nameserver".into(),
|
"proxy-server-nameserver".into(),
|
||||||
Value::Sequence(vec![
|
Value::Sequence(vec![
|
||||||
Value::String("https://doh.pub/dns-query".into()),
|
Value::String("https://doh.pub/dns-query".into()),
|
||||||
Value::String("https://dns.alidns.com/dns-query".into()),
|
Value::String("https://dns.google/dns-query".into()),
|
||||||
Value::String("tls://223.5.5.5".into()),
|
Value::String("https://cloudflare-dns.com/dns-query".into()),
|
||||||
|
Value::String("tls://1.1.1.1".into()),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
("direct-nameserver".into(), Value::Sequence(vec![])),
|
("direct-nameserver".into(), Value::Sequence(vec![])),
|
||||||
@@ -246,7 +247,7 @@ fn init_dns_config() -> Result<()> {
|
|||||||
help::save_yaml(
|
help::save_yaml(
|
||||||
&dns_path,
|
&dns_path,
|
||||||
&default_dns_config,
|
&default_dns_config,
|
||||||
Some("# Clash Verge DNS Config"),
|
Some("# Koala Clash DNS Config"),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,14 +275,14 @@ pub fn init_config() -> Result<()> {
|
|||||||
|
|
||||||
crate::log_err!(dirs::clash_path().map(|path| {
|
crate::log_err!(dirs::clash_path().map(|path| {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
help::save_yaml(&path, &IClashTemp::template().0, Some("# Clash Vergeasu"))?;
|
help::save_yaml(&path, &IClashTemp::template().0, Some("# Koala Clash"))?;
|
||||||
}
|
}
|
||||||
<Result<()>>::Ok(())
|
<Result<()>>::Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
crate::log_err!(dirs::verge_path().map(|path| {
|
crate::log_err!(dirs::verge_path().map(|path| {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
help::save_yaml(&path, &IVerge::template(), Some("# Clash Verge"))?;
|
help::save_yaml(&path, &IVerge::template(), Some("# Koala Clash"))?;
|
||||||
}
|
}
|
||||||
<Result<()>>::Ok(())
|
<Result<()>>::Ok(())
|
||||||
}));
|
}));
|
||||||
@@ -291,7 +292,7 @@ pub fn init_config() -> Result<()> {
|
|||||||
|
|
||||||
crate::log_err!(dirs::profiles_path().map(|path| {
|
crate::log_err!(dirs::profiles_path().map(|path| {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
help::save_yaml(&path, &IProfiles::template(), Some("# Clash Verge"))?;
|
help::save_yaml(&path, &IProfiles::template(), Some("# Koala Clash"))?;
|
||||||
}
|
}
|
||||||
<Result<()>>::Ok(())
|
<Result<()>>::Ok(())
|
||||||
}));
|
}));
|
||||||
@@ -371,8 +372,8 @@ pub fn init_scheme() -> Result<()> {
|
|||||||
|
|
||||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
|
let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
|
||||||
clash.set_value("", &"Clash Verge")?;
|
clash.set_value("", &"Koala Clash")?;
|
||||||
clash.set_value("URL Protocol", &"Clash Verge URL Scheme Protocol")?;
|
clash.set_value("URL Protocol", &"Koala Clash URL Scheme Protocol")?;
|
||||||
let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
|
let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
|
||||||
default_icon.set_value("", &app_exe)?;
|
default_icon.set_value("", &app_exe)?;
|
||||||
let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
|
let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
|
||||||
@@ -384,7 +385,7 @@ pub fn init_scheme() -> Result<()> {
|
|||||||
pub fn init_scheme() -> Result<()> {
|
pub fn init_scheme() -> Result<()> {
|
||||||
let output = std::process::Command::new("xdg-mime")
|
let output = std::process::Command::new("xdg-mime")
|
||||||
.arg("default")
|
.arg("default")
|
||||||
.arg("clash-verge.desktop")
|
.arg("koala-clash.desktop")
|
||||||
.arg("x-scheme-handler/clash")
|
.arg("x-scheme-handler/clash")
|
||||||
.output()?;
|
.output()?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
@@ -397,6 +398,33 @@ pub fn init_scheme() -> Result<()> {
|
|||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn init_scheme() -> Result<()> {
|
pub fn init_scheme() -> Result<()> {
|
||||||
|
use std::process::Command;
|
||||||
|
use tauri::utils::platform::current_exe;
|
||||||
|
|
||||||
|
// Try to re-register the app bundle with LaunchServices to ensure URL schemes are active
|
||||||
|
if let Ok(exe) = current_exe() {
|
||||||
|
if let (Some(_parent1), Some(_parent2), Some(app_bundle)) =
|
||||||
|
(exe.parent(), exe.parent().and_then(|p| p.parent()), exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()))
|
||||||
|
{
|
||||||
|
let app_bundle_path = app_bundle.to_string_lossy().into_owned();
|
||||||
|
let lsregister = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
|
||||||
|
let output = Command::new(lsregister)
|
||||||
|
.args(["-f", "-R", &app_bundle_path])
|
||||||
|
.output();
|
||||||
|
match output {
|
||||||
|
Ok(out) => {
|
||||||
|
if !out.status.success() {
|
||||||
|
log::warn!(target: "app", "lsregister returned non-zero: {:?}", out.status);
|
||||||
|
} else {
|
||||||
|
log::info!(target: "app", "Re-registered URL schemes with LaunchServices");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(target: "app", "Failed to run lsregister: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ pub mod network;
|
|||||||
pub mod notification;
|
pub mod notification;
|
||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
pub mod sys_info;
|
||||||
pub mod tmpl;
|
pub mod tmpl;
|
||||||
pub mod window_manager;
|
pub mod window_manager;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use tokio::runtime::{Builder, Runtime};
|
use tokio::runtime::{Builder, Runtime};
|
||||||
|
|
||||||
use crate::{config::Config, logging, utils::logging::Type};
|
use crate::{config::Config, logging, utils::logging::Type, utils::sys_info};
|
||||||
|
|
||||||
// HTTP2 相关
|
// HTTP2 相关
|
||||||
const H2_CONNECTION_WINDOW_SIZE: u32 = 1024 * 1024;
|
const H2_CONNECTION_WINDOW_SIZE: u32 = 1024 * 1024;
|
||||||
@@ -40,7 +40,7 @@ impl NetworkManager {
|
|||||||
// 创建专用的异步运行时,线程数限制为4个
|
// 创建专用的异步运行时,线程数限制为4个
|
||||||
let runtime = Builder::new_multi_thread()
|
let runtime = Builder::new_multi_thread()
|
||||||
.worker_threads(4)
|
.worker_threads(4)
|
||||||
.thread_name("clash-verge-network")
|
.thread_name("koala-clash-network")
|
||||||
.enable_io()
|
.enable_io()
|
||||||
.enable_time()
|
.enable_time()
|
||||||
.build()
|
.build()
|
||||||
@@ -65,7 +65,7 @@ impl NetworkManager {
|
|||||||
pub fn init(&self) {
|
pub fn init(&self) {
|
||||||
self.init.call_once(|| {
|
self.init.call_once(|| {
|
||||||
self.runtime.spawn(async {
|
self.runtime.spawn(async {
|
||||||
logging!(info, Type::Network, true, "初始化网络管理器");
|
logging!(info, Type::Network, true, "Initializing network manager");
|
||||||
|
|
||||||
// 创建无代理客户端
|
// 创建无代理客户端
|
||||||
let no_proxy_client = ClientBuilder::new()
|
let no_proxy_client = ClientBuilder::new()
|
||||||
@@ -81,7 +81,7 @@ impl NetworkManager {
|
|||||||
let mut no_proxy_guard = NETWORK_MANAGER.no_proxy_client.lock().unwrap();
|
let mut no_proxy_guard = NETWORK_MANAGER.no_proxy_client.lock().unwrap();
|
||||||
*no_proxy_guard = Some(no_proxy_client);
|
*no_proxy_guard = Some(no_proxy_client);
|
||||||
|
|
||||||
logging!(info, Type::Network, true, "网络管理器初始化完成");
|
logging!(info, Type::Network, true, "Network manager initialization completed");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ impl NetworkManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_clients(&self) {
|
pub fn reset_clients(&self) {
|
||||||
logging!(info, Type::Network, true, "正在重置所有HTTP客户端");
|
logging!(info, Type::Network, true, "Resetting all HTTP clients");
|
||||||
{
|
{
|
||||||
let mut client = self.self_proxy_client.lock().unwrap();
|
let mut client = self.self_proxy_client.lock().unwrap();
|
||||||
*client = None;
|
*client = None;
|
||||||
@@ -248,6 +248,7 @@ impl NetworkManager {
|
|||||||
timeout_secs: Option<u64>,
|
timeout_secs: Option<u64>,
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
accept_invalid_certs: bool,
|
accept_invalid_certs: bool,
|
||||||
|
use_hwid: bool,
|
||||||
) -> RequestBuilder {
|
) -> RequestBuilder {
|
||||||
if self.should_reset_clients() {
|
if self.should_reset_clients() {
|
||||||
self.reset_clients();
|
self.reset_clients();
|
||||||
@@ -322,8 +323,8 @@ impl NetworkManager {
|
|||||||
use crate::utils::resolve::VERSION;
|
use crate::utils::resolve::VERSION;
|
||||||
|
|
||||||
let version = match VERSION.get() {
|
let version = match VERSION.get() {
|
||||||
Some(v) => format!("clash-verge/v{v}"),
|
Some(v) => format!("koala-clash/v{v}"),
|
||||||
None => "clash-verge/unknown".to_string(),
|
None => "koala-clash/unknown".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
builder = builder.user_agent(version);
|
builder = builder.user_agent(version);
|
||||||
@@ -331,7 +332,19 @@ impl NetworkManager {
|
|||||||
|
|
||||||
let client = builder.build().expect("Failed to build custom HTTP client");
|
let client = builder.build().expect("Failed to build custom HTTP client");
|
||||||
|
|
||||||
client.get(url)
|
let mut request_builder = client.get(url);
|
||||||
|
|
||||||
|
if use_hwid {
|
||||||
|
let sys_info = sys_info::get_system_info();
|
||||||
|
logging!(info, Type::Network, true, "Adding HWID headers to request");
|
||||||
|
request_builder = request_builder
|
||||||
|
.header("x-hwid", &sys_info.hwid)
|
||||||
|
.header("x-device-os", &sys_info.os_type)
|
||||||
|
.header("x-ver-os", &sys_info.os_ver)
|
||||||
|
.header("x-device-model", &sys_info.device_model);
|
||||||
|
}
|
||||||
|
|
||||||
|
request_builder
|
||||||
}
|
}
|
||||||
|
|
||||||
/* /// 执行GET请求,添加错误跟踪
|
/* /// 执行GET请求,添加错误跟踪
|
||||||
@@ -378,6 +391,7 @@ impl NetworkManager {
|
|||||||
timeout_secs: Option<u64>,
|
timeout_secs: Option<u64>,
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
accept_invalid_certs: bool,
|
accept_invalid_certs: bool,
|
||||||
|
use_hwid: bool,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let request = self.create_request(
|
let request = self.create_request(
|
||||||
url,
|
url,
|
||||||
@@ -385,6 +399,7 @@ impl NetworkManager {
|
|||||||
timeout_secs,
|
timeout_secs,
|
||||||
user_agent,
|
user_agent,
|
||||||
accept_invalid_certs,
|
accept_invalid_certs,
|
||||||
|
use_hwid,
|
||||||
);
|
);
|
||||||
|
|
||||||
let timeout_duration = timeout_secs.unwrap_or(20);
|
let timeout_duration = timeout_secs.unwrap_or(20);
|
||||||
@@ -395,7 +410,7 @@ impl NetworkManager {
|
|||||||
let watchdog = tokio::spawn(async move {
|
let watchdog = tokio::spawn(async move {
|
||||||
tokio::time::sleep(Duration::from_secs(timeout_duration)).await;
|
tokio::time::sleep(Duration::from_secs(timeout_duration)).await;
|
||||||
let _ = cancel_tx.send(());
|
let _ = cancel_tx.send(());
|
||||||
logging!(warn, Type::Network, true, "请求超时取消: {}", url_clone);
|
logging!(warn, Type::Network, true, "Request canceled due to timeout: {}", url_clone);
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = tokio::select! {
|
let result = tokio::select! {
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ use crate::AppHandleManager;
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, IVerge, PrfItem},
|
config::{Config, IVerge, PrfItem},
|
||||||
core::*,
|
core::*,
|
||||||
|
core::handle::Handle,
|
||||||
logging, logging_error,
|
logging, logging_error,
|
||||||
module::lightweight::{self, auto_lightweight_mode_init},
|
module::lightweight::{self, auto_lightweight_mode_init},
|
||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::{init, logging::Type, server},
|
utils::{init, logging::Type, server, window_manager::WindowManager},
|
||||||
wrap_err,
|
wrap_err,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
@@ -65,6 +66,35 @@ impl Default for UiReadyState {
|
|||||||
// 获取UI就绪状态细节
|
// 获取UI就绪状态细节
|
||||||
static UI_READY_STATE: OnceCell<Arc<UiReadyState>> = OnceCell::new();
|
static UI_READY_STATE: OnceCell<Arc<UiReadyState>> = OnceCell::new();
|
||||||
|
|
||||||
|
// Early deep link capture on cold start
|
||||||
|
static EARLY_DEEP_LINK: OnceCell<Mutex<Option<String>>> = OnceCell::new();
|
||||||
|
// Deduplication for deep links to avoid processing same URL twice in short time
|
||||||
|
static LAST_DEEP_LINK: OnceCell<Mutex<Option<(String, Instant)>>> = OnceCell::new();
|
||||||
|
|
||||||
|
fn get_early_deep_link() -> &'static Mutex<Option<String>> {
|
||||||
|
EARLY_DEEP_LINK.get_or_init(|| Mutex::new(None))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Capture deep link from process arguments as early as possible (cold start on macOS)
|
||||||
|
pub fn capture_early_deep_link_from_args() {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if let Some(url) = args.iter().find(|a| a.starts_with("clash://") || a.starts_with("koala-clash://")).cloned() {
|
||||||
|
println!("[DeepLink][argv] {}", url);
|
||||||
|
logging!(info, Type::Setup, true, "argv captured deep link: {}", url);
|
||||||
|
*get_early_deep_link().lock() = Some(url);
|
||||||
|
} else {
|
||||||
|
println!("[DeepLink][argv] none: {:?}", args);
|
||||||
|
logging!(info, Type::Setup, true, "no deep link found in argv at startup: {:?}", args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If an early deep link was captured before setup, schedule it now
|
||||||
|
pub fn replay_early_deep_link() {
|
||||||
|
if let Some(url) = get_early_deep_link().lock().take() {
|
||||||
|
schedule_handle_deep_link(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_window_creating_lock() -> &'static Mutex<(bool, Instant)> {
|
fn get_window_creating_lock() -> &'static Mutex<(bool, Instant)> {
|
||||||
WINDOW_CREATING.get_or_init(|| Mutex::new((false, Instant::now())))
|
WINDOW_CREATING.get_or_init(|| Mutex::new((false, Instant::now())))
|
||||||
}
|
}
|
||||||
@@ -73,6 +103,11 @@ fn get_ui_ready() -> &'static Arc<RwLock<bool>> {
|
|||||||
UI_READY.get_or_init(|| Arc::new(RwLock::new(false)))
|
UI_READY.get_or_init(|| Arc::new(RwLock::new(false)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether the UI has finished initialization on the frontend side
|
||||||
|
pub fn is_ui_ready() -> bool {
|
||||||
|
*get_ui_ready().read()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_ui_ready_state() -> &'static Arc<UiReadyState> {
|
fn get_ui_ready_state() -> &'static Arc<UiReadyState> {
|
||||||
UI_READY_STATE.get_or_init(|| Arc::new(UiReadyState::default()))
|
UI_READY_STATE.get_or_init(|| Arc::new(UiReadyState::default()))
|
||||||
}
|
}
|
||||||
@@ -93,7 +128,10 @@ pub fn update_ui_ready_stage(stage: UiReadyStage) {
|
|||||||
pub fn mark_ui_ready() {
|
pub fn mark_ui_ready() {
|
||||||
let mut ready = get_ui_ready().write();
|
let mut ready = get_ui_ready().write();
|
||||||
*ready = true;
|
*ready = true;
|
||||||
logging!(info, Type::Window, true, "UI已标记为完全就绪");
|
logging!(info, Type::Window, true, "UI marked as fully ready");
|
||||||
|
|
||||||
|
// If any deep links were queued while UI was not ready, handle them now
|
||||||
|
// No queued deep links list anymore; early and runtime deep links are deduped
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置UI就绪状态
|
// 重置UI就绪状态
|
||||||
@@ -107,7 +145,83 @@ pub fn reset_ui_ready() {
|
|||||||
let mut stage = state.stage.write();
|
let mut stage = state.stage.write();
|
||||||
*stage = UiReadyStage::NotStarted;
|
*stage = UiReadyStage::NotStarted;
|
||||||
}
|
}
|
||||||
logging!(info, Type::Window, true, "UI就绪状态已重置");
|
logging!(info, Type::Window, true, "UI readiness state has been reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedule robust deep-link handling to avoid races with lightweight mode and window creation
|
||||||
|
pub fn schedule_handle_deep_link(url: String) {
|
||||||
|
AsyncHandler::spawn(move || async move {
|
||||||
|
// Normalize dedup key to the actual subscription URL inside the deep link
|
||||||
|
let dedup_key = (|| {
|
||||||
|
if let Ok(parsed) = Url::parse(&url) {
|
||||||
|
for (k, v) in parsed.query_pairs() {
|
||||||
|
if k == "url" {
|
||||||
|
return percent_decode_str(&v).decode_utf8_lossy().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url.clone()
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Deduplicate: if the same deep/subscription link was handled very recently, skip
|
||||||
|
{
|
||||||
|
let now = Instant::now();
|
||||||
|
let mut last = LAST_DEEP_LINK.get_or_init(|| Mutex::new(None)).lock();
|
||||||
|
if let Some((prev_url, prev_time)) = last.as_ref() {
|
||||||
|
if *prev_url == dedup_key && now.duration_since(*prev_time) < Duration::from_secs(5) {
|
||||||
|
log::warn!(target: "app", "Skip duplicate deep link within 5s: {}", dedup_key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*last = Some((dedup_key.clone(), now));
|
||||||
|
}
|
||||||
|
// Wait until app handle exists
|
||||||
|
for i in 0..100u8 {
|
||||||
|
if Handle::global().app_handle().is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if i % 10 == 0 { logging!(info, Type::Setup, true, "waiting for app handle... ({}ms)", i as u64 * 20); }
|
||||||
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we are not in lightweight mode (webview destroyed)
|
||||||
|
lightweight::exit_lightweight_mode();
|
||||||
|
for _ in 0..150u16 {
|
||||||
|
if !lightweight::is_in_lightweight_mode() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure a window exists ASAP so UI can mount
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
AppHandleManager::global().set_activation_policy_regular();
|
||||||
|
}
|
||||||
|
// If lightweight mode was active, give it a bit of time to unwind before recreating window
|
||||||
|
if lightweight::is_in_lightweight_mode() {
|
||||||
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||||
|
}
|
||||||
|
let _ = WindowManager::show_main_window();
|
||||||
|
|
||||||
|
// Ensure profiles directory exists on cold start
|
||||||
|
if let Ok(dir) = crate::utils::dirs::app_profiles_dir() {
|
||||||
|
if !dir.exists() {
|
||||||
|
let _ = std::fs::create_dir_all(&dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process deep link (add profile regardless of UI state)
|
||||||
|
logging!(info, Type::Setup, true, "processing deep link: {}", dedup_key);
|
||||||
|
if let Err(e) = resolve_scheme(url.clone()).await {
|
||||||
|
log::error!(target: "app", "Deep link handling failed: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If UI is ready, small delay to let listeners settle before finishing
|
||||||
|
if is_ui_ready() {
|
||||||
|
tokio::time::sleep(Duration::from_millis(120)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_unused_port() -> Result<u16> {
|
pub async fn find_unused_port() -> Result<u16> {
|
||||||
@@ -130,12 +244,12 @@ pub async fn find_unused_port() -> Result<u16> {
|
|||||||
/// 异步方式处理启动后的额外任务
|
/// 异步方式处理启动后的额外任务
|
||||||
pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
logging!(info, Type::Setup, true, "开始执行异步设置任务...");
|
logging!(info, Type::Setup, true, "Starting asynchronous setup tasks...");
|
||||||
|
|
||||||
if VERSION.get().is_none() {
|
if VERSION.get().is_none() {
|
||||||
let version = app_handle.package_info().version.to_string();
|
let version = app_handle.package_info().version.to_string();
|
||||||
VERSION.get_or_init(|| {
|
VERSION.get_or_init(|| {
|
||||||
logging!(info, Type::Setup, true, "初始化版本信息: {}", version);
|
logging!(info, Type::Setup, true, "Initializing version information: {}", version);
|
||||||
version.clone()
|
version.clone()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -154,40 +268,40 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(trace, Type::Config, true, "初始化配置...");
|
logging!(trace, Type::Config, true, "Initializing configuration...");
|
||||||
logging_error!(Type::Config, true, Config::init_config().await);
|
logging_error!(Type::Config, true, Config::init_config().await);
|
||||||
|
|
||||||
// 启动时清理冗余的 Profile 文件
|
// 启动时清理冗余的 Profile 文件
|
||||||
logging!(info, Type::Setup, true, "清理冗余的Profile文件...");
|
logging!(info, Type::Setup, true, "Cleaning redundant profile files...");
|
||||||
let profiles = Config::profiles();
|
let profiles = Config::profiles();
|
||||||
if let Err(e) = profiles.latest().auto_cleanup() {
|
if let Err(e) = profiles.latest().auto_cleanup() {
|
||||||
logging!(warn, Type::Setup, true, "启动时清理Profile文件失败: {}", e);
|
logging!(warn, Type::Setup, true, "Failed to clean profile files at startup: {}", e);
|
||||||
} else {
|
} else {
|
||||||
logging!(info, Type::Setup, true, "启动时Profile文件清理完成");
|
logging!(info, Type::Setup, true, "Startup profile files cleanup completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(trace, Type::Core, true, "启动核心管理器...");
|
logging!(trace, Type::Core, true, "Starting core manager...");
|
||||||
logging_error!(Type::Core, true, CoreManager::global().init().await);
|
logging_error!(Type::Core, true, CoreManager::global().init().await);
|
||||||
|
|
||||||
log::trace!(target: "app", "启动内嵌服务器...");
|
log::trace!(target: "app", "Starting embedded server...");
|
||||||
server::embed_server();
|
server::embed_server();
|
||||||
|
|
||||||
logging_error!(Type::Tray, true, tray::Tray::global().init());
|
logging_error!(Type::Tray, true, tray::Tray::global().init());
|
||||||
|
|
||||||
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
||||||
logging!(info, Type::Tray, true, "创建系统托盘...");
|
logging!(info, Type::Tray, true, "Creating system tray...");
|
||||||
let result = tray::Tray::global().create_tray_from_handle(&app_handle);
|
let result = tray::Tray::global().create_tray_from_handle(&app_handle);
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
logging!(info, Type::Tray, true, "系统托盘创建成功");
|
logging!(info, Type::Tray, true, "System tray created successfully");
|
||||||
} else if let Err(e) = result {
|
} else if let Err(e) = result {
|
||||||
logging!(error, Type::Tray, true, "系统托盘创建失败: {}", e);
|
logging!(error, Type::Tray, true, "Failed to create system tray: {}", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logging!(
|
logging!(
|
||||||
error,
|
error,
|
||||||
Type::Tray,
|
Type::Tray,
|
||||||
true,
|
true,
|
||||||
"无法创建系统托盘: app_handle不存在"
|
"Unable to create system tray: app_handle missing"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +337,7 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
|||||||
|
|
||||||
logging_error!(Type::Tray, true, tray::Tray::global().update_part());
|
logging_error!(Type::Tray, true, tray::Tray::global().update_part());
|
||||||
|
|
||||||
logging!(trace, Type::System, true, "初始化热键...");
|
logging!(trace, Type::System, true, "Initializing hotkeys...");
|
||||||
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
|
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
|
||||||
|
|
||||||
let elapsed = start_time.elapsed();
|
let elapsed = start_time.elapsed();
|
||||||
@@ -231,7 +345,7 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
|||||||
info,
|
info,
|
||||||
Type::Setup,
|
Type::Setup,
|
||||||
true,
|
true,
|
||||||
"异步设置任务完成,耗时: {:?}",
|
"Asynchronous task completed, time taken: {:?}",
|
||||||
elapsed
|
elapsed
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -241,7 +355,7 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
|||||||
warn,
|
warn,
|
||||||
Type::Setup,
|
Type::Setup,
|
||||||
true,
|
true,
|
||||||
"异步设置任务耗时较长({:?})",
|
"Asynchronous task setup takes a long time ({:?})",
|
||||||
elapsed
|
elapsed
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -273,12 +387,12 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
info,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"开始创建/显示主窗口, is_show={}",
|
"Creating/showing main window, is_show={}",
|
||||||
is_show
|
is_show
|
||||||
);
|
);
|
||||||
|
|
||||||
if !is_show {
|
if !is_show {
|
||||||
logging!(info, Type::Window, true, "静默模式启动时不创建窗口");
|
logging!(info, Type::Window, true, "Silent start: do not create window");
|
||||||
lightweight::set_lightweight_mode(true);
|
lightweight::set_lightweight_mode(true);
|
||||||
handle::Handle::notify_startup_completed();
|
handle::Handle::notify_startup_completed();
|
||||||
return false;
|
return false;
|
||||||
@@ -286,22 +400,35 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
|
|
||||||
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
logging!(info, Type::Window, true, "主窗口已存在,将显示现有窗口");
|
logging!(info, Type::Window, true, "Main window already exists; will try to show it");
|
||||||
if is_show {
|
if is_show {
|
||||||
if window.is_minimized().unwrap_or(false) {
|
if window.is_minimized().unwrap_or(false) {
|
||||||
logging!(info, Type::Window, true, "窗口已最小化,正在取消最小化");
|
logging!(info, Type::Window, true, "Window is minimized; unminimizing");
|
||||||
let _ = window.unminimize();
|
let _ = window.unminimize();
|
||||||
}
|
}
|
||||||
let _ = window.show();
|
let show_result = window.show();
|
||||||
let _ = window.set_focus();
|
let focus_result = window.set_focus();
|
||||||
|
|
||||||
|
// If showing or focusing fails (possibly destroyed webview after lightweight), fallback to recreate
|
||||||
|
if show_result.is_err() || focus_result.is_err() {
|
||||||
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"Failed to show existing window; will destroy and recreate"
|
||||||
|
);
|
||||||
|
let _ = window.destroy();
|
||||||
|
} else {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
AppHandleManager::global().set_activation_policy_regular();
|
AppHandleManager::global().set_activation_policy_regular();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let creating_lock = get_window_creating_lock();
|
let creating_lock = get_window_creating_lock();
|
||||||
@@ -315,7 +442,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
info,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"窗口创建请求被忽略,因为最近创建过 ({:?}ms)",
|
"Window creation request ignored because recently created ({:?}ms)",
|
||||||
elapsed.as_millis()
|
elapsed.as_millis()
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
@@ -326,7 +453,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
// ScopeGuard 确保创建状态重置,防止 webview 卡死
|
// ScopeGuard 确保创建状态重置,防止 webview 卡死
|
||||||
let _guard = scopeguard::guard(creating, |mut creating_guard| {
|
let _guard = scopeguard::guard(creating, |mut creating_guard| {
|
||||||
*creating_guard = (false, Instant::now());
|
*creating_guard = (false, Instant::now());
|
||||||
logging!(debug, Type::Window, true, "[ScopeGuard] 窗口创建状态已重置");
|
logging!(debug, Type::Window, true, "[ScopeGuard] Window creation state reset");
|
||||||
});
|
});
|
||||||
|
|
||||||
match tauri::WebviewWindowBuilder::new(
|
match tauri::WebviewWindowBuilder::new(
|
||||||
@@ -334,25 +461,25 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
"main", /* the unique window label */
|
"main", /* the unique window label */
|
||||||
tauri::WebviewUrl::App("index.html".into()),
|
tauri::WebviewUrl::App("index.html".into()),
|
||||||
)
|
)
|
||||||
.title("Clash Verge")
|
.title("Koala Clash")
|
||||||
.center()
|
.center()
|
||||||
.decorations(true)
|
.decorations(true)
|
||||||
.fullscreen(false)
|
.fullscreen(false)
|
||||||
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
||||||
.min_inner_size(520.0, 520.0)
|
.min_inner_size(1000.0, 700.0)
|
||||||
.visible(true) // 立即显示窗口,避免用户等待
|
.visible(true) // 立即显示窗口,避免用户等待
|
||||||
.initialization_script(
|
.initialization_script(
|
||||||
r#"
|
r#"
|
||||||
console.log('[Tauri] 窗口初始化脚本开始执行');
|
console.log('[Tauri] Window init script started');
|
||||||
|
|
||||||
function createLoadingOverlay() {
|
function createLoadingOverlay() {
|
||||||
|
|
||||||
if (document.getElementById('initial-loading-overlay')) {
|
if (document.getElementById('initial-loading-overlay')) {
|
||||||
console.log('[Tauri] 加载指示器已存在');
|
console.log('[Tauri] Loading indicator already exists');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Tauri] 创建加载指示器');
|
console.log('[Tauri] Creating loading indicator');
|
||||||
const loadingDiv = document.createElement('div');
|
const loadingDiv = document.createElement('div');
|
||||||
loadingDiv.id = 'initial-loading-overlay';
|
loadingDiv.id = 'initial-loading-overlay';
|
||||||
loadingDiv.innerHTML = `
|
loadingDiv.innerHTML = `
|
||||||
@@ -371,7 +498,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
"></div>
|
"></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 14px; opacity: 0.7;">Loading Clash Verge...</div>
|
<div style="font-size: 14px; opacity: 0.7;">Loading Koala Clash...</div>
|
||||||
</div>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
@@ -403,13 +530,13 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
createLoadingOverlay();
|
createLoadingOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Tauri] 窗口初始化脚本执行完成');
|
console.log('[Tauri] Window init script finished');
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
{
|
{
|
||||||
Ok(newly_created_window) => {
|
Ok(newly_created_window) => {
|
||||||
logging!(debug, Type::Window, true, "主窗口实例创建成功");
|
logging!(debug, Type::Window, true, "Main window instance created successfully");
|
||||||
|
|
||||||
update_ui_ready_stage(UiReadyStage::NotStarted);
|
update_ui_ready_stage(UiReadyStage::NotStarted);
|
||||||
|
|
||||||
@@ -419,7 +546,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
debug,
|
debug,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"异步窗口任务开始 (启动已标记完成)"
|
"Async window task started (startup marked completed)"
|
||||||
);
|
);
|
||||||
|
|
||||||
// 先运行轻量模式检测
|
// 先运行轻量模式检测
|
||||||
@@ -430,7 +557,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
debug,
|
debug,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"发送 verge://startup-completed 事件"
|
"Sending verge://startup-completed event"
|
||||||
);
|
);
|
||||||
handle::Handle::notify_startup_completed();
|
handle::Handle::notify_startup_completed();
|
||||||
|
|
||||||
@@ -440,7 +567,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
// 立即显示窗口
|
// 立即显示窗口
|
||||||
let _ = window_clone.show();
|
let _ = window_clone.show();
|
||||||
let _ = window_clone.set_focus();
|
let _ = window_clone.set_focus();
|
||||||
logging!(info, Type::Window, true, "窗口已立即显示");
|
logging!(info, Type::Window, true, "Window shown immediately");
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
AppHandleManager::global().set_activation_policy_regular();
|
AppHandleManager::global().set_activation_policy_regular();
|
||||||
@@ -456,7 +583,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
info,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"开始监控UI加载状态 (最多{}秒)...",
|
"Start monitoring UI load status (up to {} seconds)...",
|
||||||
timeout_seconds
|
timeout_seconds
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -475,7 +602,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
debug,
|
debug,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"UI加载状态检查... ({}秒)",
|
"UI loading status check... ({}s)",
|
||||||
check_count / 10
|
check_count / 10
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -485,7 +612,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
|
|
||||||
match wait_result {
|
match wait_result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Window, true, "UI已完全加载就绪");
|
logging!(info, Type::Window, true, "UI fully loaded and ready");
|
||||||
// 移除初始加载指示器
|
// 移除初始加载指示器
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
if let Some(window) = handle::Handle::global().get_window() {
|
||||||
let _ = window.eval(r#"
|
let _ = window.eval(r#"
|
||||||
@@ -502,7 +629,7 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
warn,
|
warn,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"UI加载监控超时({}秒),但窗口已正常显示",
|
"UI load monitoring timed out ({}s), but window is already visible",
|
||||||
timeout_seconds
|
timeout_seconds
|
||||||
);
|
);
|
||||||
*get_ui_ready().write() = true;
|
*get_ui_ready().write() = true;
|
||||||
@@ -510,20 +637,20 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
logging!(info, Type::Window, true, "窗口显示流程完成");
|
logging!(info, Type::Window, true, "Window display flow completed");
|
||||||
} else {
|
} else {
|
||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"is_show为false,窗口保持隐藏状态"
|
"is_show is false; keeping window hidden"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Window, true, "主窗口构建失败: {}", e);
|
logging!(error, Type::Window, true, "Failed to build main window: {}", e);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -548,33 +675,30 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if link_parsed.scheme() == "clash" || link_parsed.scheme() == "clash-verge" {
|
if link_parsed.scheme() == "clash" || link_parsed.scheme() == "koala-clash" {
|
||||||
let name = link_parsed
|
let mut name: Option<String> = None;
|
||||||
.query_pairs()
|
let mut url_param: Option<String> = None;
|
||||||
.find(|(key, _)| key == "name")
|
|
||||||
.map(|(_, value)| value.into_owned());
|
|
||||||
|
|
||||||
let url_param = if let Some(query) = link_parsed.query() {
|
for (key, value) in link_parsed.query_pairs() {
|
||||||
let prefix = "url=";
|
match key.as_ref() {
|
||||||
if let Some(pos) = query.find(prefix) {
|
"name" => name = Some(value.into_owned()),
|
||||||
let raw_url = &query[pos + prefix.len()..];
|
"url" => {
|
||||||
Some(percent_decode_str(raw_url).decode_utf8_lossy().to_string())
|
url_param = Some(percent_decode_str(&value).decode_utf8_lossy().to_string())
|
||||||
} else {
|
}
|
||||||
None
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
match url_param {
|
match url_param {
|
||||||
Some(url) => {
|
Some(url) => {
|
||||||
log::info!(target:"app", "decoded subscription url: {url}");
|
log::info!(target:"app", "decoded subscription url: {url}");
|
||||||
|
|
||||||
create_window(false);
|
// Deep link inside resolver is now executed via schedule_handle_deep_link
|
||||||
match PrfItem::from_url(url.as_ref(), name, None, None).await {
|
match PrfItem::from_url(url.as_ref(), name, None, None).await {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
let uid = item.uid.clone().unwrap();
|
let uid = item.uid.clone().unwrap();
|
||||||
let _ = wrap_err!(Config::profiles().data().append_item(item));
|
let _ = wrap_err!(Config::profiles().data().append_item(item));
|
||||||
|
// If UI not ready yet, message will be queued and flushed on ready
|
||||||
handle::Handle::notice_message("import_sub_url::ok", uid);
|
handle::Handle::notice_message("import_sub_url::ok", uid);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ use crate::{
|
|||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::logging::Type,
|
utils::logging::Type,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use port_scanner::local_port_available;
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
|
||||||
@@ -17,32 +16,6 @@ struct QueryParam {
|
|||||||
param: String,
|
param: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// check whether there is already exists
|
|
||||||
pub async fn check_singleton() -> Result<()> {
|
|
||||||
let port = IVerge::get_singleton_port();
|
|
||||||
if !local_port_available(port) {
|
|
||||||
let argvs: Vec<String> = std::env::args().collect();
|
|
||||||
if argvs.len() > 1 {
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
{
|
|
||||||
let param = argvs[1].as_str();
|
|
||||||
if param.starts_with("clash:") {
|
|
||||||
let _ = reqwest::get(format!(
|
|
||||||
"http://127.0.0.1:{port}/commands/scheme?param={param}"
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = reqwest::get(format!("http://127.0.0.1:{port}/commands/visible")).await;
|
|
||||||
}
|
|
||||||
log::error!("failed to setup singleton listen server");
|
|
||||||
bail!("app exists");
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The embed server only be used to implement singleton process
|
/// The embed server only be used to implement singleton process
|
||||||
/// maybe it can be used as pac server later
|
/// maybe it can be used as pac server later
|
||||||
pub fn embed_server() {
|
pub fn embed_server() {
|
||||||
|
|||||||
177
src-tauri/src/utils/sys_info.rs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone)]
|
||||||
|
pub struct SystemInfo {
|
||||||
|
pub hwid: String,
|
||||||
|
pub os_type: String,
|
||||||
|
pub os_ver: String,
|
||||||
|
pub device_model: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static SYSTEM_INFO: Lazy<SystemInfo> = Lazy::new(|| {
|
||||||
|
let hwid = machine_uid::get().unwrap_or_else(|_| "unknown_hwid".to_string());
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
SystemInfo {
|
||||||
|
hwid,
|
||||||
|
os_type: "Windows".to_string(),
|
||||||
|
os_ver: get_windows_build_name(),
|
||||||
|
device_model: get_windows_edition(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
SystemInfo {
|
||||||
|
hwid,
|
||||||
|
os_type: "macOS".to_string(),
|
||||||
|
os_ver: get_macos_version(),
|
||||||
|
device_model: get_mac_model(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
SystemInfo {
|
||||||
|
hwid,
|
||||||
|
os_type: "Linux".to_string(),
|
||||||
|
os_ver: get_linux_distro_version(),
|
||||||
|
device_model: get_linux_distro_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn get_system_info() -> &'static SystemInfo {
|
||||||
|
&SYSTEM_INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn get_windows_build_name() -> String {
|
||||||
|
use winreg::enums::*;
|
||||||
|
use winreg::RegKey;
|
||||||
|
|
||||||
|
let hklm = match RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion") {
|
||||||
|
Ok(key) => key,
|
||||||
|
Err(_) => return "Unknown".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Пытаемся получить DisplayVersion (например, "24H2", "23H2", "22H2")
|
||||||
|
if let Ok(display_version) = hklm.get_value::<String, _>("DisplayVersion") {
|
||||||
|
return display_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если DisplayVersion нет, получаем ReleaseId
|
||||||
|
if let Ok(release_id) = hklm.get_value::<String, _>("ReleaseId") {
|
||||||
|
return release_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// В крайнем случае возвращаем номер сборки
|
||||||
|
if let Ok(build) = hklm.get_value::<String, _>("CurrentBuild") {
|
||||||
|
return format!("Build {}", build);
|
||||||
|
}
|
||||||
|
|
||||||
|
"Unknown".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn get_windows_edition() -> String {
|
||||||
|
use winreg::enums::*;
|
||||||
|
use winreg::RegKey;
|
||||||
|
|
||||||
|
let hklm = match RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion") {
|
||||||
|
Ok(key) => key,
|
||||||
|
Err(_) => return "Windows".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let product_name = hklm.get_value::<String, _>("ProductName").unwrap_or_else(|_| "Windows".to_string());
|
||||||
|
|
||||||
|
product_name
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn get_macos_version() -> String {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let output = Command::new("sw_vers")
|
||||||
|
.arg("-productVersion")
|
||||||
|
.output();
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(output) if output.status.success() => {
|
||||||
|
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Fallback to os_info
|
||||||
|
let os_info = os_info::get();
|
||||||
|
os_info.version().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn get_mac_model() -> String {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Получаем идентификатор модели (например, "MacBookPro18,3")
|
||||||
|
let output = Command::new("sysctl")
|
||||||
|
.arg("-n")
|
||||||
|
.arg("hw.model")
|
||||||
|
.output();
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(output) if output.status.success() => {
|
||||||
|
let model = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
|
if !model.is_empty() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если не получилось, пробуем получить marketing name
|
||||||
|
let output = Command::new("system_profiler")
|
||||||
|
.arg("SPHardwareDataType")
|
||||||
|
.output();
|
||||||
|
|
||||||
|
if let Ok(output) = output {
|
||||||
|
if output.status.success() {
|
||||||
|
let text = String::from_utf8_lossy(&output.stdout);
|
||||||
|
for line in text.lines() {
|
||||||
|
if line.contains("Model Name:") {
|
||||||
|
if let Some(name) = line.split(':').nth(1) {
|
||||||
|
return name.trim().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"Mac".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn get_linux_distro_version() -> String {
|
||||||
|
let os_info = os_info::get();
|
||||||
|
|
||||||
|
// os_info::Version может содержать версию дистрибутива
|
||||||
|
let version = os_info.version();
|
||||||
|
let version_str = version.to_string();
|
||||||
|
|
||||||
|
if version_str != "Unknown" && !version_str.is_empty() {
|
||||||
|
version_str
|
||||||
|
} else {
|
||||||
|
"Unknown".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn get_linux_distro_name() -> String {
|
||||||
|
let os_info = os_info::get();
|
||||||
|
|
||||||
|
// Получаем тип дистрибутива (Ubuntu, Fedora, etc.)
|
||||||
|
let os_type = os_info.os_type();
|
||||||
|
|
||||||
|
format!("{}", os_type)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Some config file template
|
//! Some config file template
|
||||||
|
|
||||||
/// template for new a profile item
|
/// template for new a profile item
|
||||||
pub const ITEM_LOCAL: &str = "# Profile Template for Clash Verge
|
pub const ITEM_LOCAL: &str = "# Profile Template for Koala Clash
|
||||||
|
|
||||||
proxies: []
|
proxies: []
|
||||||
|
|
||||||
@@ -11,13 +11,13 @@ rules: []
|
|||||||
";
|
";
|
||||||
|
|
||||||
/// enhanced profile
|
/// enhanced profile
|
||||||
pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Clash Verge
|
pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Koala Clash
|
||||||
|
|
||||||
profile:
|
profile:
|
||||||
store-selected: true
|
store-selected: true
|
||||||
";
|
";
|
||||||
|
|
||||||
pub const ITEM_MERGE_EMPTY: &str = "# Profile Enhancement Merge Template for Clash Verge
|
pub const ITEM_MERGE_EMPTY: &str = "# Profile Enhancement Merge Template for Koala Clash
|
||||||
|
|
||||||
";
|
";
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ function main(config, profileName) {
|
|||||||
";
|
";
|
||||||
|
|
||||||
/// enhanced profile
|
/// enhanced profile
|
||||||
pub const ITEM_RULES: &str = "# Profile Enhancement Rules Template for Clash Verge
|
pub const ITEM_RULES: &str = "# Profile Enhancement Rules Template for Koala Clash
|
||||||
|
|
||||||
prepend: []
|
prepend: []
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ delete: []
|
|||||||
";
|
";
|
||||||
|
|
||||||
/// enhanced profile
|
/// enhanced profile
|
||||||
pub const ITEM_PROXIES: &str = "# Profile Enhancement Proxies Template for Clash Verge
|
pub const ITEM_PROXIES: &str = "# Profile Enhancement Proxies Template for Koala Clash
|
||||||
|
|
||||||
prepend: []
|
prepend: []
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ delete: []
|
|||||||
";
|
";
|
||||||
|
|
||||||
/// enhanced profile
|
/// enhanced profile
|
||||||
pub const ITEM_GROUPS: &str = "# Profile Enhancement Groups Template for Clash Verge
|
pub const ITEM_GROUPS: &str = "# Profile Enhancement Groups Template for Koala Clash
|
||||||
|
|
||||||
prepend: []
|
prepend: []
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ fn get_window_operation_debounce() -> &'static Mutex<Instant> {
|
|||||||
|
|
||||||
fn should_handle_window_operation() -> bool {
|
fn should_handle_window_operation() -> bool {
|
||||||
if WINDOW_OPERATION_IN_PROGRESS.load(Ordering::Acquire) {
|
if WINDOW_OPERATION_IN_PROGRESS.load(Ordering::Acquire) {
|
||||||
log::warn!(target: "app", "[防抖] 窗口操作已在进行中,跳过重复调用");
|
log::warn!(target: "app", "[debounce] Window operation already in progress, skipping duplicate call");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,16 +62,16 @@ fn should_handle_window_operation() -> bool {
|
|||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let elapsed = now.duration_since(*last_operation);
|
let elapsed = now.duration_since(*last_operation);
|
||||||
|
|
||||||
log::debug!(target: "app", "[防抖] 检查窗口操作间隔: {}ms (需要>={}ms)",
|
log::debug!(target: "app", "[debounce] Checking window operation interval: {}ms (need >={}ms)",
|
||||||
elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS);
|
elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS);
|
||||||
|
|
||||||
if elapsed >= Duration::from_millis(WINDOW_OPERATION_DEBOUNCE_MS) {
|
if elapsed >= Duration::from_millis(WINDOW_OPERATION_DEBOUNCE_MS) {
|
||||||
*last_operation = now;
|
*last_operation = now;
|
||||||
WINDOW_OPERATION_IN_PROGRESS.store(true, Ordering::Release);
|
WINDOW_OPERATION_IN_PROGRESS.store(true, Ordering::Release);
|
||||||
log::info!(target: "app", "[防抖] 窗口操作被允许执行");
|
log::info!(target: "app", "[debounce] Window operation allowed to execute");
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
log::warn!(target: "app", "[防抖] 窗口操作被防抖机制忽略,距离上次操作 {}ms < {}ms",
|
log::warn!(target: "app", "[debounce] Window operation ignored by debounce: {}ms since last < {}ms",
|
||||||
elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS);
|
elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ impl WindowManager {
|
|||||||
finish_window_operation();
|
finish_window_operation();
|
||||||
});
|
});
|
||||||
|
|
||||||
logging!(info, Type::Window, true, "开始智能显示主窗口");
|
logging!(info, Type::Window, true, "Starting smart show for main window");
|
||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
@@ -140,18 +140,18 @@ impl WindowManager {
|
|||||||
|
|
||||||
match current_state {
|
match current_state {
|
||||||
WindowState::NotExist => {
|
WindowState::NotExist => {
|
||||||
logging!(info, Type::Window, true, "窗口不存在,创建新窗口");
|
logging!(info, Type::Window, true, "Main window not found; creating new window");
|
||||||
if Self::create_new_window() {
|
if Self::create_new_window() {
|
||||||
logging!(info, Type::Window, true, "窗口创建成功");
|
logging!(info, Type::Window, true, "Window created successfully");
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
WindowOperationResult::Created
|
WindowOperationResult::Created
|
||||||
} else {
|
} else {
|
||||||
logging!(warn, Type::Window, true, "窗口创建失败");
|
logging!(warn, Type::Window, true, "Window creation failed");
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowState::VisibleFocused => {
|
WindowState::VisibleFocused => {
|
||||||
logging!(info, Type::Window, true, "窗口已经可见且有焦点,无需操作");
|
logging!(info, Type::Window, true, "Window already visible and focused; no action needed");
|
||||||
WindowOperationResult::NoAction
|
WindowOperationResult::NoAction
|
||||||
}
|
}
|
||||||
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
|
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
|
||||||
@@ -184,14 +184,14 @@ impl WindowManager {
|
|||||||
finish_window_operation();
|
finish_window_operation();
|
||||||
});
|
});
|
||||||
|
|
||||||
logging!(info, Type::Window, true, "开始切换主窗口显示状态");
|
logging!(info, Type::Window, true, "Toggling main window visibility");
|
||||||
|
|
||||||
let current_state = Self::get_main_window_state();
|
let current_state = Self::get_main_window_state();
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"当前窗口状态: {:?} | 详细状态: {}",
|
"Current window state: {:?} | Details: {}",
|
||||||
current_state,
|
current_state,
|
||||||
Self::get_window_status_info()
|
Self::get_window_status_info()
|
||||||
);
|
);
|
||||||
@@ -199,7 +199,7 @@ impl WindowManager {
|
|||||||
match current_state {
|
match current_state {
|
||||||
WindowState::NotExist => {
|
WindowState::NotExist => {
|
||||||
// 窗口不存在,创建新窗口
|
// 窗口不存在,创建新窗口
|
||||||
logging!(info, Type::Window, true, "窗口不存在,将创建新窗口");
|
logging!(info, Type::Window, true, "Main window not found; will create new window");
|
||||||
// 由于已经有防抖保护,直接调用内部方法
|
// 由于已经有防抖保护,直接调用内部方法
|
||||||
if Self::create_new_window() {
|
if Self::create_new_window() {
|
||||||
WindowOperationResult::Created
|
WindowOperationResult::Created
|
||||||
@@ -212,26 +212,26 @@ impl WindowManager {
|
|||||||
info,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"窗口可见(焦点状态: {}),将隐藏窗口",
|
"Window visible (focused: {}), hiding window",
|
||||||
if current_state == WindowState::VisibleFocused {
|
if current_state == WindowState::VisibleFocused {
|
||||||
"有焦点"
|
"focused"
|
||||||
} else {
|
} else {
|
||||||
"无焦点"
|
"unfocused"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if let Some(window) = Self::get_main_window() {
|
if let Some(window) = Self::get_main_window() {
|
||||||
match window.hide() {
|
match window.hide() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Window, true, "窗口已成功隐藏");
|
logging!(info, Type::Window, true, "Window hidden successfully");
|
||||||
WindowOperationResult::Hidden
|
WindowOperationResult::Hidden
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(warn, Type::Window, true, "隐藏窗口失败: {}", e);
|
logging!(warn, Type::Window, true, "Failed to hide window: {}", e);
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logging!(warn, Type::Window, true, "无法获取窗口实例");
|
logging!(warn, Type::Window, true, "Unable to get window instance");
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,12 +240,12 @@ impl WindowManager {
|
|||||||
info,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"窗口存在但被隐藏或最小化,将激活窗口"
|
"Window exists but is hidden or minimized; activating"
|
||||||
);
|
);
|
||||||
if let Some(window) = Self::get_main_window() {
|
if let Some(window) = Self::get_main_window() {
|
||||||
Self::activate_window(&window)
|
Self::activate_window(&window)
|
||||||
} else {
|
} else {
|
||||||
logging!(warn, Type::Window, true, "无法获取窗口实例");
|
logging!(warn, Type::Window, true, "Unable to get window instance");
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,35 +254,35 @@ impl WindowManager {
|
|||||||
|
|
||||||
/// 激活窗口(取消最小化、显示、设置焦点)
|
/// 激活窗口(取消最小化、显示、设置焦点)
|
||||||
fn activate_window(window: &WebviewWindow<Wry>) -> WindowOperationResult {
|
fn activate_window(window: &WebviewWindow<Wry>) -> WindowOperationResult {
|
||||||
logging!(info, Type::Window, true, "开始激活窗口");
|
logging!(info, Type::Window, true, "Starting to activate window");
|
||||||
|
|
||||||
let mut operations_successful = true;
|
let mut operations_successful = true;
|
||||||
|
|
||||||
// 1. 如果窗口最小化,先取消最小化
|
// 1. 如果窗口最小化,先取消最小化
|
||||||
if window.is_minimized().unwrap_or(false) {
|
if window.is_minimized().unwrap_or(false) {
|
||||||
logging!(info, Type::Window, true, "窗口已最小化,正在取消最小化");
|
logging!(info, Type::Window, true, "Window minimized; unminimizing");
|
||||||
if let Err(e) = window.unminimize() {
|
if let Err(e) = window.unminimize() {
|
||||||
logging!(warn, Type::Window, true, "取消最小化失败: {}", e);
|
logging!(warn, Type::Window, true, "Failed to unminimize window: {}", e);
|
||||||
operations_successful = false;
|
operations_successful = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 显示窗口
|
// 2. 显示窗口
|
||||||
if let Err(e) = window.show() {
|
if let Err(e) = window.show() {
|
||||||
logging!(warn, Type::Window, true, "显示窗口失败: {}", e);
|
logging!(warn, Type::Window, true, "Failed to show window: {}", e);
|
||||||
operations_successful = false;
|
operations_successful = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 设置焦点
|
// 3. 设置焦点
|
||||||
if let Err(e) = window.set_focus() {
|
if let Err(e) = window.set_focus() {
|
||||||
logging!(warn, Type::Window, true, "设置窗口焦点失败: {}", e);
|
logging!(warn, Type::Window, true, "Failed to set window focus: {}", e);
|
||||||
operations_successful = false;
|
operations_successful = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 平台特定的激活策略
|
// 4. 平台特定的激活策略
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
logging!(info, Type::Window, true, "应用 macOS 特定的激活策略");
|
logging!(info, Type::Window, true, "Applying macOS-specific activation policy");
|
||||||
AppHandleManager::global().set_activation_policy_regular();
|
AppHandleManager::global().set_activation_policy_regular();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +294,7 @@ impl WindowManager {
|
|||||||
debug,
|
debug,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"设置置顶失败(非关键错误): {}",
|
"Failed to set always-on-top (non-critical): {}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -304,38 +304,38 @@ impl WindowManager {
|
|||||||
debug,
|
debug,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"取消置顶失败(非关键错误): {}",
|
"Failed to unset always-on-top (non-critical): {}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if operations_successful {
|
if operations_successful {
|
||||||
logging!(info, Type::Window, true, "窗口激活成功");
|
logging!(info, Type::Window, true, "Window activation successful");
|
||||||
WindowOperationResult::Shown
|
WindowOperationResult::Shown
|
||||||
} else {
|
} else {
|
||||||
logging!(warn, Type::Window, true, "窗口激活部分失败");
|
logging!(warn, Type::Window, true, "Window activation partially failed");
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 隐藏主窗口
|
/// 隐藏主窗口
|
||||||
pub fn hide_main_window() -> WindowOperationResult {
|
pub fn hide_main_window() -> WindowOperationResult {
|
||||||
logging!(info, Type::Window, true, "开始隐藏主窗口");
|
logging!(info, Type::Window, true, "Starting to hide main window");
|
||||||
|
|
||||||
match Self::get_main_window() {
|
match Self::get_main_window() {
|
||||||
Some(window) => match window.hide() {
|
Some(window) => match window.hide() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Window, true, "窗口已隐藏");
|
logging!(info, Type::Window, true, "Window hidden");
|
||||||
WindowOperationResult::Hidden
|
WindowOperationResult::Hidden
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(warn, Type::Window, true, "隐藏窗口失败: {}", e);
|
logging!(warn, Type::Window, true, "Failed to hide window: {}", e);
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
logging!(info, Type::Window, true, "窗口不存在,无需隐藏");
|
logging!(info, Type::Window, true, "Window does not exist; nothing to hide");
|
||||||
WindowOperationResult::NoAction
|
WindowOperationResult::NoAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,7 +376,7 @@ impl WindowManager {
|
|||||||
let is_minimized = Self::is_main_window_minimized();
|
let is_minimized = Self::is_main_window_minimized();
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"窗口状态: {state:?} | 可见: {is_visible} | 有焦点: {is_focused} | 最小化: {is_minimized}"
|
"WindowState: {state:?} | visible: {is_visible} | focused: {is_focused} | minimized: {is_minimized}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"version": "2.3.2",
|
"version": "0.2.8",
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"longDescription": "Clash Verge Rev",
|
"longDescription": "Koala Clash",
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
@@ -11,12 +11,18 @@
|
|||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
"resources": ["resources", "resources/locales/*"],
|
"resources": [
|
||||||
"publisher": "Clash Verge Rev",
|
"resources",
|
||||||
"externalBin": ["sidecar/verge-mihomo", "sidecar/verge-mihomo-alpha"],
|
"resources/locales/*"
|
||||||
|
],
|
||||||
|
"publisher": "Koala Clash",
|
||||||
|
"externalBin": [
|
||||||
|
"sidecar/koala-mihomo",
|
||||||
|
"sidecar/koala-mihomo-alpha"
|
||||||
|
],
|
||||||
"copyright": "GNU General Public License v3.0",
|
"copyright": "GNU General Public License v3.0",
|
||||||
"category": "DeveloperTool",
|
"category": "DeveloperTool",
|
||||||
"shortDescription": "Clash Verge Rev",
|
"shortDescription": "Koala Clash",
|
||||||
"createUpdaterArtifacts": true
|
"createUpdaterArtifacts": true
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
@@ -25,18 +31,14 @@
|
|||||||
"beforeDevCommand": "pnpm run web:dev",
|
"beforeDevCommand": "pnpm run web:dev",
|
||||||
"devUrl": "http://localhost:3000/"
|
"devUrl": "http://localhost:3000/"
|
||||||
},
|
},
|
||||||
"productName": "Clash Verge",
|
"productName": "Koala Clash",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERCQjQ1QjQ0QUJDQTU1RTkKUldUcFZjcXJSRnUwMjdXSERoZVQ1R0hHRDMrT3VkSmpvbDJmb01sN3ZpYWhVYnEwaWpYUWU4YU0K",
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
|
"https://github.com/coolcoala/clash-verge-rev-lite/releases/download/updater/update.json",
|
||||||
"https://gh-proxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
|
"https://github.com/coolcoala/clash-verge-rev-lite/releases/download/updater-alpha/update-alpha.json"
|
||||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json",
|
|
||||||
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater-alpha/update-alpha-proxy.json",
|
|
||||||
"https://gh-proxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater-alpha/update-alpha-proxy.json",
|
|
||||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater-alpha/update-alpha.json"
|
|
||||||
],
|
],
|
||||||
"windows": {
|
"windows": {
|
||||||
"installMode": "basicUi"
|
"installMode": "basicUi"
|
||||||
@@ -44,15 +46,25 @@
|
|||||||
},
|
},
|
||||||
"deep-link": {
|
"deep-link": {
|
||||||
"desktop": {
|
"desktop": {
|
||||||
"schemes": ["clash", "clash-verge"]
|
"schemes": [
|
||||||
|
"clash",
|
||||||
|
"koala-clash"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"security": {
|
"security": {
|
||||||
"capabilities": ["desktop-capability", "migrated"],
|
"capabilities": [
|
||||||
|
"desktop-capability",
|
||||||
|
"migrated"
|
||||||
|
],
|
||||||
"assetProtocol": {
|
"assetProtocol": {
|
||||||
"scope": ["$APPDATA/**", "$RESOURCE/../**", "**"],
|
"scope": [
|
||||||
|
"$APPDATA/**",
|
||||||
|
"$RESOURCE/../**",
|
||||||
|
"**"
|
||||||
|
],
|
||||||
"enable": true
|
"enable": true
|
||||||
},
|
},
|
||||||
"csp": null
|
"csp": null
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["deb", "rpm"],
|
"targets": ["deb", "rpm"],
|
||||||
"linux": {
|
"linux": {
|
||||||
"deb": {
|
"deb": {
|
||||||
"depends": ["openssl"],
|
"depends": ["openssl"],
|
||||||
"desktopTemplate": "./packages/linux/clash-verge.desktop",
|
"desktopTemplate": "./packages/linux/koala-clash.desktop",
|
||||||
"provides": ["clash-verge"],
|
"provides": ["koala-clash"],
|
||||||
"conflicts": ["clash-verge"],
|
"conflicts": ["koala-clash"],
|
||||||
"replaces": ["clash-verge"],
|
"replaces": ["koala-clash"],
|
||||||
"postInstallScript": "./packages/linux/post-install.sh",
|
"postInstallScript": "./packages/linux/post-install.sh",
|
||||||
"preRemoveScript": "./packages/linux/pre-remove.sh"
|
"preRemoveScript": "./packages/linux/pre-remove.sh"
|
||||||
},
|
},
|
||||||
"rpm": {
|
"rpm": {
|
||||||
"depends": ["openssl"],
|
"depends": ["openssl"],
|
||||||
"desktopTemplate": "./packages/linux/clash-verge.desktop",
|
"desktopTemplate": "./packages/linux/koala-clash.desktop",
|
||||||
"provides": ["clash-verge"],
|
"provides": ["koala-clash"],
|
||||||
"conflicts": ["clash-verge"],
|
"conflicts": ["koala-clash"],
|
||||||
"obsoletes": ["clash-verge"],
|
"obsoletes": ["koala-clash"],
|
||||||
"postInstallScript": "./packages/linux/post-install.sh",
|
"postInstallScript": "./packages/linux/post-install.sh",
|
||||||
"preRemoveScript": "./packages/linux/pre-remove.sh"
|
"preRemoveScript": "./packages/linux/pre-remove.sh"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"externalBin": [
|
"externalBin": [
|
||||||
"./resources/clash-verge-service",
|
"./resources/koala-clash-service",
|
||||||
"./resources/install-service",
|
"./resources/install-service",
|
||||||
"./resources/uninstall-service",
|
"./resources/uninstall-service",
|
||||||
"./sidecar/verge-mihomo",
|
"./sidecar/koala-mihomo",
|
||||||
"./sidecar/verge-mihomo-alpha"
|
"./sidecar/koala-mihomo-alpha"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"productName": "Clash Verge",
|
"productName": "Koala Clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["app", "dmg"],
|
"targets": ["app", "dmg"],
|
||||||
"macOS": {
|
"macOS": {
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
"background": "images/background.png",
|
"background": "images/background.png",
|
||||||
"appPosition": {
|
"appPosition": {
|
||||||
"x": 180,
|
"x": 180,
|
||||||
"y": 170
|
"y": 200
|
||||||
},
|
},
|
||||||
"applicationFolderPosition": {
|
"applicationFolderPosition": {
|
||||||
"x": 480,
|
"x": 480,
|
||||||
"y": 170
|
"y": 200
|
||||||
},
|
},
|
||||||
"windowSize": {
|
"windowSize": {
|
||||||
"height": 400,
|
"height": 400,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["nsis"],
|
"targets": ["nsis"],
|
||||||
"windows": {
|
"windows": {
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"nsis": {
|
"nsis": {
|
||||||
"displayLanguageSelector": true,
|
"displayLanguageSelector": true,
|
||||||
"installerIcon": "icons/icon.ico",
|
"installerIcon": "icons/icon.ico",
|
||||||
"languages": ["SimpChinese", "English"],
|
"languages": ["Russian", "English"],
|
||||||
"installMode": "perMachine",
|
"installMode": "perMachine",
|
||||||
"template": "./packages/windows/installer.nsi"
|
"template": "./packages/windows/installer.nsi"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["nsis"],
|
"targets": ["nsis"],
|
||||||
"windows": {
|
"windows": {
|
||||||
@@ -9,12 +9,12 @@
|
|||||||
"timestampUrl": "",
|
"timestampUrl": "",
|
||||||
"webviewInstallMode": {
|
"webviewInstallMode": {
|
||||||
"type": "fixedRuntime",
|
"type": "fixedRuntime",
|
||||||
"path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.arm64/"
|
"path": "./Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.arm64/"
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"displayLanguageSelector": true,
|
"displayLanguageSelector": true,
|
||||||
"installerIcon": "icons/icon.ico",
|
"installerIcon": "icons/icon.ico",
|
||||||
"languages": ["SimpChinese", "English"],
|
"languages": ["Russian", "English"],
|
||||||
"installMode": "perMachine",
|
"installMode": "perMachine",
|
||||||
"template": "./packages/windows/installer.nsi"
|
"template": "./packages/windows/installer.nsi"
|
||||||
}
|
}
|
||||||
@@ -25,10 +25,9 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"dialog": false,
|
"dialog": false,
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
"https://github.com/coolcoala/clash-verge-rev-lite/releases/download/updater/update-fixed-webview2.json"
|
||||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
|
|
||||||
],
|
],
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERCQjQ1QjQ0QUJDQTU1RTkKUldUcFZjcXJSRnUwMjdXSERoZVQ1R0hHRDMrT3VkSmpvbDJmb01sN3ZpYWhVYnEwaWpYUWU4YU0K"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["nsis"],
|
"targets": ["nsis"],
|
||||||
"windows": {
|
"windows": {
|
||||||
@@ -9,12 +9,12 @@
|
|||||||
"timestampUrl": "",
|
"timestampUrl": "",
|
||||||
"webviewInstallMode": {
|
"webviewInstallMode": {
|
||||||
"type": "fixedRuntime",
|
"type": "fixedRuntime",
|
||||||
"path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.x64/"
|
"path": "./Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.x64/"
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"displayLanguageSelector": true,
|
"displayLanguageSelector": true,
|
||||||
"installerIcon": "icons/icon.ico",
|
"installerIcon": "icons/icon.ico",
|
||||||
"languages": ["SimpChinese", "English"],
|
"languages": ["Russian", "English"],
|
||||||
"installMode": "perMachine",
|
"installMode": "perMachine",
|
||||||
"template": "./packages/windows/installer.nsi"
|
"template": "./packages/windows/installer.nsi"
|
||||||
}
|
}
|
||||||
@@ -25,10 +25,9 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"dialog": false,
|
"dialog": false,
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
"https://github.com/coolcoala/clash-verge-rev-lite/releases/download/updater/update-fixed-webview2.json"
|
||||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
|
|
||||||
],
|
],
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERCQjQ1QjQ0QUJDQTU1RTkKUldUcFZjcXJSRnUwMjdXSERoZVQ1R0hHRDMrT3VkSmpvbDJmb01sN3ZpYWhVYnEwaWpYUWU4YU0K"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.koala-clash",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["nsis"],
|
"targets": ["nsis"],
|
||||||
"windows": {
|
"windows": {
|
||||||
@@ -9,12 +9,12 @@
|
|||||||
"timestampUrl": "",
|
"timestampUrl": "",
|
||||||
"webviewInstallMode": {
|
"webviewInstallMode": {
|
||||||
"type": "fixedRuntime",
|
"type": "fixedRuntime",
|
||||||
"path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.x86/"
|
"path": "./Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.x86/"
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"displayLanguageSelector": true,
|
"displayLanguageSelector": true,
|
||||||
"installerIcon": "icons/icon.ico",
|
"installerIcon": "icons/icon.ico",
|
||||||
"languages": ["SimpChinese", "English"],
|
"languages": ["Russian", "English"],
|
||||||
"installMode": "perMachine",
|
"installMode": "perMachine",
|
||||||
"template": "./packages/windows/installer.nsi"
|
"template": "./packages/windows/installer.nsi"
|
||||||
}
|
}
|
||||||
@@ -25,10 +25,9 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"dialog": false,
|
"dialog": false,
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
"https://github.com/coolcoala/clash-verge-rev-lite/releases/download/updater/update-fixed-webview2.json"
|
||||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
|
|
||||||
],
|
],
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERCQjQ1QjQ0QUJDQTU1RTkKUldUcFZjcXJSRnUwMjdXSERoZVQ1R0hHRDMrT3VkSmpvbDJmb01sN3ZpYWhVYnEwaWpYUWU4YU0K"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,4 @@ function App() {
|
|||||||
</AppDataProvider>
|
</AppDataProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||