Compare commits
462 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a04dd0e27 | ||
|
|
f6a4e8987d | ||
|
|
734d0331bc | ||
|
|
7d9cededeb | ||
|
|
3749da0fbf | ||
|
|
61093eb8d6 | ||
|
|
b2ba3cd20b | ||
|
|
d56d56e2df | ||
|
|
99cbc449c2 | ||
|
|
4ece5c4315 | ||
|
|
a597a4611d | ||
|
|
253ae118df | ||
|
|
11fe93b8c6 | ||
|
|
38d0d4397e | ||
|
|
131097a236 | ||
|
|
88c33b1763 | ||
|
|
d4ba9fd808 | ||
|
|
290d63943f | ||
|
|
a59e87143c | ||
|
|
7178d0f3ec | ||
|
|
618533887d | ||
|
|
adbc68aa62 | ||
|
|
4bf5016921 | ||
|
|
6a9a469073 | ||
|
|
ac6cfa02c8 | ||
|
|
2e852e54f8 | ||
|
|
309cc6af12 | ||
|
|
39987022c6 | ||
|
|
6f521d5ac1 | ||
|
|
efb2df507d | ||
|
|
54077cb29f | ||
|
|
19cc82e0db | ||
|
|
4e45661e26 | ||
|
|
5816076bf6 | ||
|
|
59f2a2019e | ||
|
|
effbf8f244 | ||
|
|
b240eb7e25 | ||
|
|
f444075342 | ||
|
|
a6ceb88956 | ||
|
|
9db73ea07b | ||
|
|
356a845b69 | ||
|
|
d344cc8e77 | ||
|
|
d8e46ae27d | ||
|
|
4f22a46f93 | ||
|
|
a012d81cd1 | ||
|
|
57f043408f | ||
|
|
e5d8c9f355 | ||
|
|
2760de9d40 | ||
|
|
64a69e6627 | ||
|
|
9fd394d7c5 | ||
|
|
e8e00108e6 | ||
|
|
3100e1700c | ||
|
|
7598481c01 | ||
|
|
565d1d7a81 | ||
|
|
e15477cc04 | ||
|
|
94c46a04c4 | ||
|
|
c513a198f4 | ||
|
|
754f2801ee | ||
|
|
4b96ce2556 | ||
|
|
15f283c1c7 | ||
|
|
6c39a894ba | ||
|
|
af2a309c7a | ||
|
|
94489d1dca | ||
|
|
88f92dd98f | ||
|
|
deeebdb271 | ||
|
|
f75ee05b52 | ||
|
|
c39838e631 | ||
|
|
b7e4ff551a | ||
|
|
7b828ee0d9 | ||
|
|
266c9da66d | ||
|
|
f0f82fb9f9 | ||
|
|
54cfb54b8b | ||
|
|
1430b29340 | ||
|
|
d79fbf2519 | ||
|
|
aea7edd2f8 | ||
|
|
8f8c7724ba | ||
|
|
2ac1ade188 | ||
|
|
6122aeffb5 | ||
|
|
97f52bbcb6 | ||
|
|
e39bfe8832 | ||
|
|
36bc78070a | ||
|
|
e164e219bd | ||
|
|
309726dddd | ||
|
|
786ac02a1d | ||
|
|
e966e52b77 | ||
|
|
0e1f3f5823 | ||
|
|
649201f52d | ||
|
|
bbe9dc42d2 | ||
|
|
3ce66040b3 | ||
|
|
43337eacd2 | ||
|
|
a197f8fce1 | ||
|
|
8f1c235af5 | ||
|
|
c6810bc441 | ||
|
|
fb1c6a2cc3 | ||
|
|
3cc77c7c95 | ||
|
|
bb2c5283f8 | ||
|
|
a04c1883fa | ||
|
|
e834d67922 | ||
|
|
cfe71bbe60 | ||
|
|
262a6b563c | ||
|
|
a1d838a98a | ||
|
|
b1459134e8 | ||
|
|
9edf35c4bc | ||
|
|
b661d94278 | ||
|
|
c82acb38d5 | ||
|
|
7e24d7fea3 | ||
|
|
158e47b372 | ||
|
|
4df39342a2 | ||
|
|
0dc219193b | ||
|
|
8c587fd22a | ||
|
|
c9fa690601 | ||
|
|
e548b933e2 | ||
|
|
90f3a09805 | ||
|
|
d62430af1b | ||
|
|
bacc75393b | ||
|
|
fec7df02e4 | ||
|
|
0a1406eb86 | ||
|
|
26c2ecef6d | ||
|
|
15ef59a856 | ||
|
|
a9b10acac2 | ||
|
|
08f87e27ed | ||
|
|
2e0d02ac1d | ||
|
|
904353a1f3 | ||
|
|
143c84d47b | ||
|
|
65bfd8a132 | ||
|
|
e7e870ac0f | ||
|
|
f64c4b5bc9 | ||
|
|
8d88011c08 | ||
|
|
bc8c24af3b | ||
|
|
fa627f0da8 | ||
|
|
e701551655 | ||
|
|
7242f2719c | ||
|
|
0126d3b09d | ||
|
|
e5bbbe28c5 | ||
|
|
5e397c2aed | ||
|
|
5e8d6df9f0 | ||
|
|
ef88732a9a | ||
|
|
2419d14505 | ||
|
|
937d9c5d3b | ||
|
|
1f6dbe4278 | ||
|
|
e2b1b10d33 | ||
|
|
f0d388d571 | ||
|
|
667d6b6109 | ||
|
|
58d16cb3de | ||
|
|
78ffee360c | ||
|
|
53bc1da503 | ||
|
|
436f8600d2 | ||
|
|
669e644d81 | ||
|
|
346096aab7 | ||
|
|
9506f50850 | ||
|
|
d7328953ba | ||
|
|
2a2686fa11 | ||
|
|
1377d42dc2 | ||
|
|
c7c13b3a89 | ||
|
|
cd3aad232b | ||
|
|
c18fe9ab77 | ||
|
|
2832c9ee4c | ||
|
|
fa360d83f8 | ||
|
|
916d6a7777 | ||
|
|
dccab90843 | ||
|
|
c64e1a005e | ||
|
|
4dd7aec7ec | ||
|
|
8fa4e33bda | ||
|
|
1b5ce606e4 | ||
|
|
60974e8672 | ||
|
|
2205a90de9 | ||
|
|
c7666a3e01 | ||
|
|
a08a0c1dd1 | ||
|
|
dc80d751cb | ||
|
|
80037c2d23 | ||
|
|
cbbcc2a6fe | ||
|
|
f8a6a6afe2 | ||
|
|
a58af39fae | ||
|
|
e47dca02cf | ||
|
|
7d9886773a | ||
|
|
6caa238298 | ||
|
|
546378b9c4 | ||
|
|
ad471f3837 | ||
|
|
00b2c816a4 | ||
|
|
e1930528b9 | ||
|
|
27c98e31f4 | ||
|
|
021939264f | ||
|
|
ccfcb71565 | ||
|
|
74c36ac735 | ||
|
|
b6f4f7ac62 | ||
|
|
bd82ba7773 | ||
|
|
acc573f1a8 | ||
|
|
9773ea73d4 | ||
|
|
b091a87a37 | ||
|
|
ecf03507e6 | ||
|
|
4051033d7d | ||
|
|
64fcf59a5f | ||
|
|
2aae2257b3 | ||
|
|
e0559f8f70 | ||
|
|
a931a882b1 | ||
|
|
d0e0a04981 | ||
|
|
ec78180820 | ||
|
|
c78945aa3a | ||
|
|
a7f3663d01 | ||
|
|
f89fad5410 | ||
|
|
1b8799b1a3 | ||
|
|
a097278076 | ||
|
|
1b44bf26b9 | ||
|
|
fbf1aee8a7 | ||
|
|
e04d532c2a | ||
|
|
15e0d87c6d | ||
|
|
191c8bc2dc | ||
|
|
79366ba9c1 | ||
|
|
9a895feb6c | ||
|
|
115abf8ea7 | ||
|
|
184a19dbb4 | ||
|
|
f2de7bb124 | ||
|
|
2d1011694c | ||
|
|
c3da619dcf | ||
|
|
14f7fefd8a | ||
|
|
927727dee1 | ||
|
|
d4c078bab0 | ||
|
|
e5563b392e | ||
|
|
2131a85699 | ||
|
|
d6c3ca83e6 | ||
|
|
7811edb88c | ||
|
|
e30a3b415e | ||
|
|
81e6e2578f | ||
|
|
57504a7e8e | ||
|
|
14a961f521 | ||
|
|
a21dc96f01 | ||
|
|
2250714569 | ||
|
|
cab2840b66 | ||
|
|
bb14957982 | ||
|
|
fa530a47d6 | ||
|
|
9dc7e5f229 | ||
|
|
fd433244c7 | ||
|
|
6d5dd87551 | ||
|
|
d6a33bc81b | ||
|
|
a4d7747078 | ||
|
|
008e99417e | ||
|
|
26a47020fe | ||
|
|
b328766b80 | ||
|
|
30ce16d086 | ||
|
|
73ebba3329 | ||
|
|
1462d70ed2 | ||
|
|
38ccdfe869 | ||
|
|
07e7f0c139 | ||
|
|
ec489ab491 | ||
|
|
ed02d0d13d | ||
|
|
31999bf09c | ||
|
|
e93267cf62 | ||
|
|
da805707e4 | ||
|
|
9038de1e62 | ||
|
|
2229b04f27 | ||
|
|
68a575367a | ||
|
|
4a71c6ad11 | ||
|
|
7b144a84e5 | ||
|
|
86e94da6a6 | ||
|
|
1d86190f8f | ||
|
|
5fd4713a1e | ||
|
|
b173a0809c | ||
|
|
ac95f41c81 | ||
|
|
b83ba170f4 | ||
|
|
c9731ae37a | ||
|
|
f112fa1b74 | ||
|
|
9aa1e56d89 | ||
|
|
06dfa140d8 | ||
|
|
01474ca37e | ||
|
|
79f335cc5b | ||
|
|
723bfb5653 | ||
|
|
ee5d50dfe0 | ||
|
|
38d7ed94a6 | ||
|
|
0083fe0db8 | ||
|
|
9587632c49 | ||
|
|
4467ef3351 | ||
|
|
95f2bd3002 | ||
|
|
a0021a0f73 | ||
|
|
2fd3bb4a36 | ||
|
|
de6311c698 | ||
|
|
f675392143 | ||
|
|
8794a53fc8 | ||
|
|
51e5142866 | ||
|
|
b93d95cb1a | ||
|
|
1eb561c784 | ||
|
|
e8b9603bdd | ||
|
|
2ab1571e3a | ||
|
|
c6084c67a5 | ||
|
|
b6ec92e642 | ||
|
|
05e4237e71 | ||
|
|
e06c901bf5 | ||
|
|
d2f5503330 | ||
|
|
38d79e57ca | ||
|
|
f6735f5863 | ||
|
|
cf80984e52 | ||
|
|
3d69aeaca1 | ||
|
|
ee0b2f9361 | ||
|
|
fff0cc277d | ||
|
|
d9c8a39948 | ||
|
|
2803a540e3 | ||
|
|
9d98b90cae | ||
|
|
e607a75882 | ||
|
|
ae9ef717a3 | ||
|
|
e40ca8f007 | ||
|
|
353a551870 | ||
|
|
d083aea38e | ||
|
|
fd0faa3a97 | ||
|
|
f442dd7082 | ||
|
|
244d788146 | ||
|
|
306a3abd6c | ||
|
|
e1933944a3 | ||
|
|
921e420add | ||
|
|
09e55cf2d2 | ||
|
|
6f3d2a213c | ||
|
|
5bc2162df0 | ||
|
|
8d69bad65b | ||
|
|
cfc35e1028 | ||
|
|
6d30c58054 | ||
|
|
5255a0de81 | ||
|
|
6626e1649b | ||
|
|
af37c0625d | ||
|
|
44ab789450 | ||
|
|
f5a93057ad | ||
|
|
97d8907a76 | ||
|
|
fd410ab38e | ||
|
|
44199eb5ba | ||
|
|
415fcadb8e | ||
|
|
f856c3f85f | ||
|
|
03bc3c0876 | ||
|
|
d753ca7225 | ||
|
|
a0cdb8acbe | ||
|
|
8695ef75de | ||
|
|
53f3fad499 | ||
|
|
fcbeec95e4 | ||
|
|
7048c10f1d | ||
|
|
a4091cc776 | ||
|
|
6fa31f96b9 | ||
|
|
bef2d5ae77 | ||
|
|
d10b80f39d | ||
|
|
11c9d7fd2d | ||
|
|
968e25bc6b | ||
|
|
8b2ef8aaac | ||
|
|
46519a6cf8 | ||
|
|
db9406655f | ||
|
|
814097ab19 | ||
|
|
34f1e6f984 | ||
|
|
41127e9312 | ||
|
|
1de0c80e40 | ||
|
|
e84d3ce7cc | ||
|
|
a5942862ff | ||
|
|
a405372f6a | ||
|
|
b472580ca6 | ||
|
|
5a587167ae | ||
|
|
5149caf2b6 | ||
|
|
ffa559a571 | ||
|
|
5c3ab5470a | ||
|
|
e6859ff56d | ||
|
|
f16b0048f3 | ||
|
|
17b3a0d0ed | ||
|
|
1e9f38f2bb | ||
|
|
ef8f9e5738 | ||
|
|
bf59ead3b2 | ||
|
|
5dbd94e42c | ||
|
|
b92fc860eb | ||
|
|
20bdf171ee | ||
|
|
5a6ca2ce12 | ||
|
|
636941da70 | ||
|
|
5bc3975591 | ||
|
|
5e54e4561c | ||
|
|
a7b560b576 | ||
|
|
5f9d4c1572 | ||
|
|
a83ecb8b22 | ||
|
|
1cc88cc04f | ||
|
|
92bce6c2c6 | ||
|
|
ab96790c73 | ||
|
|
d5618321e9 | ||
|
|
4b8fc6ae9a | ||
|
|
152e24748b | ||
|
|
2efdf21130 | ||
|
|
e99c2b64b0 | ||
|
|
3915d9549e | ||
|
|
23077537f9 | ||
|
|
de0cbd6b29 | ||
|
|
d0efa0690a | ||
|
|
5b6d04d558 | ||
|
|
791794f59d | ||
|
|
6a6465ae5b | ||
|
|
056a78c7c4 | ||
|
|
11b0025faf | ||
|
|
3009b29e0c | ||
|
|
5d52d970a3 | ||
|
|
aef0b84275 | ||
|
|
12c704d89c | ||
|
|
38e070a565 | ||
|
|
c7d33c684b | ||
|
|
b2661d31fd | ||
|
|
53aa718674 | ||
|
|
c173114368 | ||
|
|
7006cb516c | ||
|
|
7809c30052 | ||
|
|
7e46d75159 | ||
|
|
87f96b9ec7 | ||
|
|
3920f338f6 | ||
|
|
2565231867 | ||
|
|
b7af3f7341 | ||
|
|
d93a7af534 | ||
|
|
1dcc8307ee | ||
|
|
9936a9469d | ||
|
|
0e245b8936 | ||
|
|
226c11dda3 | ||
|
|
8330054a81 | ||
|
|
c7cb1dab38 | ||
|
|
a3686c16d4 | ||
|
|
b2a6a6a1ac | ||
|
|
e3e9a8ccc3 | ||
|
|
7058c7a4cd | ||
|
|
2591de1c1d | ||
|
|
d20f19c08f | ||
|
|
a5ba4b4d23 | ||
|
|
094815a098 | ||
|
|
5224fa656c | ||
|
|
73550164a8 | ||
|
|
48a9ab313e | ||
|
|
25936c8361 | ||
|
|
3397cdd95f | ||
|
|
69f8ff2688 | ||
|
|
41ab062103 | ||
|
|
c78431a0fb | ||
|
|
3b33216961 | ||
|
|
6373636d57 | ||
|
|
04a1f9987e | ||
|
|
90d286cd5a | ||
|
|
1826d108a5 | ||
|
|
a5619bb0ee | ||
|
|
5c1fadaba2 | ||
|
|
7928f4321a | ||
|
|
3b62367003 | ||
|
|
c8994d8488 | ||
|
|
4033994111 | ||
|
|
2050fb56d0 | ||
|
|
eeea9d0fce | ||
|
|
4b1f7a76c7 | ||
|
|
db2a7feb34 | ||
|
|
8b91b2eeb1 | ||
|
|
899803739b | ||
|
|
1ea3a5b682 | ||
|
|
f21cf86cc4 | ||
|
|
bb1052bbc7 | ||
|
|
7dad08346c | ||
|
|
b7c56e58ae | ||
|
|
a6f6da5bc5 | ||
|
|
f271ba3d97 | ||
|
|
66d02c7e2d | ||
|
|
0406573317 | ||
|
|
4a8b6b2704 | ||
|
|
603cec113c | ||
|
|
a579c7bb86 | ||
|
|
654c488ed8 | ||
|
|
ecfb339680 | ||
|
|
ecbe222a50 | ||
|
|
aacf62c259 | ||
|
|
3973fb2706 | ||
|
|
07ff81f4db | ||
|
|
c0e1e45b72 | ||
|
|
b07f70ab0b | ||
|
|
e1a35f8b2d | ||
|
|
e364af393a |
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
||||
57
.github/ISSUE_TEMPLATE/01-bug-report-en.md
vendored
@@ -1,57 +0,0 @@
|
||||
---
|
||||
name: "[English] Bug report"
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Be sure to put a clear title after [BUG] in the text box above -->
|
||||
<!-- Be sure to put a clear title after [BUG] in the text box above -->
|
||||
<!-- Be sure to put a clear title after [BUG] in the text box above -->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Device Info (please complete the following information):**
|
||||
|
||||
- Device: [e.g. Pixel 4]
|
||||
- ROM: [e.g: AOSP]
|
||||
- ROM Version:
|
||||
- Android Version [e.g. 10]
|
||||
|
||||
**Application Info (please complete the following information):**
|
||||
|
||||
- Version: [e.g. 1.1.10]
|
||||
- Apk File Name: [e.g. app-release-arm64-v8a.apk]
|
||||
- Distribution Channel: [e.g. Google Play]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
**Configure**
|
||||
Paste configure file which **removed server info**
|
||||
```yaml
|
||||
# paste here
|
||||
```
|
||||
|
||||
**Logs**
|
||||
Paste logs to help detect problem
|
||||
|
||||
```
|
||||
<paste here>
|
||||
```
|
||||
107
.github/ISSUE_TEMPLATE/01-bug-report-en.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: "[English] Bug Report"
|
||||
description: "Create a report to help us debug bugs"
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
NOTE: Be sure to put a clear and concise title **AFTER** `[BUG]` in the text box above.
|
||||
|
||||
NOTE: We do not provide any services such as proxies, DO NOT feedback any problems not caused by this application here.
|
||||
|
||||
<!-- template -->
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "Describe the bug"
|
||||
description: "A clear and concise description of what the bug is."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: "To Reproduce"
|
||||
description: "Steps to reproduce the behavior:"
|
||||
value: |
|
||||
Step 1: ...
|
||||
Step 2: ...
|
||||
Step 3: ...
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: device-info
|
||||
attributes:
|
||||
label: "Device Info"
|
||||
description: |
|
||||
Input your device information.
|
||||
|
||||
Example:
|
||||
- Device: Pixel 4
|
||||
- ROM: AOSP
|
||||
- Android Version: 10
|
||||
value: |
|
||||
- Device:
|
||||
- ROM:
|
||||
- Android Version:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: app-info
|
||||
attributes:
|
||||
label: "Application Info"
|
||||
description: |
|
||||
Input application you are using information.
|
||||
|
||||
Example:
|
||||
```
|
||||
- Version: 2.5.4-premium
|
||||
- APK filename: cfa-2.5.4-premium-arm64-v8a-release.apk
|
||||
- Distribution Channel: Google Play
|
||||
```
|
||||
value: |
|
||||
- Version:
|
||||
- APK filename:
|
||||
- Distribution Channel:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: configure
|
||||
attributes:
|
||||
render: yml
|
||||
label: "Configure File"
|
||||
description: |
|
||||
Please paste or upload the configuration file here.
|
||||
|
||||
TIPS: If you only have a subscription link, please use your browser to download it.
|
||||
|
||||
**NOTE: Please remove proxies from the configuration file before uploading it.**
|
||||
**NOTE: Please remove proxies from the configuration file before uploading it.**
|
||||
**NOTE: Please remove proxies from the configuration file before uploading it.**
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
render: raw
|
||||
label: "Logs"
|
||||
description: |
|
||||
Please paste or upload the log file here.
|
||||
|
||||
TIPS: Please use the `Logcat` in application or `adb logcat`. `adb logcat` would be better.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshot
|
||||
attributes:
|
||||
label: "Screenshot"
|
||||
description: "If applicable, add screenshots to help explain your problem."
|
||||
placeholder: "Optional"
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: "Additional"
|
||||
description: "Add any other context about the problem here."
|
||||
24
.github/ISSUE_TEMPLATE/02-feature-request-en.md
vendored
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: "[English] Feature request"
|
||||
about: Suggest an idea for this app
|
||||
title: "[Feature Request] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Be sure to put a clear title after [Feature Request] in the text box above -->
|
||||
<!-- Be sure to put a clear title after [Feature Request] in the text box above -->
|
||||
<!-- Be sure to put a clear title after [Feature Request] in the text box above -->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
27
.github/ISSUE_TEMPLATE/02-feature-request-en.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: "[English] Feature Request"
|
||||
description: "Create a report to help us improve"
|
||||
title: "[Feature Request] "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request!
|
||||
|
||||
NOTE: Be sure to put a clear and concise title **AFTER** `[Feature Request]` in the text box above.
|
||||
|
||||
<!-- template -->
|
||||
- type: textarea
|
||||
id: "description"
|
||||
attributes:
|
||||
label: "Feature Description"
|
||||
description: |
|
||||
A clear and concise description of the feature.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "additional"
|
||||
attributes:
|
||||
label: "Additional"
|
||||
description: |
|
||||
Add any other context or screenshots about the feature request here.
|
||||
55
.github/ISSUE_TEMPLATE/03-bug-report-zh-cn.md
vendored
@@ -1,55 +0,0 @@
|
||||
---
|
||||
name: "[简体中文] 创建错误报告"
|
||||
about: 创建错误报告以帮助我们改进应用
|
||||
title: "[BUG] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- 请务必在上方文本框处 [BUG] 后填入清晰明了的标题 -->
|
||||
<!-- 请务必在上方文本框处 [BUG] 后填入清晰明了的标题 -->
|
||||
<!-- 请务必在上方文本框处 [BUG] 后填入清晰明了的标题 -->
|
||||
|
||||
**描述出现的错误**
|
||||
请简洁的描述你遇到的错误
|
||||
|
||||
**如何复现该错误**
|
||||
复现步骤:
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
4. ...
|
||||
|
||||
**预期行为**
|
||||
清晰简单的描述你预期的应用应该表现的行为
|
||||
|
||||
**屏幕截图**
|
||||
如果适用, 上传屏幕截图以帮助描述错误
|
||||
|
||||
**设备信息 (请完成以下信息):**
|
||||
- 机型: [例如: Pixel 4]
|
||||
- 系统/ROM: [例如: MIUI 11]
|
||||
- Android 版本 [例如: 10]
|
||||
- ROM版本 [例如: 20.3.19]
|
||||
|
||||
**应用信息**
|
||||
- 版本: [例如: 1.1.10]
|
||||
- 安装包文件名: [例如: app-release-arm64-v8a.apk]
|
||||
- 应用来源: [例如: Google Play]
|
||||
|
||||
**附加信息**
|
||||
其他的可能与改错误相关的信息
|
||||
|
||||
**配置文件**
|
||||
在此粘贴 **去除服务器信息的** 的 **配置文件**
|
||||
```yaml
|
||||
# 在此粘贴
|
||||
```
|
||||
|
||||
**日志**
|
||||
粘贴日志以帮助侦测错误
|
||||
```
|
||||
<在此粘贴>
|
||||
```
|
||||
|
||||
108
.github/ISSUE_TEMPLATE/03-bug-report-zh-cn.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
name: "[简体中文] 错误报告"
|
||||
description: "创建错误报告以帮助我们修正应用"
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您在百忙之中填写此错误报告。
|
||||
|
||||
注意: 请务必在上方文本框的 `[BUG]` **之后**填写清晰明了的标题。
|
||||
|
||||
注意:这里不提供像是代理服务器之类的服务,请不要反馈非应用自身引起的问题。
|
||||
|
||||
<!-- template -->
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "描述此错误"
|
||||
description: "请清晰简洁的描述你遇到的错误。"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: "如何复现该错误"
|
||||
description: "复现步骤:"
|
||||
value: |
|
||||
步骤 1: ...
|
||||
步骤 2: ...
|
||||
步骤 3: ...
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: device-info
|
||||
attributes:
|
||||
label: "设备信息"
|
||||
description: |
|
||||
输入您正在使用的设备信息。
|
||||
|
||||
例子:
|
||||
- 机型: Pixel 4
|
||||
- 系统类型: MIUI/AOSP
|
||||
- Android 版本: 10
|
||||
value: |
|
||||
- 机型:
|
||||
- 系统类型:
|
||||
- Android 版本:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: app-info
|
||||
attributes:
|
||||
label: "应用信息"
|
||||
description: |
|
||||
输入您正在使用的应用信息。
|
||||
|
||||
例子:
|
||||
```
|
||||
- 版本: 2.5.4-premium
|
||||
- 安装包文件名: cfa-2.5.4-premium-arm64-v8a-release.apk
|
||||
- 应用来源: Google Play
|
||||
```
|
||||
value: |
|
||||
- 版本:
|
||||
- 安装包文件名:
|
||||
- 应用来源:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: configure
|
||||
attributes:
|
||||
render: yml
|
||||
label: "配置文件"
|
||||
description: |
|
||||
请在此粘贴和上传配置文件。
|
||||
|
||||
提示:如果您仅有一个订阅链接,请使用浏览器打开此链接以下载配置文件。
|
||||
|
||||
**注意: 请在上传配置文件前,移除其中的代理服务器信息。**
|
||||
**注意: 请在上传配置文件前,移除其中的代理服务器信息。**
|
||||
**注意: 请在上传配置文件前,移除其中的代理服务器信息。**
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
render: raw
|
||||
label: "日志"
|
||||
description: |
|
||||
请在此粘贴或上传日志。
|
||||
|
||||
提示: 请使用应用内的 `Logcat` 或 `adb logcat` 捕获日志. `adb logcat` 能更好地帮助侦测问题.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshot
|
||||
attributes:
|
||||
label: "屏幕截图"
|
||||
description: "如果适用,请在此粘贴或上传屏幕截图。"
|
||||
placeholder: "可选"
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: "附加信息"
|
||||
description: "其他的可能与改错误相关的信息。"
|
||||
placeholder: "可选"
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
name: "[简体中文] 功能请求"
|
||||
about: 你希望的能够在应用中增加的功能
|
||||
title: "[Feature Request] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- 请务必在上方文本框处 [Feature Request] 后填入清晰明了的标题 -->
|
||||
<!-- 请务必在上方文本框处 [Feature Request] 后填入清晰明了的标题 -->
|
||||
<!-- 请务必在上方文本框处 [Feature Request] 后填入清晰明了的标题 -->
|
||||
|
||||
**功能描述**
|
||||
请清晰的描述你想要的功能
|
||||
|
||||
**描述你希望的实现方式**
|
||||
清晰的描述应用应该如何实现该功能
|
||||
|
||||
**附加信息**
|
||||
其他的与改功能相关的附加信息
|
||||
27
.github/ISSUE_TEMPLATE/04-feature-request-zh-cn.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: "[简体中文] 功能请求"
|
||||
description: "您希望的能够在应用中增加功能"
|
||||
title: "[Feature Request] "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您在百忙之中填写此功能请求报告。
|
||||
|
||||
注意: 请务必在上方文本框的 `[Feature Request]` **之后**填写清晰明了的标题。
|
||||
|
||||
<!-- template -->
|
||||
- type: textarea
|
||||
id: "description"
|
||||
attributes:
|
||||
label: "功能描述"
|
||||
description: |
|
||||
简介明了的描述此功能。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "additional"
|
||||
attributes:
|
||||
label: "附加信息"
|
||||
description: |
|
||||
与此功能相关的其他附加信息。
|
||||
105
.github/workflows/build-debug.yaml
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
name: Build Debug
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
BuildDebug:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout submodules
|
||||
run: git submodule update --init --recursive --force
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.25"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Update CA
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install ca-certificates
|
||||
sudo update-ca-certificates
|
||||
cp -f /etc/ssl/certs/ca-certificates.crt core/src/foss/golang/clash/component/ca/ca-certificates.crt
|
||||
|
||||
# - name: Signing properties
|
||||
# env:
|
||||
# SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
# SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
# SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
# run: |
|
||||
# touch signing.properties
|
||||
# echo keystore.password="$SIGNING_STORE_PASSWORD" > signing.properties
|
||||
# echo key.alias="$SIGNING_KEY_ALIAS" >> signing.properties
|
||||
# echo key.password="$SIGNING_KEY_PASSWORD" >> signing.properties
|
||||
|
||||
# echo "cat signing.properties"
|
||||
# cat signing.properties
|
||||
|
||||
- name: Build
|
||||
if: success()
|
||||
run: ./gradlew --no-daemon app:assembleAlphaRelease
|
||||
|
||||
- name: Upload Aritfact (universal)
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
name: CMFA Debug Unsigned APK (universal)
|
||||
path: |
|
||||
app/build/outputs/apk/alpha/release/*-universal-*.apk
|
||||
|
||||
- name: Upload Aritfact (arm64-v8a)
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
name: CMFA Debug Unsigned APK (arm64-v8a)
|
||||
path: |
|
||||
app/build/outputs/apk/alpha/release/*-arm64-v8a-*.apk
|
||||
|
||||
- name: Upload Aritfact (armeabi-v7a)
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
name: CMFA Debug Unsigned APK (armeabi-v7a)
|
||||
path: |
|
||||
app/build/outputs/apk/alpha/release/*-armeabi-v7a-*.apk
|
||||
|
||||
- name: Upload Aritfact (x86_64)
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
name: CMFA Debug Unsigned APK (x86_64)
|
||||
path: |
|
||||
app/build/outputs/apk/alpha/release/*-x86_64-*.apk
|
||||
|
||||
- name: Upload Aritfact (x86)
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
name: CMFA Debug Unsigned APK (x86)
|
||||
path: |
|
||||
app/build/outputs/apk/alpha/release/*-x86-*.apk
|
||||
89
.github/workflows/build-pre-release.yaml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
name: Build Pre-Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
BuildPreRelease:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout submodules
|
||||
run: git submodule update --init --recursive --force
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.25"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Update CA
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install ca-certificates
|
||||
sudo update-ca-certificates
|
||||
cp -f /etc/ssl/certs/ca-certificates.crt core/src/foss/golang/clash/component/ca/ca-certificates.crt
|
||||
|
||||
- name: Signing properties
|
||||
env:
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
run: |
|
||||
touch signing.properties
|
||||
echo keystore.password="$SIGNING_STORE_PASSWORD" > signing.properties
|
||||
echo key.alias="$SIGNING_KEY_ALIAS" >> signing.properties
|
||||
echo key.password="$SIGNING_KEY_PASSWORD" >> signing.properties
|
||||
|
||||
echo "cat signing.properties"
|
||||
cat signing.properties
|
||||
|
||||
- name: Pre-release Build
|
||||
if: success()
|
||||
run: ./gradlew --no-daemon app:assembleAlphaRelease
|
||||
|
||||
# Delete old Prerelease-alpha
|
||||
- uses: dev-drprasad/delete-tag-and-release@v1.1
|
||||
with:
|
||||
tag_name: Prerelease-alpha
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
delete_release: true
|
||||
|
||||
- name: Tag Repo
|
||||
uses: richardsimko/update-tag@v1
|
||||
with:
|
||||
tag_name: Prerelease-alpha
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload Alpha
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
tag_name: Prerelease-alpha
|
||||
files: app/build/outputs/apk/alpha/release/*
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
|
||||
- name: Release Changelog Builder
|
||||
uses: mikepenz/release-changelog-builder-action@v4
|
||||
127
.github/workflows/build-release.yaml
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
name: Build Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release-tag:
|
||||
description: 'Release Tag (v2.x.x)'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
BuildRelease:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout submodules
|
||||
run: git submodule update --init --recursive --force
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.25"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Convert and set version env
|
||||
id: process-version
|
||||
run: |
|
||||
VERSION_TAG=${{ inputs.release-tag }}
|
||||
VERSION_TAG=${VERSION_TAG#v} # remove the 'v' prefix
|
||||
IFS='.' read -ra VERSION_PARTS <<< "$VERSION_TAG" # split into array
|
||||
VERSION_CODE=$(printf "%1d%02d%03d" "${VERSION_PARTS[0]}" "${VERSION_PARTS[1]}" "${VERSION_PARTS[2]}")
|
||||
|
||||
echo "versonName=$VERSION_TAG" >> $GITHUB_OUTPUT # "1.2.3"
|
||||
echo "versonCode=$VERSION_CODE" >> $GITHUB_OUTPUT # "102003"
|
||||
|
||||
# Re-write version in build.gradle.kts
|
||||
- name: Re-write version
|
||||
uses: Devofure/advance-android-version-actions@v1.5
|
||||
with:
|
||||
gradlePath: build.gradle.kts
|
||||
versionCode: ${{ steps.process-version.outputs.versonCode }}
|
||||
versionName: ${{ steps.process-version.outputs.versonName }}
|
||||
|
||||
# If any change found, commit it and push
|
||||
- name: Commit and push if changes
|
||||
run: |
|
||||
changes=$(git diff --name-only origin/main | wc -l)
|
||||
if [ $changes -gt 0 ]
|
||||
then
|
||||
newVersionName=${{ steps.process-version.outputs.versonName }}
|
||||
newVersionCode=${{ steps.process-version.outputs.versonCode }}
|
||||
git config --global user.name 'GitHub Action'
|
||||
git config --global user.email 'action@github.com'
|
||||
git add build.gradle.kts
|
||||
git commit -am "Bump version to $newVersionName ($newVersionCode)"
|
||||
git tag "v$newVersionName"
|
||||
git push --follow-tags
|
||||
fi
|
||||
|
||||
- name: Update CA
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install ca-certificates
|
||||
sudo update-ca-certificates
|
||||
cp -f /etc/ssl/certs/ca-certificates.crt core/src/foss/golang/clash/component/ca/ca-certificates.crt
|
||||
|
||||
- name: Signing properties
|
||||
env:
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
run: |
|
||||
touch signing.properties
|
||||
echo keystore.password="$SIGNING_STORE_PASSWORD" > signing.properties
|
||||
echo key.alias="$SIGNING_KEY_ALIAS" >> signing.properties
|
||||
echo key.password="$SIGNING_KEY_PASSWORD" >> signing.properties
|
||||
|
||||
echo "cat signing.properties"
|
||||
cat signing.properties
|
||||
|
||||
- name: Release Build
|
||||
if: success()
|
||||
run: ./gradlew --no-daemon app:assembleMetaRelease
|
||||
|
||||
- name: Tag Repo
|
||||
uses: richardsimko/update-tag@v1
|
||||
with:
|
||||
tag_name: ${{ inputs.release-tag }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
tag_name: ${{ inputs.release-tag }}
|
||||
files: app/build/outputs/apk/meta/release/*
|
||||
generate_release_notes: true
|
||||
|
||||
- name: Release Changelog Builder
|
||||
uses: mikepenz/release-changelog-builder-action@v4.1.1
|
||||
with:
|
||||
configurationJson: |
|
||||
{
|
||||
"ignore_labels": [
|
||||
"Update"
|
||||
],
|
||||
}
|
||||
40
.github/workflows/build-unsigned.yaml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Build Unsigned
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '.idea/**'
|
||||
- '.gitignore'
|
||||
- '.gitmodules'
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- 'NOTICE'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '.idea/**'
|
||||
- '.gitignore'
|
||||
- '.gitmodules'
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- 'NOTICE'
|
||||
jobs:
|
||||
BuildUnsigned:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v2
|
||||
- name: Build
|
||||
run: ./gradlew --no-daemon app:assembleFossRelease
|
||||
81
.github/workflows/update-dependencies.yaml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: Update Clash-Core and Go Modules
|
||||
on:
|
||||
repository_dispatch:
|
||||
types:
|
||||
- core-updated
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-dependencies:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout and Update submodules
|
||||
run: git submodule update --init --recursive --remote --force
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.25"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Install update-go-mod-replace
|
||||
run: |
|
||||
go install github.com/metacubex/update-go-mod-replace@latest
|
||||
|
||||
- name: Update Foss Gomod
|
||||
run: |
|
||||
cd ${{ github.workspace }}/core/src/foss/golang/
|
||||
update-go-mod-replace ${{ github.workspace }}/core/src/foss/golang/clash/go.mod $(pwd)/go.mod
|
||||
go mod tidy
|
||||
|
||||
- name: Update Main Gomod
|
||||
run: |
|
||||
cd ${{ github.workspace }}/core/src/main/golang/
|
||||
update-go-mod-replace ${{ github.workspace }}/core/src/foss/golang/clash/go.mod $(pwd)/go.mod
|
||||
go mod tidy
|
||||
|
||||
- uses: tibdex/github-app-token@v2
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.MAINTAINER_APPID }}
|
||||
private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
commit-message: Update Dependencies
|
||||
branch: update-dependencies
|
||||
delete-branch: true
|
||||
title: 'Update Dependencies'
|
||||
draft: false
|
||||
body: |
|
||||
- Update Clash-Meta Core
|
||||
- Update Go Module Dependecies
|
||||
labels: |
|
||||
Update
|
||||
|
||||
- name: PR result
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
|
||||
23
.gitignore
vendored
@@ -20,12 +20,18 @@ gradle-app.setting
|
||||
*.iml
|
||||
/.idea/*
|
||||
!/.idea/codeStyles
|
||||
/core/src/foss/go/.idea/*
|
||||
!/core/src/foss/go/.idea/codeStyles
|
||||
/core/src/premium/go/.idea/*
|
||||
!/core/src/premium/go/.idea/codeStyles
|
||||
/core/src/main/golang/.idea/*
|
||||
!/core/src/main/golang/.idea/codeStyles
|
||||
/core/src/foss/golang/.idea/*
|
||||
!/core/src/foss/golang/.idea/codeStyles
|
||||
/core/src/premium/golang/.idea/*
|
||||
!/core/src/premium/golang/.idea/codeStyles
|
||||
|
||||
# Ignore builtin geofiles
|
||||
app/src/main/assets
|
||||
|
||||
# KeyStore
|
||||
signing.properties
|
||||
*.keystore
|
||||
*.jks
|
||||
|
||||
@@ -35,8 +41,9 @@ cmake-build-*
|
||||
# local.properties
|
||||
local.properties
|
||||
|
||||
# keystore
|
||||
keystore.properties
|
||||
|
||||
# tracker
|
||||
tracker.properties
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
@@ -55,5 +62,5 @@ google-services.json
|
||||
# logs
|
||||
*.log
|
||||
|
||||
# Ignore sum
|
||||
/core/src/main/golang/go.sum
|
||||
# MacOS
|
||||
.DS_Store
|
||||
|
||||
5
.gitmodules
vendored
@@ -1,3 +1,4 @@
|
||||
[submodule "clash-foss"]
|
||||
path = core/src/foss/go/clash
|
||||
url = https://github.com/Kr328/clash.git
|
||||
path = core/src/foss/golang/clash
|
||||
url = https://github.com/MetaCubeX/mihomo
|
||||
branch = Alpha
|
||||
|
||||
9
.idea/codeStyles/Project.xml
generated
@@ -20,15 +20,6 @@
|
||||
<package name="io.ktor" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="PACKAGES_IMPORT_LAYOUT">
|
||||
<value>
|
||||
<package name="" alias="false" withSubpackages="true" />
|
||||
<package name="java" alias="false" withSubpackages="true" />
|
||||
<package name="javax" alias="false" withSubpackages="true" />
|
||||
<package name="kotlin" alias="false" withSubpackages="true" />
|
||||
<package name="" alias="true" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
|
||||
63
README.md
@@ -1,12 +1,14 @@
|
||||
## Clash for Android
|
||||
## Clash Meta for Android
|
||||
|
||||
A Graphical user interface of [clash](https://github.com/Dreamacro/clash) for Android
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=com.github.kr328.clash"><img width="200px" alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"/></a> or [Releases](https://github.com/Kr328/ClashForAndroid/releases)
|
||||
A Graphical user interface of [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta) for Android
|
||||
|
||||
### Feature
|
||||
|
||||
Fully feature of [clash](https://github.com/Dreamacro/clash) ~~(Exclude `external-controller`~~
|
||||
Feature of [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/packages/com.github.metacubex.clash.meta/)
|
||||
|
||||
### Requirement
|
||||
|
||||
@@ -14,14 +16,6 @@ Fully feature of [clash](https://github.com/Dreamacro/clash) ~~(Exclude `externa
|
||||
- Android 7.0+ (recommend)
|
||||
- `armeabi-v7a` , `arm64-v8a`, `x86` or `x86_64` Architecture
|
||||
|
||||
### License
|
||||
|
||||
See also [LICENSE](./LICENSE) and [NOTICE](./NOTICE)
|
||||
|
||||
### Privacy Policy
|
||||
|
||||
See also [PRIVACY_POLICY.md](./PRIVACY_POLICY.md)
|
||||
|
||||
### Build
|
||||
|
||||
1. Update submodules
|
||||
@@ -38,19 +32,48 @@ See also [PRIVACY_POLICY.md](./PRIVACY_POLICY.md)
|
||||
sdk.dir=/path/to/android-sdk
|
||||
```
|
||||
|
||||
4. Create `keystore.properties` in project root with
|
||||
4. Create `signing.properties` in project root with
|
||||
|
||||
```properties
|
||||
storeFile=/path/to/keystore/file
|
||||
storePassword=<key store password>
|
||||
keyAlias=<key alias>
|
||||
keyPassword=<key password>
|
||||
keystore.path=/path/to/keystore/file
|
||||
keystore.password=<key store password>
|
||||
key.alias=<key alias>
|
||||
key.password=<key password>
|
||||
```
|
||||
|
||||
5. Build
|
||||
|
||||
```bash
|
||||
./gradlew app:assembleFossRelease
|
||||
./gradlew app:assembleMeta-AlphaRelease
|
||||
```
|
||||
|
||||
6. Pick `app-foss-<arch>-release-signed.apk` in `app/build/outputs/apk/foss/release/`
|
||||
### Automation
|
||||
|
||||
APP package name is `com.github.metacubex.clash.meta`
|
||||
|
||||
- Toggle Clash.Meta service status
|
||||
- Send intent to activity `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.TOGGLE_CLASH`
|
||||
- Start Clash.Meta service
|
||||
- Send intent to activity `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.START_CLASH`
|
||||
- Stop Clash.Meta service
|
||||
- Send intent to activity `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.STOP_CLASH`
|
||||
- Import a profile
|
||||
- URL Scheme `clash://install-config?url=<encoded URI>` or `clashmeta://install-config?url=<encoded URI>`
|
||||
|
||||
### Contribution and Project Maintenance
|
||||
|
||||
#### Meta Kernel
|
||||
|
||||
- CMFA uses the kernel from `android-real` branch under `MetaCubeX/Clash.Meta`, which is a merge of the main `Alpha` branch and `android-open`.
|
||||
- If you want to contribute to the kernel, make PRs to `Alpha` branch of the Meta kernel repository.
|
||||
- If you want to contribute Android-specific patches to the kernel, make PRs to `android-open` branch of the Meta kernel repository.
|
||||
|
||||
#### Maintenance
|
||||
|
||||
- When `MetaCubeX/Clash.Meta` kernel is updated to a new version, the `Update Dependencies` actions in this repo will be triggered automatically.
|
||||
- It will pull the new version of the meta kernel, update all the golang dependencies, and create a PR without manual intervention.
|
||||
- If there is any compile error in PR, you need to fix it before merging. Alternatively, you may merge the PR directly.
|
||||
- Manually triggering `Build Pre-Release` actions will compile and publish a `PreRelease` version.
|
||||
- Manually triggering `Build Release` actions will compile, tag and publish a `Release` version.
|
||||
- You must fill the blank `Release Tag` with the tag you want to release in the format of `v1.2.3`.
|
||||
- `versionName` and `versionCode` in `build.gradle.kts` will be automatically bumped to the tag you filled above.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
plugins {
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
@@ -5,27 +9,61 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":hideapi"))
|
||||
|
||||
implementation(project(":core"))
|
||||
implementation(project(":service"))
|
||||
implementation(project(":design"))
|
||||
implementation(project(":common"))
|
||||
|
||||
implementation(kotlin("stdlib-jdk7"))
|
||||
implementation(deps.kotlin.coroutine)
|
||||
implementation(deps.androidx.core)
|
||||
implementation(deps.androidx.activity)
|
||||
implementation(deps.androidx.fragment)
|
||||
implementation(deps.androidx.appcompat)
|
||||
implementation(deps.androidx.coordinator)
|
||||
implementation(deps.androidx.recyclerview)
|
||||
implementation(deps.google.material)
|
||||
|
||||
val premiumImplementation by configurations
|
||||
|
||||
premiumImplementation(deps.appcenter.analytics)
|
||||
premiumImplementation(deps.appcenter.crashes)
|
||||
implementation(libs.kotlin.coroutine)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.fragment)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.coordinator)
|
||||
implementation(libs.androidx.recyclerview)
|
||||
implementation(libs.google.material)
|
||||
}
|
||||
|
||||
tasks.getByName("clean", type = Delete::class) {
|
||||
delete(file("release"))
|
||||
}
|
||||
|
||||
val geoFilesDownloadDir = "src/main/assets"
|
||||
|
||||
task("downloadGeoFiles") {
|
||||
|
||||
val geoFilesUrls = mapOf(
|
||||
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb" to "geoip.metadb",
|
||||
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat" to "geosite.dat",
|
||||
// "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country.mmdb" to "country.mmdb",
|
||||
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb" to "ASN.mmdb",
|
||||
)
|
||||
|
||||
doLast {
|
||||
geoFilesUrls.forEach { (downloadUrl, outputFileName) ->
|
||||
val url = URL(downloadUrl)
|
||||
val outputPath = file("$geoFilesDownloadDir/$outputFileName")
|
||||
outputPath.parentFile.mkdirs()
|
||||
url.openStream().use { input ->
|
||||
Files.copy(input, outputPath.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
println("$outputFileName downloaded to $outputPath")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val downloadGeoFilesTask = tasks["downloadGeoFiles"]
|
||||
|
||||
tasks.forEach {
|
||||
if (it.name.startsWith("assemble")) {
|
||||
it.dependsOn(downloadGeoFilesTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.getByName("clean", type = Delete::class) {
|
||||
delete(file(geoFilesDownloadDir))
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Application
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
object Tracker {
|
||||
fun initialize(application: Application) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
fun uploadLogcat(logcat: String) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.github.kr328.clash">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
@@ -12,10 +11,11 @@
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
@@ -44,20 +44,29 @@
|
||||
android:label="@string/launch_name"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ExternalImportActivity"
|
||||
<activity-alias
|
||||
android:name=".MainActivityAlias"
|
||||
android:exported="true"
|
||||
android:label="@string/import_from_file"
|
||||
android:targetActivity=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name=".ExternalControlActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/external_control_activity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
@@ -65,9 +74,21 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="install-config"
|
||||
android:scheme="clash" />
|
||||
<data android:scheme="clash"/>
|
||||
<data android:scheme="clashmeta"/>
|
||||
<data android:host="install-config"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.github.metacubex.clash.meta.action.START_CLASH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.github.metacubex.clash.meta.action.STOP_CLASH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.github.metacubex.clash.meta.action.TOGGLE_CLASH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
@@ -136,6 +157,11 @@
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="@string/override" />
|
||||
<activity
|
||||
android:name=".MetaFeatureSettingsActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="@string/meta_features" />
|
||||
<activity
|
||||
android:name=".AccessControlActivity"
|
||||
android:configChanges="uiMode"
|
||||
@@ -155,7 +181,11 @@
|
||||
<service
|
||||
android:name=".LogcatService"
|
||||
android:exported="false"
|
||||
android:label="@string/clash_logcat" />
|
||||
android:label="@string/clash_logcat"
|
||||
android:foregroundServiceType="specialUse">
|
||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="explanation_for_special_use"/>
|
||||
</service>
|
||||
<service
|
||||
android:name=".TileService"
|
||||
android:exported="true"
|
||||
@@ -176,5 +206,14 @@
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".DialerReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.provider.Telephony.SECRET_CODE" />
|
||||
<!-- 252746382 is the name of Clash Meta in T9 -->
|
||||
<data android:scheme="android_secret_code" android:host="252746382" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
BIN
app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
@@ -11,7 +11,10 @@ import com.github.kr328.clash.design.AccessControlDesign
|
||||
import com.github.kr328.clash.design.model.AppInfo
|
||||
import com.github.kr328.clash.design.util.toAppInfo
|
||||
import com.github.kr328.clash.service.store.ServiceStore
|
||||
import com.github.kr328.clash.util.startClashService
|
||||
import com.github.kr328.clash.util.stopClashService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -26,7 +29,15 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
|
||||
|
||||
defer {
|
||||
withContext(Dispatchers.IO) {
|
||||
val changed = selected != service.accessControlPackages
|
||||
service.accessControlPackages = selected
|
||||
if (clashRunning && changed) {
|
||||
stopClashService()
|
||||
while (clashRunning) {
|
||||
delay(200)
|
||||
}
|
||||
startClashService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +57,7 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
|
||||
AccessControlDesign.Request.ReloadApps -> {
|
||||
design.patchApps(loadApps(selected))
|
||||
}
|
||||
|
||||
AccessControlDesign.Request.SelectAll -> {
|
||||
val all = withContext(Dispatchers.Default) {
|
||||
design.apps.map(AppInfo::packageName)
|
||||
@@ -56,11 +68,13 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
|
||||
|
||||
design.rebindAll()
|
||||
}
|
||||
|
||||
AccessControlDesign.Request.SelectNone -> {
|
||||
selected.clear()
|
||||
|
||||
design.rebindAll()
|
||||
}
|
||||
|
||||
AccessControlDesign.Request.SelectInvert -> {
|
||||
val all = withContext(Dispatchers.Default) {
|
||||
design.apps.map(AppInfo::packageName).toSet() - selected
|
||||
@@ -71,16 +85,14 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
|
||||
|
||||
design.rebindAll()
|
||||
}
|
||||
|
||||
AccessControlDesign.Request.Import -> {
|
||||
val clipboard = getSystemService<ClipboardManager>()
|
||||
val data = clipboard?.primaryClip
|
||||
|
||||
if (data != null && data.itemCount > 0) {
|
||||
val all = withContext(Dispatchers.IO) {
|
||||
val packages = data.getItemAt(0).text.split("\n").toSet()
|
||||
|
||||
design.apps.map(AppInfo::packageName).intersect(packages)
|
||||
}
|
||||
val packages = data.getItemAt(0).text.split("\n").toSet()
|
||||
val all = design.apps.map(AppInfo::packageName).intersect(packages)
|
||||
|
||||
selected.clear()
|
||||
selected.addAll(all)
|
||||
@@ -88,17 +100,16 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
|
||||
|
||||
design.rebindAll()
|
||||
}
|
||||
|
||||
AccessControlDesign.Request.Export -> {
|
||||
val clipboard = getSystemService<ClipboardManager>()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val data = ClipData.newPlainText(
|
||||
"packages",
|
||||
selected.joinToString("\n")
|
||||
)
|
||||
val data = ClipData.newPlainText(
|
||||
"packages",
|
||||
selected.joinToString("\n")
|
||||
)
|
||||
|
||||
clipboard?.setPrimaryClip(data)
|
||||
}
|
||||
clipboard?.setPrimaryClip(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,7 +134,10 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
|
||||
it.packageName != packageName
|
||||
}
|
||||
.filter {
|
||||
it.packageName == "android" || it.requestedPermissions?.contains(INTERNET) == true
|
||||
it.applicationInfo != null
|
||||
}
|
||||
.filter {
|
||||
it.requestedPermissions?.contains(INTERNET) == true || it.applicationInfo!!.uid < android.os.Process.FIRST_APPLICATION_UID
|
||||
}
|
||||
.filter {
|
||||
systemApp || !it.isSystemApp
|
||||
@@ -137,6 +151,6 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
|
||||
|
||||
private val PackageInfo.isSystemApp: Boolean
|
||||
get() {
|
||||
return applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0
|
||||
return applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.pm.PackageManager
|
||||
import com.github.kr328.clash.common.util.componentName
|
||||
import com.github.kr328.clash.design.AppSettingsDesign
|
||||
@@ -17,6 +18,7 @@ class AppSettingsActivity : BaseActivity<AppSettingsDesign>(), Behavior {
|
||||
ServiceStore(this),
|
||||
this,
|
||||
clashRunning,
|
||||
::onHideIconChange,
|
||||
)
|
||||
|
||||
setContentDesign(design)
|
||||
@@ -59,4 +61,17 @@ class AppSettingsActivity : BaseActivity<AppSettingsDesign>(), Behavior {
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onHideIconChange(hide: Boolean) {
|
||||
val newState = if (hide) {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
} else {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
}
|
||||
packageManager.setComponentEnabledSetting(
|
||||
ComponentName(this, mainActivityAlias),
|
||||
newState,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -24,34 +24,24 @@ import com.github.kr328.clash.util.ActivityResultLifecycle
|
||||
import com.github.kr328.clash.util.ApplicationObserver
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
abstract class BaseActivity<D : Design<*>> :
|
||||
AppCompatActivity(),
|
||||
abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
|
||||
CoroutineScope by MainScope(),
|
||||
Broadcasts.Observer {
|
||||
enum class Event {
|
||||
ServiceRecreated,
|
||||
ActivityStart,
|
||||
ActivityStop,
|
||||
ClashStop,
|
||||
ClashStart,
|
||||
ProfileLoaded,
|
||||
ProfileChanged
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected val uiStore by lazy { UiStore(this) }
|
||||
protected val events = Channel<Event>(Channel.UNLIMITED)
|
||||
protected var activityStarted: Boolean = false
|
||||
protected val clashRunning: Boolean
|
||||
get() = Remote.broadcasts.clashRunning
|
||||
protected var design: D? = null
|
||||
private set(value) {
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
if (value != null) {
|
||||
setContentView(value.root)
|
||||
} else {
|
||||
@@ -72,14 +62,14 @@ abstract class BaseActivity<D : Design<*>> :
|
||||
|
||||
suspend fun <I, O> startActivityForResult(
|
||||
contracts: ActivityResultContract<I, O>,
|
||||
input: I
|
||||
input: I,
|
||||
): O = withContext(Dispatchers.Main) {
|
||||
val requestKey = nextRequestKey.getAndIncrement().toString()
|
||||
|
||||
ActivityResultLifecycle().use { lifecycle, start ->
|
||||
suspendCoroutine { c ->
|
||||
activityResultRegistry.register(requestKey, lifecycle, contracts) {
|
||||
c.resumeWith(Result.success(it))
|
||||
c.resume(it)
|
||||
}.apply { start() }.launch(input)
|
||||
}
|
||||
}
|
||||
@@ -89,7 +79,6 @@ abstract class BaseActivity<D : Design<*>> :
|
||||
suspendCoroutine<Unit> {
|
||||
window.decorView.post {
|
||||
this.design = design
|
||||
|
||||
it.resume(Unit)
|
||||
}
|
||||
}
|
||||
@@ -97,49 +86,35 @@ abstract class BaseActivity<D : Design<*>> :
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
applyDayNight()
|
||||
|
||||
launch {
|
||||
main()
|
||||
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
activityStarted = true
|
||||
|
||||
Remote.broadcasts.addObserver(this)
|
||||
|
||||
events.trySend(Event.ActivityStart)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
activityStarted = false
|
||||
|
||||
Remote.broadcasts.removeObserver(this)
|
||||
|
||||
events.trySend(Event.ActivityStop)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
design?.cancel()
|
||||
|
||||
cancel()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
if (deferRunning) {
|
||||
return
|
||||
}
|
||||
|
||||
if (deferRunning) return
|
||||
deferRunning = true
|
||||
|
||||
launch {
|
||||
@@ -169,7 +144,6 @@ abstract class BaseActivity<D : Design<*>> :
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
this.onBackPressed()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -177,6 +151,14 @@ abstract class BaseActivity<D : Design<*>> :
|
||||
events.trySend(Event.ProfileChanged)
|
||||
}
|
||||
|
||||
override fun onProfileUpdateCompleted(uuid: UUID?) {
|
||||
events.trySend(Event.ProfileUpdateCompleted)
|
||||
}
|
||||
|
||||
override fun onProfileUpdateFailed(uuid: UUID?, reason: String?) {
|
||||
events.trySend(Event.ProfileUpdateFailed)
|
||||
}
|
||||
|
||||
override fun onProfileLoaded() {
|
||||
events.trySend(Event.ProfileLoaded)
|
||||
}
|
||||
@@ -201,49 +183,45 @@ abstract class BaseActivity<D : Design<*>> :
|
||||
|
||||
private fun queryDayNight(config: Configuration = resources.configuration): DayNight {
|
||||
return when (uiStore.darkMode) {
|
||||
DarkMode.Auto -> {
|
||||
if (config.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES)
|
||||
DayNight.Night
|
||||
else
|
||||
DayNight.Day
|
||||
}
|
||||
DarkMode.ForceLight -> {
|
||||
DayNight.Day
|
||||
}
|
||||
DarkMode.ForceDark -> {
|
||||
DayNight.Night
|
||||
}
|
||||
DarkMode.Auto -> if (config.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) DayNight.Night else DayNight.Day
|
||||
DarkMode.ForceLight -> DayNight.Day
|
||||
DarkMode.ForceDark -> DayNight.Night
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyDayNight(config: Configuration = resources.configuration) {
|
||||
val dayNight = queryDayNight(config)
|
||||
|
||||
when (dayNight) {
|
||||
DayNight.Night -> {
|
||||
theme.applyStyle(R.style.AppThemeDark, true)
|
||||
}
|
||||
DayNight.Day -> {
|
||||
theme.applyStyle(R.style.AppThemeLight, true)
|
||||
}
|
||||
DayNight.Night -> theme.applyStyle(R.style.AppThemeDark, true)
|
||||
DayNight.Day -> theme.applyStyle(R.style.AppThemeLight, true)
|
||||
}
|
||||
|
||||
window.isAllowForceDarkCompat = false
|
||||
window.isSystemBarsTranslucentCompat = true
|
||||
|
||||
|
||||
window.statusBarColor = resolveThemedColor(android.R.attr.statusBarColor)
|
||||
window.navigationBarColor = resolveThemedColor(android.R.attr.navigationBarColor)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
window.isLightStatusBarsCompat =
|
||||
resolveThemedBoolean(android.R.attr.windowLightStatusBar)
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
window.isLightStatusBarsCompat = resolveThemedBoolean(android.R.attr.windowLightStatusBar)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
window.isLightNavigationBarCompat =
|
||||
resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
|
||||
if (Build.VERSION.SDK_INT >= 27) {
|
||||
window.isLightNavigationBarCompat = resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
|
||||
}
|
||||
|
||||
this.dayNight = dayNight
|
||||
}
|
||||
|
||||
enum class Event {
|
||||
ServiceRecreated,
|
||||
ActivityStart,
|
||||
ActivityStop,
|
||||
ClashStop,
|
||||
ClashStart,
|
||||
ProfileLoaded,
|
||||
ProfileChanged,
|
||||
ProfileUpdateCompleted,
|
||||
ProfileUpdateFailed,
|
||||
}
|
||||
}
|
||||
|
||||
13
app/src/main/java/com/github/kr328/clash/DialerReceiver.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
||||
class DialerReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.github.kr328.clash.common.constants.Intents
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.setUUID
|
||||
import com.github.kr328.clash.design.MainDesign
|
||||
import com.github.kr328.clash.design.ui.ToastDuration
|
||||
import com.github.kr328.clash.remote.Remote
|
||||
import com.github.kr328.clash.remote.StatusClient
|
||||
import com.github.kr328.clash.service.model.Profile
|
||||
import com.github.kr328.clash.util.startClashService
|
||||
import com.github.kr328.clash.util.stopClashService
|
||||
import com.github.kr328.clash.util.withProfile
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
class ExternalControlActivity : Activity(), CoroutineScope by MainScope() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
when(intent.action) {
|
||||
Intent.ACTION_VIEW -> {
|
||||
val uri = intent.data ?: return finish()
|
||||
val url = uri.getQueryParameter("url") ?: return finish()
|
||||
|
||||
launch {
|
||||
val uuid = withProfile {
|
||||
val type = when (uri.getQueryParameter("type")?.lowercase(Locale.getDefault())) {
|
||||
"url" -> Profile.Type.Url
|
||||
"file" -> Profile.Type.File
|
||||
else -> Profile.Type.Url
|
||||
}
|
||||
val name = uri.getQueryParameter("name") ?: getString(R.string.new_profile)
|
||||
|
||||
create(type, name).also {
|
||||
patch(it, name, url, 0)
|
||||
}
|
||||
}
|
||||
startActivity(PropertiesActivity::class.intent.setUUID(uuid))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
Intents.ACTION_TOGGLE_CLASH -> if(Remote.broadcasts.clashRunning) {
|
||||
stopClash()
|
||||
}
|
||||
else {
|
||||
startClash()
|
||||
}
|
||||
|
||||
Intents.ACTION_START_CLASH -> if(!Remote.broadcasts.clashRunning) {
|
||||
startClash()
|
||||
}
|
||||
else {
|
||||
Toast.makeText(this, R.string.external_control_started, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
Intents.ACTION_STOP_CLASH -> if(Remote.broadcasts.clashRunning) {
|
||||
stopClash()
|
||||
}
|
||||
else {
|
||||
Toast.makeText(this, R.string.external_control_stopped, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
return finish()
|
||||
}
|
||||
|
||||
private fun startClash() {
|
||||
// if (currentProfile == null) {
|
||||
// Toast.makeText(this, R.string.no_profile_selected, Toast.LENGTH_LONG).show()
|
||||
// return
|
||||
// }
|
||||
val vpnRequest = startClashService()
|
||||
if (vpnRequest != null) {
|
||||
Toast.makeText(this, R.string.unable_to_start_vpn, Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
Toast.makeText(this, R.string.external_control_started, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
private fun stopClash() {
|
||||
stopClashService()
|
||||
Toast.makeText(this, R.string.external_control_stopped, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.setUUID
|
||||
import com.github.kr328.clash.service.model.Profile
|
||||
import com.github.kr328.clash.util.withProfile
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
class ExternalImportActivity : Activity(), CoroutineScope by MainScope() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (intent.action != Intent.ACTION_VIEW)
|
||||
return finish()
|
||||
|
||||
val uri = intent.data ?: return finish()
|
||||
val url = uri.getQueryParameter("url") ?: return finish()
|
||||
|
||||
launch {
|
||||
val uuid = withProfile {
|
||||
val type = when (uri.getQueryParameter("type")?.lowercase(Locale.getDefault())) {
|
||||
"url" -> Profile.Type.Url
|
||||
"file" -> Profile.Type.File
|
||||
else -> Profile.Type.Url
|
||||
}
|
||||
val name = uri.getQueryParameter("name") ?: getString(R.string.new_profile)
|
||||
|
||||
create(type, name).also {
|
||||
patch(it, name, url, 0)
|
||||
}
|
||||
}
|
||||
|
||||
startActivity(PropertiesActivity::class.intent.setUUID(uuid))
|
||||
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,24 +81,6 @@ class FilesActivity : BaseActivity<FilesDesign>() {
|
||||
client.renameDocument(it.file.id, newName)
|
||||
}
|
||||
is FilesDesign.Request.ImportFile -> {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
val hasPermission = ContextCompat.checkSelfPermission(
|
||||
this@FilesActivity,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
if (!hasPermission) {
|
||||
val granted = startActivityForResult(
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
)
|
||||
|
||||
if (!granted) {
|
||||
return@onReceive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val uri: Uri? = startActivityForResult(
|
||||
ActivityResultContracts.GetContent(),
|
||||
"*/*"
|
||||
@@ -116,7 +98,7 @@ class FilesActivity : BaseActivity<FilesDesign>() {
|
||||
}
|
||||
is FilesDesign.Request.ExportFile -> {
|
||||
val uri: Uri? = startActivityForResult(
|
||||
ActivityResultContracts.CreateDocument(),
|
||||
ActivityResultContracts.CreateDocument("text/plain"),
|
||||
it.file.name
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.os.IBinder
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.github.kr328.clash.common.compat.startForegroundServiceCompat
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.fileName
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
@@ -27,6 +28,7 @@ import kotlinx.coroutines.withContext
|
||||
import java.io.OutputStreamWriter
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
class LogcatActivity : BaseActivity<LogcatDesign>() {
|
||||
private var conn: ServiceConnection? = null
|
||||
@@ -47,6 +49,7 @@ class LogcatActivity : BaseActivity<LogcatDesign>() {
|
||||
val messages = try {
|
||||
LogcatReader(this, file).readAll()
|
||||
} catch (e: Exception) {
|
||||
Log.e("Fail to read log file ${file.fileName}: ${e.message}")
|
||||
return showInvalid()
|
||||
}
|
||||
|
||||
@@ -67,7 +70,7 @@ class LogcatActivity : BaseActivity<LogcatDesign>() {
|
||||
}
|
||||
LogcatDesign.Request.Export -> {
|
||||
val output = startActivityForResult(
|
||||
ActivityResultContracts.CreateDocument(),
|
||||
ActivityResultContracts.CreateDocument("text/plain"),
|
||||
file.fileName
|
||||
)
|
||||
|
||||
@@ -109,7 +112,7 @@ class LogcatActivity : BaseActivity<LogcatDesign>() {
|
||||
when (it) {
|
||||
LogcatDesign.Request.Close -> {
|
||||
stopService(LogcatService::class.intent)
|
||||
|
||||
startActivity(LogsActivity::class.intent)
|
||||
finish()
|
||||
}
|
||||
else -> Unit
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.ComponentName
|
||||
@@ -9,12 +7,14 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.IInterface
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.github.kr328.clash.common.compat.getColorCompat
|
||||
import com.github.kr328.clash.common.compat.pendingIntentFlags
|
||||
import com.github.kr328.clash.common.compat.startForegroundCompat
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.core.model.LogMessage
|
||||
@@ -126,38 +126,34 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
return
|
||||
|
||||
NotificationManagerCompat.from(this)
|
||||
.createNotificationChannel(
|
||||
NotificationChannel(
|
||||
NotificationChannelCompat.Builder(
|
||||
CHANNEL_ID,
|
||||
getString(R.string.clash_logcat),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
NotificationManagerCompat.IMPORTANCE_DEFAULT
|
||||
).setName(getString(com.github.kr328.clash.design.R.string.clash_logcat)).build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun showNotification() {
|
||||
val notification = NotificationCompat
|
||||
.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_logo_service)
|
||||
.setColor(getColorCompat(R.color.color_clash_light))
|
||||
.setContentTitle(getString(R.string.clash_logcat))
|
||||
.setContentText(getString(R.string.running))
|
||||
.setSmallIcon(com.github.kr328.clash.service.R.drawable.ic_logo_service)
|
||||
.setColor(getColorCompat(com.github.kr328.clash.design.R.color.color_clash_light))
|
||||
.setContentTitle(getString(com.github.kr328.clash.design.R.string.clash_logcat))
|
||||
.setContentText(getString(com.github.kr328.clash.design.R.string.running))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
R.id.nf_logcat_status,
|
||||
LogcatActivity::class.intent
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
pendingIntentFlags(PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
)
|
||||
.build()
|
||||
|
||||
startForeground(R.id.nf_logcat_status, notification)
|
||||
startForegroundCompat(R.id.nf_logcat_status, notification)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -11,11 +11,8 @@ import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class LogsActivity : BaseActivity<LogsDesign>() {
|
||||
override suspend fun main() {
|
||||
if (LogcatService.running) {
|
||||
return startActivity(LogcatActivity::class.intent)
|
||||
}
|
||||
|
||||
override suspend fun main() {
|
||||
val design = LogsDesign(this)
|
||||
|
||||
setContentDesign(design)
|
||||
@@ -38,7 +35,6 @@ class LogsActivity : BaseActivity<LogsDesign>() {
|
||||
when (it) {
|
||||
LogsDesign.Request.StartLogcat -> {
|
||||
startActivity(LogcatActivity::class.intent)
|
||||
|
||||
finish()
|
||||
}
|
||||
LogsDesign.Request.DeleteAll -> {
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.design.MainDesign
|
||||
import com.github.kr328.clash.design.ui.ToastDuration
|
||||
import com.github.kr328.clash.store.TipsStore
|
||||
import com.github.kr328.clash.util.startClashService
|
||||
import com.github.kr328.clash.util.stopClashService
|
||||
import com.github.kr328.clash.util.withClash
|
||||
import com.github.kr328.clash.util.withProfile
|
||||
import com.github.kr328.clash.core.bridge.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
class MainActivity : BaseActivity<MainDesign>() {
|
||||
override suspend fun main() {
|
||||
@@ -23,10 +30,6 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
|
||||
setContentDesign(design)
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
showUpdatedTips(design)
|
||||
}
|
||||
|
||||
design.fetch()
|
||||
|
||||
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
@@ -56,8 +59,13 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
startActivity(ProfilesActivity::class.intent)
|
||||
MainDesign.Request.OpenProviders ->
|
||||
startActivity(ProvidersActivity::class.intent)
|
||||
MainDesign.Request.OpenLogs ->
|
||||
startActivity(LogsActivity::class.intent)
|
||||
MainDesign.Request.OpenLogs -> {
|
||||
if (LogcatService.running) {
|
||||
startActivity(LogcatActivity::class.intent)
|
||||
} else {
|
||||
startActivity(LogsActivity::class.intent)
|
||||
}
|
||||
}
|
||||
MainDesign.Request.OpenSettings ->
|
||||
startActivity(SettingsActivity::class.intent)
|
||||
MainDesign.Request.OpenHelp ->
|
||||
@@ -75,20 +83,6 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun showUpdatedTips(design: MainDesign) {
|
||||
val tips = TipsStore(this)
|
||||
|
||||
if (tips.primaryVersion != TipsStore.CURRENT_PRIMARY_VERSION) {
|
||||
tips.primaryVersion = TipsStore.CURRENT_PRIMARY_VERSION
|
||||
|
||||
val pkg = packageManager.getPackageInfo(packageName, 0)
|
||||
|
||||
if (pkg.firstInstallTime != pkg.lastUpdateTime) {
|
||||
design.showUpdatedTips()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MainDesign.fetch() {
|
||||
setClashRunning(clashRunning)
|
||||
|
||||
@@ -145,7 +139,25 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
|
||||
private suspend fun queryAppVersionName(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
packageManager.getPackageInfo(packageName, 0).versionName
|
||||
packageManager.getPackageInfo(packageName, 0).versionName + "\n" + Bridge.nativeCoreVersion().replace("_", "-")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val requestPermissionLauncher =
|
||||
registerForActivityResult(RequestPermission()
|
||||
) { isGranted: Boolean ->
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
android.Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mainActivityAlias = "${MainActivity::class.java.name}Alias"
|
||||
@@ -7,6 +7,10 @@ import com.github.kr328.clash.common.compat.currentProcessName
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.remote.Remote
|
||||
import com.github.kr328.clash.service.util.sendServiceRecreated
|
||||
import com.github.kr328.clash.util.clashDir
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
class MainApplication : Application() {
|
||||
@@ -19,10 +23,8 @@ class MainApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// Initialize AppCenter
|
||||
Tracker.initialize(this)
|
||||
|
||||
val processName = currentProcessName
|
||||
extractGeoFiles()
|
||||
|
||||
Log.d("Process $processName started")
|
||||
|
||||
@@ -33,7 +35,42 @@ class MainApplication : Application() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractGeoFiles() {
|
||||
clashDir.mkdirs()
|
||||
|
||||
val updateDate = packageManager.getPackageInfo(packageName, 0).lastUpdateTime
|
||||
val geoipFile = File(clashDir, "geoip.metadb")
|
||||
if (geoipFile.exists() && geoipFile.lastModified() < updateDate) {
|
||||
geoipFile.delete()
|
||||
}
|
||||
if (!geoipFile.exists()) {
|
||||
FileOutputStream(geoipFile).use {
|
||||
assets.open("geoip.metadb").copyTo(it)
|
||||
}
|
||||
}
|
||||
|
||||
val geositeFile = File(clashDir, "geosite.dat")
|
||||
if (geositeFile.exists() && geositeFile.lastModified() < updateDate) {
|
||||
geositeFile.delete()
|
||||
}
|
||||
if (!geositeFile.exists()) {
|
||||
FileOutputStream(geositeFile).use {
|
||||
assets.open("geosite.dat").copyTo(it)
|
||||
}
|
||||
}
|
||||
|
||||
val asnFile = File(clashDir, "ASN.mmdb")
|
||||
if (asnFile.exists() && asnFile.lastModified() < updateDate) {
|
||||
asnFile.delete()
|
||||
}
|
||||
if (!asnFile.exists()) {
|
||||
FileOutputStream(asnFile).use {
|
||||
assets.open("ASN.mmdb").copyTo(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun finalize() {
|
||||
Global.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.design.MetaFeatureSettingsDesign
|
||||
import com.github.kr328.clash.util.clashDir
|
||||
import com.github.kr328.clash.util.withClash
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
|
||||
class MetaFeatureSettingsActivity : BaseActivity<MetaFeatureSettingsDesign>() {
|
||||
override suspend fun main() {
|
||||
val configuration = withClash { queryOverride(Clash.OverrideSlot.Persist) }
|
||||
|
||||
defer {
|
||||
withClash {
|
||||
patchOverride(Clash.OverrideSlot.Persist, configuration)
|
||||
}
|
||||
}
|
||||
|
||||
val design = MetaFeatureSettingsDesign(
|
||||
this,
|
||||
configuration
|
||||
)
|
||||
|
||||
setContentDesign(design)
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
|
||||
}
|
||||
design.requests.onReceive {
|
||||
when (it) {
|
||||
MetaFeatureSettingsDesign.Request.ResetOverride -> {
|
||||
if (design.requestResetConfirm()) {
|
||||
defer {
|
||||
withClash {
|
||||
clearOverride(Clash.OverrideSlot.Persist)
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
MetaFeatureSettingsDesign.Request.ImportGeoIp -> {
|
||||
val uri = startActivityForResult(
|
||||
ActivityResultContracts.GetContent(),
|
||||
"*/*")
|
||||
importGeoFile(uri, MetaFeatureSettingsDesign.Request.ImportGeoIp)
|
||||
}
|
||||
MetaFeatureSettingsDesign.Request.ImportGeoSite -> {
|
||||
val uri = startActivityForResult(
|
||||
ActivityResultContracts.GetContent(),
|
||||
"*/*")
|
||||
importGeoFile(uri, MetaFeatureSettingsDesign.Request.ImportGeoSite)
|
||||
}
|
||||
MetaFeatureSettingsDesign.Request.ImportCountry -> {
|
||||
val uri = startActivityForResult(
|
||||
ActivityResultContracts.GetContent(),
|
||||
"*/*")
|
||||
importGeoFile(uri, MetaFeatureSettingsDesign.Request.ImportCountry)
|
||||
}
|
||||
MetaFeatureSettingsDesign.Request.ImportASN -> {
|
||||
val uri = startActivityForResult(
|
||||
ActivityResultContracts.GetContent(),
|
||||
"*/*")
|
||||
importGeoFile(uri, MetaFeatureSettingsDesign.Request.ImportASN)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val validDatabaseExtensions = listOf(
|
||||
".metadb", ".db", ".dat", ".mmdb"
|
||||
)
|
||||
|
||||
private suspend fun importGeoFile(uri: Uri?, importType: MetaFeatureSettingsDesign.Request) {
|
||||
val cursor: Cursor? = uri?.let {
|
||||
contentResolver.query(it, null, null, null, null, null)
|
||||
}
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
val columnIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
val displayName: String =
|
||||
if (columnIndex != -1) it.getString(columnIndex) else "";
|
||||
val ext = "." + displayName.substringAfterLast(".")
|
||||
|
||||
if (!validDatabaseExtensions.contains(ext)) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.geofile_unknown_db_format)
|
||||
.setMessage(getString(R.string.geofile_unknown_db_format_message,
|
||||
validDatabaseExtensions.joinToString("/")))
|
||||
.setPositiveButton("OK") { _, _ -> }
|
||||
.show()
|
||||
return
|
||||
}
|
||||
val outputFileName = when (importType) {
|
||||
MetaFeatureSettingsDesign.Request.ImportGeoIp ->
|
||||
"geoip$ext"
|
||||
MetaFeatureSettingsDesign.Request.ImportGeoSite ->
|
||||
"geosite$ext"
|
||||
MetaFeatureSettingsDesign.Request.ImportCountry ->
|
||||
"country$ext"
|
||||
MetaFeatureSettingsDesign.Request.ImportASN ->
|
||||
"ASN$ext"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val outputFile = File(clashDir, outputFileName);
|
||||
contentResolver.openInputStream(uri).use { ins ->
|
||||
FileOutputStream(outputFile).use { outs ->
|
||||
ins?.copyTo(outs)
|
||||
}
|
||||
}
|
||||
}
|
||||
Toast.makeText(this, getString(R.string.geofile_imported, displayName),
|
||||
Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
}
|
||||
Toast.makeText(this, R.string.geofile_import_failed, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
class NewProfileActivity : BaseActivity<NewProfileDesign>() {
|
||||
private val self: NewProfileActivity
|
||||
|
||||
@@ -45,45 +45,14 @@ class OverrideSettingsActivity : BaseActivity<OverrideSettingsDesign>() {
|
||||
withClash {
|
||||
clearOverride(Clash.OverrideSlot.Persist)
|
||||
}
|
||||
|
||||
service.sideloadGeoip = ""
|
||||
}
|
||||
|
||||
finish()
|
||||
}
|
||||
}
|
||||
OverrideSettingsDesign.Request.EditSideloadGeoip -> {
|
||||
withContext(Dispatchers.IO) {
|
||||
val list = querySideloadProviders()
|
||||
val initial = service.sideloadGeoip
|
||||
val exist = list.any { info -> info.packageName == initial }
|
||||
|
||||
service.sideloadGeoip =
|
||||
design.requestSelectSideload(if (exist) initial else "", list)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun querySideloadProviders(): List<AppInfo> {
|
||||
val apps = packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
|
||||
.filter {
|
||||
it.applicationInfo.metaData?.containsKey(Metadata.GEOIP_FILE_NAME)
|
||||
?: false
|
||||
}
|
||||
.map { it.toAppInfo(packageManager) }
|
||||
|
||||
return listOf(
|
||||
AppInfo(
|
||||
packageName = "",
|
||||
label = getString(R.string.use_built_in),
|
||||
icon = getDrawableCompat(R.drawable.ic_baseline_work)!!,
|
||||
installTime = 0,
|
||||
updateDate = 0,
|
||||
)
|
||||
) + apps
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,24 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.setUUID
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.design.ProfilesDesign
|
||||
import com.github.kr328.clash.design.ui.ToastDuration
|
||||
import com.github.kr328.clash.service.model.Profile
|
||||
import com.github.kr328.clash.util.withProfile
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
class ProfilesActivity : BaseActivity<ProfilesDesign>() {
|
||||
override suspend fun main() {
|
||||
@@ -34,9 +44,16 @@ class ProfilesActivity : BaseActivity<ProfilesDesign>() {
|
||||
startActivity(NewProfileActivity::class.intent)
|
||||
ProfilesDesign.Request.UpdateAll ->
|
||||
withProfile {
|
||||
queryAll().forEach { p ->
|
||||
if (p.imported && p.type != Profile.Type.File)
|
||||
update(p.uuid)
|
||||
try {
|
||||
queryAll().forEach { p ->
|
||||
if (p.imported && p.type != Profile.Type.File)
|
||||
update(p.uuid)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
withContext(Dispatchers.Main) {
|
||||
design.finishUpdateAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
is ProfilesDesign.Request.Update ->
|
||||
@@ -74,4 +91,37 @@ class ProfilesActivity : BaseActivity<ProfilesDesign>() {
|
||||
patchProfiles(queryAll())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProfileUpdateCompleted(uuid: UUID?) {
|
||||
if(uuid == null)
|
||||
return;
|
||||
launch {
|
||||
var name: String? = null;
|
||||
withProfile {
|
||||
name = queryByUUID(uuid)?.name
|
||||
}
|
||||
design?.showToast(
|
||||
getString(R.string.toast_profile_updated_complete, name),
|
||||
ToastDuration.Long
|
||||
)
|
||||
}
|
||||
}
|
||||
override fun onProfileUpdateFailed(uuid: UUID?, reason: String?) {
|
||||
if(uuid == null)
|
||||
return;
|
||||
launch {
|
||||
var name: String? = null;
|
||||
withProfile {
|
||||
name = queryByUUID(uuid)?.name
|
||||
}
|
||||
design?.showToast(
|
||||
getString(R.string.toast_profile_updated_failed, name, reason),
|
||||
ToastDuration.Long
|
||||
){
|
||||
setAction(R.string.edit) {
|
||||
startActivity(PropertiesActivity::class.intent.setUUID(uuid))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,11 @@ import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
class PropertiesActivity : BaseActivity<PropertiesDesign>() {
|
||||
private var canceled: Boolean = false
|
||||
private lateinit var original: Profile
|
||||
|
||||
override suspend fun main() {
|
||||
setResult(RESULT_CANCELED)
|
||||
@@ -22,7 +24,7 @@ class PropertiesActivity : BaseActivity<PropertiesDesign>() {
|
||||
val uuid = intent.uuid ?: return finish()
|
||||
val design = PropertiesDesign(this)
|
||||
|
||||
val original = withProfile { queryByUUID(uuid) } ?: return finish()
|
||||
original = withProfile { queryByUUID(uuid) } ?: return finish()
|
||||
|
||||
design.profile = original
|
||||
|
||||
@@ -71,7 +73,7 @@ class PropertiesActivity : BaseActivity<PropertiesDesign>() {
|
||||
design?.apply {
|
||||
launch {
|
||||
if (!progressing) {
|
||||
if (requestExitWithoutSaving())
|
||||
if (original == profile || requestExitWithoutSaving())
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
class ProvidersActivity : BaseActivity<ProvidersDesign>() {
|
||||
override suspend fun main() {
|
||||
|
||||
@@ -5,15 +5,12 @@ import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.model.Proxy
|
||||
import com.github.kr328.clash.design.ProxyDesign
|
||||
import com.github.kr328.clash.design.model.ProxyState
|
||||
import com.github.kr328.clash.store.TipsStore
|
||||
import com.github.kr328.clash.util.withClash
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
override suspend fun main() {
|
||||
@@ -22,7 +19,6 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
val states = List(names.size) { ProxyState("?") }
|
||||
val unorderedStates = names.indices.map { names[it] to states[it] }.toMap()
|
||||
val reloadLock = Semaphore(10)
|
||||
val tips = TipsStore(this)
|
||||
|
||||
val design = ProxyDesign(
|
||||
this,
|
||||
@@ -33,17 +29,6 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
|
||||
setContentDesign(design)
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
val pkg = packageManager.getPackageInfo(packageName, 0)
|
||||
val validate = System.currentTimeMillis() - pkg.firstInstallTime > TimeUnit.DAYS.toMillis(5)
|
||||
|
||||
if (tips.requestDonate && validate) {
|
||||
tips.requestDonate = false
|
||||
|
||||
design.requestDonate()
|
||||
}
|
||||
}
|
||||
|
||||
design.requests.send(ProxyDesign.Request.ReloadAll)
|
||||
|
||||
while (isActive) {
|
||||
|
||||
@@ -24,6 +24,8 @@ class SettingsActivity : BaseActivity<SettingsDesign>() {
|
||||
startActivity(NetworkSettingsActivity::class.intent)
|
||||
SettingsDesign.Request.StartOverride ->
|
||||
startActivity(OverrideSettingsActivity::class.intent)
|
||||
SettingsDesign.Request.StartMetaFeature ->
|
||||
startActivity(MetaFeatureSettingsActivity::class.intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import android.os.Build
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.github.kr328.clash.common.compat.registerReceiverCompat
|
||||
import com.github.kr328.clash.common.constants.Intents
|
||||
import com.github.kr328.clash.common.constants.Permissions
|
||||
import com.github.kr328.clash.remote.StatusClient
|
||||
import com.github.kr328.clash.util.startClashService
|
||||
import com.github.kr328.clash.util.stopClashService
|
||||
import com.github.kr328.clash.service.R
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
class TileService : TileService() {
|
||||
@@ -36,7 +38,7 @@ class TileService : TileService() {
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
|
||||
registerReceiver(
|
||||
registerReceiverCompat(
|
||||
receiver,
|
||||
IntentFilter().apply {
|
||||
addAction(Intents.ACTION_CLASH_STARTED)
|
||||
|
||||
@@ -16,16 +16,28 @@ class LogcatReader(context: Context, file: LogFile) : AutoCloseable {
|
||||
}
|
||||
|
||||
fun readAll(): List<LogMessage> {
|
||||
var lastTime = Date(0)
|
||||
return reader.lineSequence()
|
||||
.map { it.trim() }
|
||||
.filter { !it.startsWith("#") }
|
||||
.map { it.split(":", limit = 3) }
|
||||
.map {
|
||||
LogMessage(
|
||||
time = Date(it[0].toLong()),
|
||||
level = LogMessage.Level.valueOf(it[1]),
|
||||
message = it[2]
|
||||
)
|
||||
val time = it[0].toLongOrNull()?.let { Date(it) } ?: lastTime
|
||||
val logMessage = if (it[0].toLongOrNull() != null) {
|
||||
LogMessage(
|
||||
time = time,
|
||||
level = LogMessage.Level.valueOf(it[1]),
|
||||
message = it[2]
|
||||
)
|
||||
} else {
|
||||
LogMessage(
|
||||
time = time,
|
||||
level = LogMessage.Level.Warning, // or any default level
|
||||
message = it.joinToString(":")
|
||||
)
|
||||
}
|
||||
lastTime = time
|
||||
logMessage
|
||||
}
|
||||
.toList()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ object SystemLogcat {
|
||||
"Go",
|
||||
"DEBUG",
|
||||
"AndroidRuntime",
|
||||
"ClashForAndroid",
|
||||
"ClashMetaForAndroid",
|
||||
"LwIP",
|
||||
)
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import com.github.kr328.clash.common.compat.registerReceiverCompat
|
||||
import com.github.kr328.clash.common.constants.Intents
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import java.util.*
|
||||
|
||||
class Broadcasts(private val context: Application) {
|
||||
interface Observer {
|
||||
@@ -14,6 +16,8 @@ class Broadcasts(private val context: Application) {
|
||||
fun onStarted()
|
||||
fun onStopped(cause: String?)
|
||||
fun onProfileChanged()
|
||||
fun onProfileUpdateCompleted(uuid: UUID?)
|
||||
fun onProfileUpdateFailed(uuid: UUID?, reason: String?)
|
||||
fun onProfileLoaded()
|
||||
}
|
||||
|
||||
@@ -52,6 +56,17 @@ class Broadcasts(private val context: Application) {
|
||||
receivers.forEach {
|
||||
it.onProfileChanged()
|
||||
}
|
||||
Intents.ACTION_PROFILE_UPDATE_COMPLETED ->
|
||||
receivers.forEach {
|
||||
it.onProfileUpdateCompleted(
|
||||
UUID.fromString(intent.getStringExtra(Intents.EXTRA_UUID)))
|
||||
}
|
||||
Intents.ACTION_PROFILE_UPDATE_FAILED ->
|
||||
receivers.forEach {
|
||||
it.onProfileUpdateFailed(
|
||||
UUID.fromString(intent.getStringExtra(Intents.EXTRA_UUID)),
|
||||
intent.getStringExtra(Intents.EXTRA_FAIL_REASON))
|
||||
}
|
||||
Intents.ACTION_PROFILE_LOADED -> {
|
||||
receivers.forEach {
|
||||
it.onProfileLoaded()
|
||||
@@ -74,11 +89,13 @@ class Broadcasts(private val context: Application) {
|
||||
return
|
||||
|
||||
try {
|
||||
context.registerReceiver(broadcastReceiver, IntentFilter().apply {
|
||||
context.registerReceiverCompat(broadcastReceiver, IntentFilter().apply {
|
||||
addAction(Intents.ACTION_SERVICE_RECREATED)
|
||||
addAction(Intents.ACTION_CLASH_STARTED)
|
||||
addAction(Intents.ACTION_CLASH_STOPPED)
|
||||
addAction(Intents.ACTION_PROFILE_CHANGED)
|
||||
addAction(Intents.ACTION_PROFILE_UPDATE_COMPLETED)
|
||||
addAction(Intents.ACTION_PROFILE_UPDATE_FAILED)
|
||||
addAction(Intents.ACTION_PROFILE_LOADED)
|
||||
})
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent
|
||||
import com.github.kr328.clash.ApkBrokenActivity
|
||||
import com.github.kr328.clash.AppCrashedActivity
|
||||
import com.github.kr328.clash.common.Global
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.store.AppStore
|
||||
import com.github.kr328.clash.util.ApplicationObserver
|
||||
@@ -29,14 +30,25 @@ object Remote {
|
||||
fun launch() {
|
||||
ApplicationObserver.attach(Global.application)
|
||||
|
||||
ApplicationObserver.onVisibleChanged { visible.trySend(it) }
|
||||
ApplicationObserver.onVisibleChanged {
|
||||
if(it) {
|
||||
Log.d("App becomes visible")
|
||||
service.bind()
|
||||
broadcasts.register()
|
||||
}
|
||||
else {
|
||||
Log.d("App becomes invisible")
|
||||
service.unbind()
|
||||
broadcasts.unregister()
|
||||
}
|
||||
}
|
||||
|
||||
Global.launch(Dispatchers.IO) {
|
||||
run()
|
||||
verifyApp()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun run() {
|
||||
private suspend fun verifyApp() {
|
||||
val context = Global.application
|
||||
val store = AppStore(context)
|
||||
val updatedAt = getLastUpdated(context)
|
||||
@@ -53,16 +65,6 @@ object Remote {
|
||||
store.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (visible.receive()) {
|
||||
service.bind()
|
||||
broadcasts.register()
|
||||
} else {
|
||||
service.unbind()
|
||||
broadcasts.unregister()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLastUpdated(context: Context): Long {
|
||||
|
||||
@@ -5,10 +5,8 @@ import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import com.github.kr328.clash.Tracker
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.log.SystemLogcat
|
||||
import com.github.kr328.clash.service.RemoteService
|
||||
import com.github.kr328.clash.service.remote.IRemoteService
|
||||
import com.github.kr328.clash.service.remote.unwrap
|
||||
@@ -28,8 +26,6 @@ class Service(private val context: Application, val crashed: () -> Unit) {
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
remote.set(null)
|
||||
|
||||
Tracker.uploadLogcat(SystemLogcat.dumpCrash())
|
||||
|
||||
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
|
||||
unbind()
|
||||
|
||||
@@ -37,8 +33,7 @@ class Service(private val context: Application, val crashed: () -> Unit) {
|
||||
}
|
||||
|
||||
lastCrashed = System.currentTimeMillis()
|
||||
|
||||
Log.w("RemoteManager crashed")
|
||||
Log.w("RemoteService killed or crashed")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,16 +7,12 @@ import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ActivityResultLifecycle : LifecycleOwner {
|
||||
private val lifecycle = LifecycleRegistry(this)
|
||||
override val lifecycle = LifecycleRegistry(this)
|
||||
|
||||
init {
|
||||
lifecycle.currentState = Lifecycle.State.INITIALIZED
|
||||
}
|
||||
|
||||
override fun getLifecycle(): Lifecycle {
|
||||
return lifecycle
|
||||
}
|
||||
|
||||
suspend fun <T> use(block: suspend (lifecycle: ActivityResultLifecycle, start: () -> Unit) -> T): T {
|
||||
return try {
|
||||
markCreated()
|
||||
|
||||
@@ -9,7 +9,8 @@ import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
object ApplicationObserver {
|
||||
private val activities: MutableSet<Activity> = mutableSetOf()
|
||||
private val _createdActivities: MutableSet<Activity> = mutableSetOf()
|
||||
private val _visibleActivities: MutableSet<Activity> = mutableSetOf()
|
||||
|
||||
private var visibleChanged: (Boolean) -> Unit = {}
|
||||
|
||||
@@ -23,25 +24,31 @@ object ApplicationObserver {
|
||||
}
|
||||
|
||||
val createdActivities: Set<Activity>
|
||||
get() = activities
|
||||
get() = _createdActivities
|
||||
|
||||
private val activityObserver = object : Application.ActivityLifecycleCallbacks {
|
||||
@Synchronized
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
activities.add(activity)
|
||||
|
||||
appVisible = true
|
||||
_createdActivities.add(activity)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
activities.remove(activity)
|
||||
|
||||
appVisible = activities.isNotEmpty()
|
||||
_createdActivities.remove(activity)
|
||||
_visibleActivities.remove(activity)
|
||||
appVisible = _visibleActivities.isNotEmpty()
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
_visibleActivities.add(activity)
|
||||
appVisible = true
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
_visibleActivities.remove(activity)
|
||||
appVisible = _visibleActivities.isNotEmpty()
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {}
|
||||
override fun onActivityStopped(activity: Activity) {}
|
||||
override fun onActivityPaused(activity: Activity) {}
|
||||
override fun onActivityResumed(activity: Activity) {}
|
||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
||||
|
||||
@@ -4,4 +4,7 @@ import android.content.Context
|
||||
import java.io.File
|
||||
|
||||
val Context.logsDir: File
|
||||
get() = cacheDir.resolve("logs")
|
||||
get() = cacheDir.resolve("logs")
|
||||
|
||||
val Context.clashDir: File
|
||||
get() = filesDir.resolve("clash")
|
||||
@@ -2,14 +2,15 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="406.92642"
|
||||
android:viewportHeight="406.92642">
|
||||
<group
|
||||
android:translateX="103.4632"
|
||||
android:translateY="103.4632">
|
||||
android:viewportWidth="403"
|
||||
android:viewportHeight="403"
|
||||
android:name="vector">
|
||||
<group>
|
||||
<path
|
||||
android:fillColor="#1E4376"
|
||||
android:pathData="M47.211,168.128C70.531,-34.962 67.471,13.788 94.071,43.818c13.45,-1.52 27.24,-3.47 40.82,-0.67c2.64,0.13 5.42,1.86 7.71,0.18c4.12,-6.27 7.35,-13.54 11.35,-20c12.19,-24.44 12.85,19.54 15.48,26.52c5.23,32.99 10.89,64.46 14.67,97.59c0.31,10.72 5.74,32.92 1.08,33.56c-49.36,5.23 -147.71,3.91 -160.84,-6.3c-15.85,-10.5 -15.18,-35.33 2.03,-43.72c3.63,-2.03 10.68,-3.72 11.94,0.7c-2.41,4.99 -8.79,5.77 -12.12,11.17C16.621,158.948 33.111,168.888 47.211,168.128zM87.841,74.008c-10.42,0.52 -9.59,14.89 -0.07,15.18C98.191,88.668 97.361,74.298 87.841,74.008zM149.121,89.188c10.46,-0.34 9.85,-14.71 0.38,-15.18C139.031,74.348 139.651,88.718 149.121,89.188zM107.871,99.228c2.16,3.48 5.28,3.29 9.79,0.16c3.81,3.17 8.06,3.28 9.18,-0.19c-3.78,1.17 -7.04,0.79 -9.4,-3.49C115.371,100.108 112.071,100.428 107.871,99.228z"
|
||||
tools:ignore="VectorPath" />
|
||||
android:pathData="M 141.08 128.47 C 141.88 128.42 142.72 128.38 143.41 128.8 C 144.51 129.46 145.39 130.41 146.33 131.25 C 160.38 144.1 174.48 156.9 188.53 169.75 C 189.48 170.59 190.37 171.56 191.54 172.11 C 192.45 172.55 193.49 172.33 194.46 172.2 C 197.04 171.76 199.66 171.74 202.26 171.74 C 204.77 171.78 207.29 171.86 209.77 172.27 C 210.77 172.38 211.95 172.71 212.71 171.84 C 228.19 157.7 243.73 143.58 259.23 129.43 C 260.54 128.06 262.55 128.36 264.26 128.57 C 268.58 129.21 272.94 129.51 277.28 130 C 278 130.14 279.02 130.19 279.27 131.05 C 279.61 132.54 279.42 134.07 279.46 135.58 C 279.44 166.17 279.44 196.77 279.42 227.36 C 279.41 228.92 279.51 230.47 279.35 232.01 C 279.29 232.56 279.22 233.21 278.76 233.59 C 277.8 234.01 276.74 234.17 275.72 234.42 C 271.39 235.43 266.98 236.16 262.66 237.26 C 260.49 237.83 258.25 237.61 256.04 237.64 C 254.91 237.63 253.74 237.73 252.61 237.54 C 251.95 237.49 251.48 236.9 251.43 236.26 C 251.19 234.74 251.33 233.2 251.31 231.67 C 251.29 216.61 251.28 201.54 251.29 186.48 C 251.27 185 251.38 183.49 251.17 182.01 C 250.9 180.3 249.11 179.1 247.44 179.31 C 246.51 179.32 245.72 179.9 245.05 180.49 C 237.57 187.38 230 194.15 222.54 201.02 C 221.68 201.76 220.7 202.63 219.48 202.51 C 217.88 202.37 216.37 201.8 214.82 201.44 C 208.61 199.89 202.12 199.7 195.77 200.24 C 192.68 200.67 189.56 201.02 186.59 202 C 185.63 202.27 184.66 202.57 183.67 202.52 C 182.65 202.45 181.86 201.78 181.14 201.15 C 173.72 194.33 166.23 187.63 158.81 180.81 C 158.09 180.18 157.31 179.49 156.33 179.34 C 155.1 179.19 153.83 179.69 152.99 180.59 C 152.26 181.42 152.23 182.56 152.19 183.61 C 152.23 200.72 152.18 217.81 152.21 234.91 C 152.18 235.57 152.18 236.24 151.92 236.87 C 151.53 237.58 150.62 237.61 149.91 237.64 C 147.99 237.68 146.08 237.63 144.16 237.64 C 141.99 237.68 139.9 237.05 137.81 236.62 C 133.97 235.77 130.14 234.91 126.29 234.1 C 125.48 233.9 124.35 233.66 124.2 232.66 C 123.96 230.91 124.13 229.14 124.1 227.36 C 124.06 196.88 124.08 166.41 124.08 135.93 C 124.08 134.49 124 133.06 124.13 131.64 C 124.18 131.12 124.33 130.51 124.84 130.24 C 125.38 130 125.99 129.97 126.56 129.88 C 131.42 129.55 136.25 128.96 141.08 128.47 Z M 198.81 240.82 C 200.99 240.64 203.19 240.7 205.35 240.82 C 207.04 240.8 208.08 242.85 207.31 244.28 C 206.36 246.17 205.38 248.03 204.27 249.84 C 203.56 251.14 201.5 251.39 200.57 250.19 C 199.67 249.09 199.15 247.78 198.44 246.56 C 197.9 245.55 197.21 244.59 196.96 243.44 C 196.7 242.24 197.62 240.99 198.81 240.82 Z"
|
||||
android:fillColor="#3372b6"/>
|
||||
<path
|
||||
android:pathData="M 125.67 244.75 C 126.09 244.7 126.51 244.67 126.95 244.67 C 139.02 244.7 151.09 244.69 163.19 244.69 C 164.26 244.67 165.5 244.94 166.14 245.92 C 166.8 247.02 166.66 248.72 165.52 249.45 C 164.74 249.99 163.74 249.99 162.83 250.02 C 150.88 249.99 138.9 250.01 126.95 250.02 C 126.28 249.99 125.6 249.99 124.97 249.77 C 123.94 249.38 123.25 248.25 123.4 247.15 C 123.49 245.97 124.43 244.86 125.67 244.76 L 125.67 244.75 Z M 239.67 244.75 C 241.29 244.59 242.93 244.72 244.57 244.69 C 255.33 244.67 266.1 244.72 276.86 244.67 C 277.55 244.7 278.27 244.69 278.92 244.97 C 279.88 245.4 280.42 246.44 280.42 247.47 C 280.44 248.67 279.44 249.8 278.24 249.92 C 276.7 250.11 275.15 249.99 273.61 250 L 243.85 250 C 242.33 249.99 240.79 250.12 239.29 249.87 C 238.15 249.67 237.23 248.64 237.31 247.46 C 237.16 246.07 238.34 244.85 239.67 244.75 Z M 162.61 257.67 C 163.41 257.45 164.3 257.33 165.06 257.72 C 166.65 258.45 167.02 260.98 165.6 262.08 C 164.76 262.67 163.74 262.91 162.8 263.24 C 159.22 264.36 155.74 265.76 152.16 266.87 C 148.04 268.17 144 269.71 139.9 271.09 C 136.91 272.06 133.94 273.07 130.97 274.1 C 129.73 274.52 128.54 275.06 127.27 275.33 C 126.49 275.5 125.63 275.35 125.02 274.83 C 124.01 274.03 123.82 272.5 124.4 271.4 C 124.87 270.6 125.82 270.32 126.63 270 C 129.2 269.1 131.73 268.17 134.31 267.36 C 140.78 265.27 147.14 262.84 153.63 260.8 C 156.65 259.81 159.59 258.62 162.61 257.67 Z M 239.3 257.54 C 240.23 257.32 241.14 257.67 242.02 257.92 C 244.84 258.89 247.61 259.97 250.45 260.88 C 257.12 262.99 263.65 265.51 270.34 267.62 C 272.53 268.33 274.71 269.14 276.91 269.9 C 277.73 270.22 278.64 270.49 279.25 271.18 C 280.21 272.5 279.64 274.64 278.1 275.23 C 277.11 275.62 276.06 275.23 275.1 274.93 C 271.13 273.46 267.11 272.14 263.09 270.83 C 259.18 269.47 255.29 268.04 251.36 266.79 C 247.78 265.66 244.32 264.26 240.74 263.16 C 239.79 262.82 238.78 262.59 238.02 261.91 C 236.65 260.58 237.39 257.88 239.3 257.54 Z"
|
||||
android:fillColor="#f39800"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/color_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/color_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
4
app/src/main/res/values/ic_banner_background.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_banner_background">#FFFFFF</color>
|
||||
</resources>
|
||||
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Application
|
||||
import com.microsoft.appcenter.AppCenter
|
||||
import com.microsoft.appcenter.analytics.Analytics
|
||||
import com.microsoft.appcenter.crashes.Crashes
|
||||
import com.microsoft.appcenter.crashes.ingestion.models.ErrorAttachmentLog
|
||||
|
||||
object Tracker {
|
||||
fun initialize(application: Application) {
|
||||
if (BuildConfig.APP_CENTER_KEY != null && !BuildConfig.DEBUG) {
|
||||
AppCenter.start(
|
||||
application,
|
||||
BuildConfig.APP_CENTER_KEY,
|
||||
Analytics::class.java, Crashes::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun uploadLogcat(logcat: String) {
|
||||
if (BuildConfig.APP_CENTER_KEY != null && !BuildConfig.DEBUG) {
|
||||
if (logcat.isNotBlank()) {
|
||||
Crashes.trackError(
|
||||
RuntimeException(),
|
||||
mapOf("type" to "app_crashed"),
|
||||
listOf(ErrorAttachmentLog.attachmentWithText(logcat, "logcat.txt"))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
174
build.gradle.kts
@@ -1,82 +1,142 @@
|
||||
@file:Suppress("UNUSED_VARIABLE")
|
||||
|
||||
import com.android.build.gradle.AppExtension
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
google()
|
||||
maven("https://maven.kr328.app/releases")
|
||||
maven("https://raw.githubusercontent.com/MetaCubeX/maven-backup/main/releases")
|
||||
}
|
||||
dependencies {
|
||||
classpath(deps.build.android)
|
||||
classpath(deps.build.kotlin.common)
|
||||
classpath(deps.build.kotlin.serialization)
|
||||
classpath(deps.build.ksp)
|
||||
classpath(deps.build.golang)
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven("https://maven.kr328.app/releases")
|
||||
classpath(libs.build.android)
|
||||
classpath(libs.build.kotlin.common)
|
||||
classpath(libs.build.kotlin.serialization)
|
||||
classpath(libs.build.ksp)
|
||||
classpath(libs.build.golang)
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven("https://raw.githubusercontent.com/MetaCubeX/maven-backup/main/releases")
|
||||
}
|
||||
|
||||
val isApp = name == "app"
|
||||
|
||||
apply(plugin = if (isApp) "com.android.application" else "com.android.library")
|
||||
|
||||
extensions.configure(BaseExtension::class) {
|
||||
val minSdkVersion = 21
|
||||
val targetSdkVersion = 30
|
||||
val buildVersionCode = 204009
|
||||
val buildVersionName = "2.4.9"
|
||||
val defaultDimension = "feature"
|
||||
|
||||
ndkVersion = "23.0.7599858"
|
||||
|
||||
compileSdkVersion(targetSdkVersion)
|
||||
|
||||
extensions.configure<BaseExtension> {
|
||||
buildFeatures.buildConfig = true
|
||||
defaultConfig {
|
||||
if (isApp) {
|
||||
applicationId = "com.github.kr328.clash"
|
||||
applicationId = "com.github.metacubex.clash"
|
||||
}
|
||||
|
||||
minSdk = minSdkVersion
|
||||
targetSdk = targetSdkVersion
|
||||
|
||||
versionName = buildVersionName
|
||||
versionCode = buildVersionCode
|
||||
|
||||
if (!isApp) {
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
project.name.let { name ->
|
||||
namespace = if (name == "app") "com.github.kr328.clash"
|
||||
else "com.github.kr328.clash.$name"
|
||||
}
|
||||
|
||||
resValue("string", "release_name", "v$buildVersionName")
|
||||
resValue("integer", "release_code", "$buildVersionCode")
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
|
||||
versionName = "2.11.15"
|
||||
versionCode = 211015
|
||||
|
||||
resValue("string", "release_name", "v$versionName")
|
||||
resValue("integer", "release_code", "$versionCode")
|
||||
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.18.1"
|
||||
if (!isApp) {
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
} else {
|
||||
setProperty("archivesBaseName", "cmfa-$versionName")
|
||||
}
|
||||
}
|
||||
|
||||
ndkVersion = "27.2.12479018"
|
||||
|
||||
compileSdkVersion(defaultConfig.targetSdk!!)
|
||||
|
||||
if (isApp) {
|
||||
packagingOptions {
|
||||
excludes.add("DebugProbesKt.bin")
|
||||
resources {
|
||||
excludes.add("DebugProbesKt.bin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
flavorDimensions("feature")
|
||||
|
||||
create("alpha") {
|
||||
isDefault = true
|
||||
dimension = flavorDimensionList[0]
|
||||
versionNameSuffix = ".Alpha"
|
||||
|
||||
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
|
||||
|
||||
resValue("string", "launch_name", "@string/launch_name_alpha")
|
||||
resValue("string", "application_name", "@string/application_name_alpha")
|
||||
|
||||
if (isApp) {
|
||||
applicationIdSuffix = ".alpha"
|
||||
}
|
||||
}
|
||||
|
||||
create("meta") {
|
||||
|
||||
dimension = flavorDimensionList[0]
|
||||
versionNameSuffix = ".Meta"
|
||||
|
||||
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
|
||||
|
||||
resValue("string", "launch_name", "@string/launch_name_meta")
|
||||
resValue("string", "application_name", "@string/application_name_meta")
|
||||
|
||||
if (isApp) {
|
||||
applicationIdSuffix = ".meta"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("meta") {
|
||||
java.srcDirs("src/foss/java")
|
||||
}
|
||||
getByName("alpha") {
|
||||
java.srcDirs("src/foss/java")
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
val keystore = rootProject.file("signing.properties")
|
||||
if (keystore.exists()) {
|
||||
create("release") {
|
||||
val prop = Properties().apply {
|
||||
keystore.inputStream().use(this::load)
|
||||
}
|
||||
|
||||
storeFile = rootProject.file("release.keystore")
|
||||
storePassword = prop.getProperty("keystore.password")!!
|
||||
keyAlias = prop.getProperty("key.alias")!!
|
||||
keyPassword = prop.getProperty("key.password")!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,27 +144,14 @@ subprojects {
|
||||
named("release") {
|
||||
isMinifyEnabled = isApp
|
||||
isShrinkResources = isApp
|
||||
signingConfig = signingConfigs.findByName("release") ?: signingConfigs["debug"]
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
flavorDimensions(defaultDimension)
|
||||
|
||||
create("foss") {
|
||||
dimension = defaultDimension
|
||||
versionNameSuffix = ".foss"
|
||||
|
||||
if (isApp) {
|
||||
applicationIdSuffix = ".foss"
|
||||
}
|
||||
}
|
||||
create("premium") {
|
||||
dimension = defaultDimension
|
||||
versionNameSuffix = ".premium"
|
||||
named("debug") {
|
||||
versionNameSuffix = ".debug"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,13 +162,22 @@ subprojects {
|
||||
}
|
||||
|
||||
if (isApp) {
|
||||
this as AppExtension
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
isUniversalApk = true
|
||||
reset()
|
||||
include("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +185,7 @@ task("clean", type = Delete::class) {
|
||||
delete(rootProject.buildDir)
|
||||
}
|
||||
|
||||
tasks.named<Wrapper>("wrapper") {
|
||||
tasks.wrapper {
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
|
||||
doLast {
|
||||
|
||||
@@ -6,7 +6,6 @@ plugins {
|
||||
dependencies {
|
||||
compileOnly(project(":hideapi"))
|
||||
|
||||
implementation(kotlin("stdlib-jdk7"))
|
||||
implementation(deps.kotlin.coroutine)
|
||||
implementation(deps.androidx.core)
|
||||
implementation(libs.kotlin.coroutine)
|
||||
implementation(libs.androidx.core)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.github.kr328.clash.common">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<permission
|
||||
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.app.ActivityThread
|
||||
import android.app.Application
|
||||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
|
||||
@@ -18,3 +20,12 @@ val Application.currentProcessName: String
|
||||
packageName
|
||||
}
|
||||
}
|
||||
|
||||
fun Drawable.foreground(): Drawable {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||
this is AdaptiveIconDrawable && this.background == null
|
||||
) {
|
||||
return this.foreground
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -2,21 +2,36 @@
|
||||
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
fun Context.getColorCompat(@ColorRes id: Int): Int {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
this.getColor(id)
|
||||
} else {
|
||||
resources.getColor(id)
|
||||
}
|
||||
return ContextCompat.getColor(this, id)
|
||||
}
|
||||
|
||||
fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(this, id)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
fun Context.registerReceiverCompat(
|
||||
receiver: BroadcastReceiver,
|
||||
filter: IntentFilter,
|
||||
permission: String? = null,
|
||||
handler: Handler? = null
|
||||
) =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
registerReceiver(receiver, filter, permission, handler,
|
||||
if (permission == null) Context.RECEIVER_EXPORTED else Context.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
else
|
||||
registerReceiver(receiver, filter, permission, handler)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import android.text.Html
|
||||
import android.text.Spanned
|
||||
|
||||
fun fromHtmlCompat(content: String): Spanned {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return if (Build.VERSION.SDK_INT >= 24) {
|
||||
Html.fromHtml(content, Html.FROM_HTML_MODE_COMPACT)
|
||||
} else {
|
||||
Html.fromHtml(content)
|
||||
|
||||
@@ -3,9 +3,13 @@ package com.github.kr328.clash.common.compat
|
||||
import android.app.PendingIntent
|
||||
import android.os.Build
|
||||
|
||||
fun pendingIntentFlags(flags: Int, immutable: Boolean = false): Int {
|
||||
return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M && immutable) {
|
||||
flags or PendingIntent.FLAG_IMMUTABLE
|
||||
fun pendingIntentFlags(flags: Int, mutable: Boolean = false): Int {
|
||||
return if (Build.VERSION.SDK_INT >= 24) {
|
||||
if (Build.VERSION.SDK_INT > 30 && mutable) {
|
||||
flags or PendingIntent.FLAG_MUTABLE
|
||||
} else {
|
||||
flags or PendingIntent.FLAG_IMMUTABLE
|
||||
}
|
||||
} else {
|
||||
flags
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.content.pm.PackageInfo
|
||||
|
||||
val PackageInfo.versionCodeCompat: Long
|
||||
get() {
|
||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
|
||||
return if (android.os.Build.VERSION.SDK_INT >= 28) {
|
||||
longVersionCode
|
||||
} else {
|
||||
versionCode.toLong()
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.*
|
||||
|
||||
val Configuration.preferredLocale: Locale
|
||||
get() {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return if (Build.VERSION.SDK_INT >= 24) {
|
||||
locales[0]
|
||||
} else {
|
||||
locale
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
package com.github.kr328.clash.common.compat
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
|
||||
fun Context.startForegroundServiceCompat(intent: Intent) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
startForegroundService(intent)
|
||||
} else {
|
||||
startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Service.startForegroundCompat(id: Int, notification: Notification) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
} else {
|
||||
startForeground(id, notification)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import com.github.kr328.clash.common.util.packageName
|
||||
object Intents {
|
||||
// Public
|
||||
val ACTION_PROVIDE_URL = "$packageName.action.PROVIDE_URL"
|
||||
val ACTION_START_CLASH = "$packageName.action.START_CLASH"
|
||||
val ACTION_STOP_CLASH = "$packageName.action.STOP_CLASH"
|
||||
val ACTION_TOGGLE_CLASH = "$packageName.action.TOGGLE_CLASH"
|
||||
|
||||
const val EXTRA_NAME = "name"
|
||||
|
||||
@@ -14,6 +17,8 @@ object Intents {
|
||||
val ACTION_CLASH_STOPPED = "$packageName.intent.action.CLASH_STOPPED"
|
||||
val ACTION_CLASH_REQUEST_STOP = "$packageName.intent.action.CLASH_REQUEST_STOP"
|
||||
val ACTION_PROFILE_CHANGED = "$packageName.intent.action.PROFILE_CHANGED"
|
||||
val ACTION_PROFILE_UPDATE_COMPLETED = "$packageName.intent.action.PROFILE_UPDATE_COMPLETED"
|
||||
val ACTION_PROFILE_UPDATE_FAILED = "$packageName.intent.action.PROFILE_UPDATE_FAILED"
|
||||
val ACTION_PROFILE_REQUEST_UPDATE = "$packageName.intent.action.REQUEST_UPDATE"
|
||||
val ACTION_PROFILE_SCHEDULE_UPDATES = "$packageName.intent.action.SCHEDULE_UPDATES"
|
||||
val ACTION_PROFILE_LOADED = "$packageName.intent.action.PROFILE_LOADED"
|
||||
@@ -21,4 +26,5 @@ object Intents {
|
||||
|
||||
const val EXTRA_STOP_REASON = "stop_reason"
|
||||
const val EXTRA_UUID = "uuid"
|
||||
const val EXTRA_FAIL_REASON = "fail_reason"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.github.kr328.clash.common.log
|
||||
|
||||
object Log {
|
||||
private const val TAG = "ClashForAndroid"
|
||||
private const val TAG = "ClashMetaForAndroid"
|
||||
|
||||
fun i(message: String, throwable: Throwable? = null) =
|
||||
android.util.Log.i(TAG, message, throwable)
|
||||
|
||||
5
common/src/main/res/values-ru/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="receive_clash_broadcasts">Получать оповещения от Clash</string>
|
||||
<string name="receive_broadcasts_of_clash">Получать оповещения от сервисов Clash</string>
|
||||
</resources>
|
||||
1
core/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/src/main/cpp/version.h
|
||||
@@ -1,7 +1,6 @@
|
||||
import android.databinding.tool.ext.capitalizeUS
|
||||
import com.github.kr328.golang.GolangBuildTask
|
||||
import com.github.kr328.golang.GolangPlugin
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URL
|
||||
import java.time.Duration
|
||||
|
||||
plugins {
|
||||
kotlin("android")
|
||||
@@ -10,21 +9,21 @@ plugins {
|
||||
id("golang-android")
|
||||
}
|
||||
|
||||
val geoipDatabaseUrl =
|
||||
"https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb"
|
||||
val geoipInvalidate = Duration.ofDays(7)!!
|
||||
val geoipOutput = buildDir.resolve("intermediates/golang_blob")
|
||||
val golangSource = file("src/main/golang/native")
|
||||
|
||||
golang {
|
||||
sourceSets {
|
||||
create("foss") {
|
||||
srcDir.set(file("src/foss/go"))
|
||||
create("alpha") {
|
||||
tags.set(listOf("foss","with_gvisor","cmfa"))
|
||||
srcDir.set(file("src/foss/golang"))
|
||||
}
|
||||
create("premium") {
|
||||
srcDir.set(file("src/premium/go"))
|
||||
create("meta") {
|
||||
tags.set(listOf("foss","with_gvisor","cmfa"))
|
||||
srcDir.set(file("src/foss/golang"))
|
||||
}
|
||||
all {
|
||||
fileName.set("libclash.so")
|
||||
packageName.set("cfa/native")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +33,7 @@ android {
|
||||
all {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DGO_SOURCE:STRING=${golang.sourceSets.getByName(name).srcDir.asFile.get()}")
|
||||
arguments("-DGO_SOURCE:STRING=${golangSource}")
|
||||
arguments("-DGO_OUTPUT:STRING=${GolangPlugin.outputDirOf(project, null, null)}")
|
||||
arguments("-DFLAVOR_NAME:STRING=$name")
|
||||
}
|
||||
@@ -52,60 +51,28 @@ android {
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
|
||||
implementation(kotlin("stdlib-jdk7"))
|
||||
implementation(deps.androidx.core)
|
||||
implementation(deps.kotlin.coroutine)
|
||||
implementation(deps.kotlin.serialization.json)
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
task("downloadGeoipDatabase") {
|
||||
val databaseFile = geoipOutput.resolve("Country.mmdb")
|
||||
val moduleFile = geoipOutput.resolve("go.mod")
|
||||
val sourceFile = geoipOutput.resolve("blob.go")
|
||||
|
||||
val moduleContent = """
|
||||
module "cfa/blob"
|
||||
""".trimIndent()
|
||||
|
||||
val sourceContent = """
|
||||
package blob
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed Country.mmdb
|
||||
var GeoipDatabase []byte
|
||||
""".trimIndent()
|
||||
|
||||
outputs.dir(geoipOutput)
|
||||
|
||||
onlyIf {
|
||||
System.currentTimeMillis() - databaseFile.lastModified() > geoipInvalidate.toMillis()
|
||||
}
|
||||
|
||||
doLast {
|
||||
geoipOutput.mkdirs()
|
||||
|
||||
moduleFile.writeText(moduleContent)
|
||||
sourceFile.writeText(sourceContent)
|
||||
|
||||
URL(geoipDatabaseUrl).openConnection().getInputStream().use { input ->
|
||||
FileOutputStream(databaseFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.kotlin.coroutine)
|
||||
implementation(libs.kotlin.serialization.json)
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val downloadTask = tasks["downloadGeoipDatabase"]
|
||||
tasks.withType(GolangBuildTask::class.java).forEach {
|
||||
it.inputs.dir(golangSource)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.forEach {
|
||||
if (it.name.startsWith("externalGolangBuild")) {
|
||||
it.dependsOn(downloadTask)
|
||||
val abis = listOf("arm64-v8a" to "Arm64V8a", "armeabi-v7a" to "ArmeabiV7a", "x86" to "X86", "x86_64" to "X8664")
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
val cmakeName = if (variant.buildType == "debug") "Debug" else "RelWithDebInfo"
|
||||
|
||||
abis.forEach { (abi, goAbi) ->
|
||||
tasks.configureEach {
|
||||
if (name.startsWith("buildCMake$cmakeName[$abi]")) {
|
||||
dependsOn("externalGolangBuild${variant.name.capitalizeUS()}$goAbi")
|
||||
println("Set up dependency: $name -> externalGolangBuild${variant.name.capitalizeUS()}$goAbi")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package app
|
||||
|
||||
import "strings"
|
||||
|
||||
var systemDns []string
|
||||
|
||||
func NotifyDnsChanged(dnsList string) {
|
||||
dns := strings.Split(dnsList, ",")
|
||||
|
||||
systemDns = dns
|
||||
}
|
||||
|
||||
func SystemDns() []string {
|
||||
return systemDns
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package config
|
||||
|
||||
var (
|
||||
defaultNameServers = []string{
|
||||
"223.5.5.5",
|
||||
"119.29.29.29",
|
||||
"8.8.8.8",
|
||||
"1.1.1.1",
|
||||
}
|
||||
defaultFallback = []string{
|
||||
"https://1.1.1.1/dns-query",
|
||||
"https://doh.pub/dns-query",
|
||||
}
|
||||
defaultFakeIPFilter = []string{
|
||||
// stun services
|
||||
"+.stun.*.*",
|
||||
"+.stun.*.*.*",
|
||||
"+.stun.*.*.*.*",
|
||||
|
||||
// Google Voices
|
||||
"lens.l.google.com",
|
||||
"stun.l.google.com",
|
||||
|
||||
// Nintendo Switch
|
||||
"*.n.n.srv.nintendo.net",
|
||||
}
|
||||
localNetwork = []string{
|
||||
"0.0.0.0/8",
|
||||
"127.0.0.0/8",
|
||||
}
|
||||
)
|
||||
@@ -1,129 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/dlclark/regexp2"
|
||||
|
||||
"cfa/app"
|
||||
"cfa/common"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHealthCheckUrl = "https://www.gstatic.com/generate_204"
|
||||
defaultHealthCheckInterval = time.Hour
|
||||
)
|
||||
|
||||
var processors = []processor{
|
||||
patchOverride,
|
||||
patchGeneral,
|
||||
patchProfile,
|
||||
patchDns,
|
||||
patchProviders,
|
||||
patchProxyGroup,
|
||||
validConfig,
|
||||
}
|
||||
|
||||
type processor func(cfg *config.RawConfig, profileDir string) error
|
||||
|
||||
func patchOverride(cfg *config.RawConfig, _ string) error {
|
||||
if err := json.NewDecoder(strings.NewReader(ReadOverride(OverrideSlotPersist))).Decode(cfg); err != nil {
|
||||
log.Warnln("Apply persist override: %s", err.Error())
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(ReadOverride(OverrideSlotSession))).Decode(cfg); err != nil {
|
||||
log.Warnln("Apply session override: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patchGeneral(cfg *config.RawConfig, _ string) error {
|
||||
cfg.Interface = ""
|
||||
cfg.ExternalUI = ""
|
||||
cfg.ExternalController = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patchProfile(cfg *config.RawConfig, _ string) error {
|
||||
cfg.Profile.StoreSelected = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patchDns(cfg *config.RawConfig, _ string) error {
|
||||
if !cfg.DNS.Enable {
|
||||
cfg.DNS.Enable = true
|
||||
cfg.DNS.IPv6 = false
|
||||
cfg.DNS.NameServer = defaultNameServers
|
||||
cfg.DNS.Fallback = defaultFallback
|
||||
cfg.DNS.FallbackFilter.GeoIP = false
|
||||
cfg.DNS.FallbackFilter.IPCIDR = localNetwork
|
||||
cfg.DNS.EnhancedMode = dns.MAPPING
|
||||
cfg.DNS.FakeIPRange = "198.18.0.0/16"
|
||||
cfg.DNS.DefaultNameserver = defaultNameServers
|
||||
cfg.DNS.FakeIPFilter = defaultFakeIPFilter
|
||||
|
||||
cfg.ClashForAndroid.AppendSystemDNS = true
|
||||
}
|
||||
|
||||
if cfg.ClashForAndroid.AppendSystemDNS {
|
||||
cfg.DNS.NameServer = append(cfg.DNS.NameServer, app.SystemDns()...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patchProviders(cfg *config.RawConfig, profileDir string) error {
|
||||
forEachProviders(cfg, func(index int, total int, key string, provider map[string]interface{}) {
|
||||
if path, ok := provider["path"].(string); ok {
|
||||
provider["path"] = profileDir + "/providers/" + common.ResolveAsRoot(path)
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patchProxyGroup(cfg *config.RawConfig, _ string) error {
|
||||
for _, g := range cfg.ProxyGroup {
|
||||
if _, exist := g["url"]; !exist {
|
||||
g["url"] = defaultHealthCheckUrl
|
||||
}
|
||||
|
||||
if _, exist := g["interval"]; !exist {
|
||||
g["interval"] = int(defaultHealthCheckInterval.Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validConfig(cfg *config.RawConfig, _ string) error {
|
||||
if len(cfg.Proxy) == 0 && len(cfg.ProxyProvider) == 0 {
|
||||
return errors.New("profile does not contain `proxies` or `proxy-providers`")
|
||||
}
|
||||
|
||||
if _, err := regexp2.Compile(cfg.ClashForAndroid.UiSubtitlePattern, 0); err != nil {
|
||||
return fmt.Errorf("compile ui-subtitle-pattern: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func process(cfg *config.RawConfig, profileDir string) error {
|
||||
for _, p := range processors {
|
||||
if err := p(cfg, profileDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// +build !premium
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
)
|
||||
|
||||
func forEachProviders(rawCfg *config.RawConfig, fun func(index int, total int, key string, provider map[string]interface{})) {
|
||||
total := len(rawCfg.ProxyProvider)
|
||||
index := 0
|
||||
|
||||
for k, v := range rawCfg.ProxyProvider {
|
||||
fun(index, total, k, v)
|
||||
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
func destroyProviders(cfg *config.Config) {
|
||||
for _, p := range cfg.Providers {
|
||||
_ = p.(io.Closer).Close()
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// +build premium
|
||||
|
||||
package config
|
||||
|
||||
import "github.com/Dreamacro/clash/config"
|
||||
|
||||
func forEachProviders(rawCfg *config.RawConfig, fun func(index int, total int, key string, provider map[string]interface{})) {
|
||||
total := len(rawCfg.ProxyProvider) + len(rawCfg.RuleProvider)
|
||||
index := 0
|
||||
|
||||
for k, v := range rawCfg.ProxyProvider {
|
||||
fun(index, total, k, v)
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
for k, v := range rawCfg.RuleProvider {
|
||||
fun(index, total, k, v)
|
||||
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
func destroyProviders(cfg *config.Config) {
|
||||
for _, p := range cfg.ProxyProviders {
|
||||
_ = p.Destroy()
|
||||
}
|
||||
|
||||
for _, p := range cfg.RuleProviders {
|
||||
_ = p.Destroy()
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
module cfa
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
cfa/blob v0.0.0 // local generated
|
||||
github.com/Dreamacro/clash v0.0.0 // local
|
||||
github.com/dlclark/regexp2 v1.4.0
|
||||
github.com/kr328/tun2socket-lwip v0.0.0-20210911023118-0b4947e2a9c1
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/oschwald/geoip2-golang v1.5.0
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
replace github.com/Dreamacro/clash => ./clash
|
||||
|
||||
replace cfa/blob => ../../../build/intermediates/golang_blob
|
||||
@@ -1,114 +0,0 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac h1:IO6EfdRnPhxgKOsk9DbewdtQZHKZKnGlW7QCUttvNys=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr328/tun2socket-lwip v0.0.0-20210911023118-0b4947e2a9c1 h1:NJ5NyPHIok6hqdELvrI4bps5lLyfJtFmm0K4c9CB/WM=
|
||||
github.com/kr328/tun2socket-lwip v0.0.0-20210911023118-0b4947e2a9c1/go.mod h1:JFkXLCpLkNVvLMRkq3gexTlRHfzbqcP5HfFOoq5Mj+g=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
|
||||
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg=
|
||||
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34 h1:GkvMjFtXUmahfDtashnc1mnrCtuBVcwse5QV2lUk/tI=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -1,32 +0,0 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func shouldHijackDns(dns net.IP, target net.IP, targetPort int) bool {
|
||||
if targetPort != 53 {
|
||||
return false
|
||||
}
|
||||
|
||||
return net.IPv4zero.Equal(dns) || target.Equal(dns)
|
||||
}
|
||||
|
||||
func relayDns(payload []byte) ([]byte, error) {
|
||||
msg := &D.Msg{}
|
||||
if err := msg.Unpack(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := resolver.ServeMsg(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.SetRcode(msg, r.Rcode)
|
||||
|
||||
return r.Pack()
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package tun
|
||||
|
||||
import "github.com/Dreamacro/clash/log"
|
||||
|
||||
func (a *adapter) rx() {
|
||||
log.Infoln("[ATUN] Device rx started")
|
||||
defer log.Infoln("[ATUN] Device rx exited")
|
||||
defer a.once.Do(a.stop)
|
||||
defer a.close()
|
||||
|
||||
buf := make([]byte, a.mtu)
|
||||
|
||||
for {
|
||||
n, err := a.device.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = a.stack.Link().Write(buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func (a *adapter) tx() {
|
||||
log.Infoln("[ATUN] Device tx started")
|
||||
defer log.Infoln("[ATUN] Device tx exited")
|
||||
defer a.once.Do(a.stop)
|
||||
defer a.close()
|
||||
|
||||
buf := make([]byte, a.mtu)
|
||||
|
||||
for {
|
||||
n, err := a.stack.Link().Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = a.device.Write(buf[:n])
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
const defaultDnsReadTimeout = time.Second * 30
|
||||
|
||||
func (a *adapter) tcp() {
|
||||
log.Infoln("[ATUN] TCP listener started")
|
||||
defer log.Infoln("[ATUN] TCP listener exited")
|
||||
defer a.stack.Close()
|
||||
|
||||
accept:
|
||||
for {
|
||||
conn, err := a.stack.TCP().Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sAddr := conn.LocalAddr().(*net.TCPAddr)
|
||||
tAddr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
|
||||
// handle dns messages
|
||||
if a.hijackTCPDNS(conn, tAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// drop all connections connect to blocking list
|
||||
for _, b := range a.blocking {
|
||||
if b.Contains(tAddr.IP) {
|
||||
_ = conn.Close()
|
||||
|
||||
continue accept
|
||||
}
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Type: C.SOCKS5,
|
||||
SrcIP: sAddr.IP,
|
||||
DstIP: tAddr.IP,
|
||||
SrcPort: strconv.Itoa(sAddr.Port),
|
||||
DstPort: strconv.Itoa(tAddr.Port),
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
RawSrcAddr: sAddr,
|
||||
RawDstAddr: tAddr,
|
||||
}
|
||||
|
||||
tunnel.TCPIn() <- context.NewConnContext(conn, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *adapter) hijackTCPDNS(conn net.Conn, tAddr *net.TCPAddr) bool {
|
||||
if !shouldHijackDns(a.dns, tAddr.IP, tAddr.Port) {
|
||||
return false
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
if err := conn.SetReadDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var length uint16
|
||||
if binary.Read(conn, binary.BigEndian, &length) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]byte, length)
|
||||
|
||||
_, err := io.ReadFull(conn, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rb, err := relayDns(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if binary.Write(conn, binary.BigEndian, uint16(len(rb))) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := conn.Write(rb); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/kr328/tun2socket-lwip"
|
||||
)
|
||||
|
||||
type adapter struct {
|
||||
device *os.File
|
||||
stack tun2socket.Stack
|
||||
blocking []*net.IPNet
|
||||
dns net.IP
|
||||
mtu int
|
||||
once sync.Once
|
||||
stop func()
|
||||
}
|
||||
|
||||
var lock sync.Mutex
|
||||
var instance *adapter
|
||||
|
||||
func (a *adapter) close() {
|
||||
_ = a.stack.Close()
|
||||
_ = a.device.Close()
|
||||
}
|
||||
|
||||
func Start(fd, mtu int, dns string, blocking string, stop func()) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if instance != nil {
|
||||
instance.close()
|
||||
}
|
||||
|
||||
_ = syscall.SetNonblock(fd, true)
|
||||
|
||||
device := os.NewFile(uintptr(fd), "/dev/tun")
|
||||
stack, err := tun2socket.NewStack(mtu)
|
||||
if err != nil {
|
||||
_ = device.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
dn := net.ParseIP(dns)
|
||||
|
||||
var blk []*net.IPNet
|
||||
|
||||
for _, b := range strings.Split(blocking, ";") {
|
||||
_, n, err := net.ParseCIDR(b)
|
||||
if err != nil {
|
||||
device.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
blk = append(blk, n)
|
||||
}
|
||||
|
||||
instance = &adapter{
|
||||
device: device,
|
||||
stack: stack,
|
||||
blocking: blk,
|
||||
dns: dn,
|
||||
mtu: mtu,
|
||||
once: sync.Once{},
|
||||
stop: stop,
|
||||
}
|
||||
|
||||
go instance.rx()
|
||||
go instance.tx()
|
||||
go instance.tcp()
|
||||
go instance.udp()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if instance != nil {
|
||||
instance.close()
|
||||
}
|
||||
|
||||
instance = nil
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/kr328/tun2socket-lwip"
|
||||
)
|
||||
|
||||
type packet struct {
|
||||
stack tun2socket.Stack
|
||||
local *net.UDPAddr
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (pkt *packet) Data() []byte {
|
||||
return pkt.data
|
||||
}
|
||||
|
||||
func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return pkt.stack.UDP().WriteTo(b, pkt.local, addr)
|
||||
}
|
||||
|
||||
func (pkt *packet) Drop() {
|
||||
pool.Put(pkt.data)
|
||||
}
|
||||
|
||||
func (pkt *packet) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
IP: pkt.local.IP,
|
||||
Port: pkt.local.Port,
|
||||
Zone: "",
|
||||
}
|
||||
}
|
||||
|
||||
func (a *adapter) udp() {
|
||||
log.Infoln("[ATUN] UDP receiver started")
|
||||
defer log.Infoln("[ATUN] UDP receiver exited")
|
||||
defer a.stack.Close()
|
||||
|
||||
read:
|
||||
for {
|
||||
buf := pool.Get(a.mtu)
|
||||
|
||||
n, lAddr, rAddr, err := a.stack.UDP().ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sAddr := lAddr.(*net.UDPAddr)
|
||||
tAddr := rAddr.(*net.UDPAddr)
|
||||
|
||||
// handle dns messages
|
||||
if a.hijackUDPDNS(buf[:n], sAddr, tAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// drop all packet send to blocking list
|
||||
for _, b := range a.blocking {
|
||||
if b.Contains(tAddr.IP) {
|
||||
continue read
|
||||
}
|
||||
}
|
||||
|
||||
pkt := &packet{
|
||||
stack: a.stack,
|
||||
local: sAddr,
|
||||
data: buf[:n],
|
||||
}
|
||||
|
||||
tunnel.UDPIn() <- inbound.NewPacket(socks5.ParseAddrToSocksAddr(tAddr), pkt, C.SOCKS5)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *adapter) hijackUDPDNS(pkt []byte, sAddr, tAddr *net.UDPAddr) bool {
|
||||
if !shouldHijackDns(a.dns, tAddr.IP, tAddr.Port) {
|
||||
return false
|
||||
}
|
||||
|
||||
go func() {
|
||||
answer, err := relayDns(pkt)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = a.stack.UDP().WriteTo(answer, sAddr, tAddr)
|
||||
|
||||
pool.Put(pkt)
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||
)
|
||||
|
||||
func closeMatch(filter func(conn C.Conn) bool) {
|
||||
for _, c := range statistic.DefaultManager.Snapshot().Connections {
|
||||
if cc, ok := c.(C.Conn); ok {
|
||||
if filter(cc) {
|
||||
_ = cc.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func closeConnByGroup(name string) {
|
||||
closeMatch(func(conn C.Conn) bool {
|
||||
for _, c := range conn.Chains() {
|
||||
if c == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
)
|
||||
|
||||
func InstallSideloadGeoip(block []byte) error {
|
||||
if block == nil {
|
||||
mmdb.InstallOverride(nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
db, err := geoip2.FromBytes(block)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load sideload geoip mmdb: %s", err.Error())
|
||||
}
|
||||
|
||||
mmdb.InstallOverride(db)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// +build !premium
|
||||
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
P "github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
var ErrInvalidType = errors.New("invalid type")
|
||||
|
||||
type Provider struct {
|
||||
Name string `json:"name"`
|
||||
VehicleType string `json:"vehicleType"`
|
||||
Type string `json:"type"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func QueryProviders() []*Provider {
|
||||
p := tunnel.Providers()
|
||||
|
||||
providers := make([]provider.Provider, 0, len(p))
|
||||
|
||||
for _, proxy := range p {
|
||||
if proxy.VehicleType() == provider.Compatible {
|
||||
continue
|
||||
}
|
||||
|
||||
providers = append(providers, proxy)
|
||||
}
|
||||
|
||||
result := make([]*Provider, 0, len(providers))
|
||||
|
||||
for _, p := range providers {
|
||||
updatedAt := time.Time{}
|
||||
|
||||
if s, ok := p.(P.UpdatableProvider); ok {
|
||||
updatedAt = s.UpdatedAt()
|
||||
}
|
||||
|
||||
result = append(result, &Provider{
|
||||
Name: p.Name(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Type: p.Type().String(),
|
||||
UpdatedAt: updatedAt.UnixNano() / 1000 / 1000,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func UpdateProvider(_ string, name string) error {
|
||||
p, ok := tunnel.Providers()[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s not found", name)
|
||||
}
|
||||
|
||||
return p.Update()
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package tunnel
|
||||
|
||||
import "github.com/Dreamacro/clash/adapter/provider"
|
||||
|
||||
func Suspend(s bool) {
|
||||
provider.Suspend(s)
|
||||
}
|
||||