mirror of
https://github.com/MetaCubeX/ClashMetaForAndroid.git
synced 2026-05-09 18:11:26 +08:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
389ecb3e8f | ||
|
|
160b96d829 | ||
|
|
5b80873810 | ||
|
|
9cae7f9ad7 | ||
|
|
1333c554a9 | ||
|
|
4d6000af22 | ||
|
|
3601d1166b | ||
|
|
7691a96996 | ||
|
|
db215a5510 | ||
|
|
17c17452b3 | ||
|
|
d7b0fa5e29 | ||
|
|
a0b5e595c8 | ||
|
|
728cc0d10b | ||
|
|
514c84ec7e | ||
|
|
f4af98f54a | ||
|
|
e1f4fef157 | ||
|
|
2004f2392a | ||
|
|
d7f0cf91c7 | ||
|
|
f34d71eb88 | ||
|
|
b0ed302d5e | ||
|
|
a4b74a7523 | ||
|
|
8581f53b20 | ||
|
|
432fa115e2 | ||
|
|
2759f59029 | ||
|
|
d24496a01e | ||
|
|
123bf65c55 | ||
|
|
f6263940f1 | ||
|
|
cba3d2d848 | ||
|
|
cecb1522fe | ||
|
|
fd6ecda520 | ||
|
|
147df6c180 | ||
|
|
8d6310d0ea | ||
|
|
c455dfc7df | ||
|
|
e381319ae8 | ||
|
|
297b39625f | ||
|
|
b42d762fa0 | ||
|
|
f23e6e82b0 | ||
|
|
2af11c29a5 | ||
|
|
b0bf57e715 | ||
|
|
cdd0e3dd30 | ||
|
|
5823955c71 | ||
|
|
714c9a554f | ||
|
|
e621324b48 | ||
|
|
dfe1e7ecd9 | ||
|
|
4c3380d822 | ||
|
|
1d68516cdb | ||
|
|
821bb49914 | ||
|
|
c95993a629 | ||
|
|
e6495f5e1f | ||
|
|
c104942b5f | ||
|
|
59835667aa | ||
|
|
8a98eea8fa | ||
|
|
3dc20b9e70 | ||
|
|
d8ab6fd755 | ||
|
|
cc499a7897 | ||
|
|
76f5261b20 | ||
|
|
6a506b761f | ||
|
|
fe966f9c5d | ||
|
|
bcf7f793ec | ||
|
|
75de6ce041 | ||
|
|
4417a85ef7 | ||
|
|
9e0b67c04a | ||
|
|
8cd203e7b0 | ||
|
|
211160f7f8 | ||
|
|
2afc7262eb | ||
|
|
94a0282c70 | ||
|
|
96b78829ab | ||
|
|
35d5a90b2a | ||
|
|
50d73ad498 | ||
|
|
d1f8bc3062 | ||
|
|
d3353b1aa5 | ||
|
|
cd48fb4e08 | ||
|
|
f70738ecf5 | ||
|
|
2d63466597 | ||
|
|
ecf67443fb | ||
|
|
43987ad111 | ||
|
|
5fbd6c6196 | ||
|
|
1ec7f1f7e9 | ||
|
|
c5104c241a | ||
|
|
021ba3519d | ||
|
|
119f74cd05 | ||
|
|
f0b61c9e1f | ||
|
|
a573631068 | ||
|
|
a12444b258 | ||
|
|
17257228a9 | ||
|
|
53b6a9dfc1 | ||
|
|
c45f8ddfc3 | ||
|
|
e04c9c15ff | ||
|
|
d5cb363579 | ||
|
|
0748021ffa | ||
|
|
e958e7f675 | ||
|
|
68113e694e |
24
.github/ISSUE_TEMPLATE/01-bug-report-en.md
vendored
24
.github/ISSUE_TEMPLATE/01-bug-report-en.md
vendored
@@ -7,6 +7,10 @@ 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.
|
||||
|
||||
@@ -23,15 +27,12 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Logs**
|
||||
if applicable, add logs to help detect problem
|
||||
|
||||
**Device Info (please complete the following information):**
|
||||
|
||||
- Device: [e.g. Pixel 4]
|
||||
- ROM: [e.g: AOSP]
|
||||
- ROM Version:
|
||||
- Android Version [e.g. Oreo]
|
||||
- Android Version [e.g. 10]
|
||||
|
||||
**Application Info (please complete the following information):**
|
||||
|
||||
@@ -40,4 +41,17 @@ if applicable, add logs to help detect problem
|
||||
- Distribution Channel: [e.g. Google Play]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
Add any other context about the problem here.
|
||||
|
||||
**Configure**
|
||||
If applicable, paste **removed server info** configure
|
||||
```yaml
|
||||
# paste here
|
||||
```
|
||||
|
||||
**Logs**
|
||||
If applicable, paste logs to help detect problem
|
||||
|
||||
```
|
||||
<paste here>
|
||||
```
|
||||
@@ -7,6 +7,10 @@ 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 [...]
|
||||
|
||||
|
||||
25
.github/ISSUE_TEMPLATE/03-bug-report-zh-cn.md
vendored
25
.github/ISSUE_TEMPLATE/03-bug-report-zh-cn.md
vendored
@@ -7,6 +7,10 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- 请务必在上方文本框处 [BUG] 后填入清晰明了的标题 -->
|
||||
<!-- 请务必在上方文本框处 [BUG] 后填入清晰明了的标题 -->
|
||||
<!-- 请务必在上方文本框处 [BUG] 后填入清晰明了的标题 -->
|
||||
|
||||
**描述出现的错误**
|
||||
请简洁的描述你遇到的错误
|
||||
|
||||
@@ -23,14 +27,10 @@ assignees: ''
|
||||
**屏幕截图**
|
||||
如果适用, 上传屏幕截图以帮助描述错误
|
||||
|
||||
**日志**
|
||||
|
||||
如果适用, 上传日志以帮助侦测错误
|
||||
|
||||
**设备信息 (请完成一下信息):**
|
||||
**设备信息 (请完成以下信息):**
|
||||
- 机型: [例如: Pixel 4]
|
||||
- 系统/ROM: [例如: MIUI 11]
|
||||
- Android 版本 [例如: Oreo]
|
||||
- Android 版本 [例如: 10]
|
||||
- ROM版本 [例如: 20.3.19]
|
||||
|
||||
**应用信息**
|
||||
@@ -40,3 +40,16 @@ assignees: ''
|
||||
|
||||
**附加信息**
|
||||
其他的可能与改错误相关的信息
|
||||
|
||||
**配置文件**
|
||||
如果适用, 在此粘贴 **去除服务器信息的** 的 **配置文件**
|
||||
```yaml
|
||||
# 在此粘贴
|
||||
```
|
||||
|
||||
**日志**
|
||||
如果适用, 粘贴日志以帮助侦测错误
|
||||
```
|
||||
<在此粘贴>
|
||||
```
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- 请务必在上方文本框处 [Feature Request] 后填入清晰明了的标题 -->
|
||||
<!-- 请务必在上方文本框处 [Feature Request] 后填入清晰明了的标题 -->
|
||||
<!-- 请务必在上方文本框处 [Feature Request] 后填入清晰明了的标题 -->
|
||||
|
||||
**功能描述**
|
||||
请清晰的描述你想要的功能
|
||||
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -6,26 +6,24 @@ plugins {
|
||||
id("kotlin-android-extensions")
|
||||
}
|
||||
|
||||
val rootExtra = rootProject.extra
|
||||
val gCompileSdkVersion: String by project
|
||||
val gBuildToolsVersion: String by project
|
||||
|
||||
val gCompileSdkVersion: Int by rootExtra
|
||||
val gBuildToolsVersion: String by rootExtra
|
||||
val gMinSdkVersion: String by project
|
||||
val gTargetSdkVersion: String by project
|
||||
|
||||
val gMinSdkVersion: Int by rootExtra
|
||||
val gTargetSdkVersion: Int by rootExtra
|
||||
val gVersionCode: String by project
|
||||
val gVersionName: String by project
|
||||
|
||||
val gVersionCode: Int by rootExtra
|
||||
val gVersionName: String by rootExtra
|
||||
|
||||
val gKotlinVersion: String by rootExtra
|
||||
val gKotlinCoroutineVersion: String by rootExtra
|
||||
val gAppCenterVersion: String by rootExtra
|
||||
val gAndroidKtxVersion: String by rootExtra
|
||||
val gRecyclerviewVersion: String by rootExtra
|
||||
val gAppCompatVersion: String by rootExtra
|
||||
val gMaterialDesignVersion: String by rootExtra
|
||||
val gShizukuPreferenceVersion: String by rootExtra
|
||||
val gMultiprocessPreferenceVersion: String by rootExtra
|
||||
val gKotlinVersion: String by project
|
||||
val gKotlinCoroutineVersion: String by project
|
||||
val gAppCenterVersion: String by project
|
||||
val gAndroidKtxVersion: String by project
|
||||
val gRecyclerviewVersion: String by project
|
||||
val gAppCompatVersion: String by project
|
||||
val gMaterialDesignVersion: String by project
|
||||
val gShizukuPreferenceVersion: String by project
|
||||
val gMultiprocessPreferenceVersion: String by project
|
||||
|
||||
android {
|
||||
compileSdkVersion(gCompileSdkVersion)
|
||||
@@ -37,12 +35,12 @@ android {
|
||||
minSdkVersion(gMinSdkVersion)
|
||||
targetSdkVersion(gTargetSdkVersion)
|
||||
|
||||
versionCode = gVersionCode
|
||||
versionCode = gVersionCode.toInt()
|
||||
versionName = gVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
maybeCreate("release").apply {
|
||||
named("release") {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
@@ -73,7 +71,7 @@ android {
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
maybeCreate("release").apply {
|
||||
named("release") {
|
||||
storeFile = rootProject.file(Objects.requireNonNull(properties.getProperty("storeFile")))
|
||||
storePassword = Objects.requireNonNull(properties.getProperty("storePassword"))
|
||||
keyAlias = Objects.requireNonNull(properties.getProperty("keyAlias"))
|
||||
@@ -81,7 +79,7 @@ android {
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
maybeCreate("release").apply {
|
||||
named("release") {
|
||||
this.signingConfig = signingConfigs.findByName("release")
|
||||
}
|
||||
}
|
||||
@@ -121,6 +119,18 @@ task("injectAppCenterKey") {
|
||||
}
|
||||
}
|
||||
|
||||
task("injectPackageNameBase64") {
|
||||
doFirst {
|
||||
val packageName = android.defaultConfig.applicationId ?: return@doFirst
|
||||
|
||||
val base64 = Base64.getEncoder().encodeToString(packageName.toByteArray(Charsets.UTF_8))
|
||||
|
||||
android.buildTypes.forEach {
|
||||
it.buildConfigField("String", "PACKAGE_NAME_BASE64", "\"$base64\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks["preBuild"].dependsOn(tasks["injectAppCenterKey"])
|
||||
tasks["preBuild"].dependsOn(tasks["injectAppCenterKey"], tasks["injectPackageNameBase64"])
|
||||
}
|
||||
@@ -16,11 +16,9 @@ import kotlinx.android.synthetic.main.activity_log_viewer.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.io.File
|
||||
import kotlin.streams.toList
|
||||
|
||||
class LogViewerActivity : BaseActivity() {
|
||||
private val pauseMutex = Mutex()
|
||||
private var pollingThread: Thread? = null
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
finish()
|
||||
@@ -49,12 +47,6 @@ class LogViewerActivity : BaseActivity() {
|
||||
startFileMode(file.toFile())
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
pollingThread?.interrupt()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
@@ -79,6 +71,7 @@ class LogViewerActivity : BaseActivity() {
|
||||
mainList.itemAnimator?.removeDuration = 100
|
||||
|
||||
stop.setOnClickListener {
|
||||
unbindService(connection)
|
||||
stopService(LogcatService::class.intent)
|
||||
finish()
|
||||
}
|
||||
@@ -92,15 +85,15 @@ class LogViewerActivity : BaseActivity() {
|
||||
launch {
|
||||
val items = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
file.readText()
|
||||
.split("\n")
|
||||
.parallelStream()
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||
.map { it.split(" ", limit = 3) }
|
||||
.filter { it.size == 3 }
|
||||
.map { LogEvent(LogEvent.Level.valueOf(it[1]), it[2], it[0].toLong()) }
|
||||
.toList()
|
||||
file.bufferedReader().useLines { lines ->
|
||||
lines
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||
.map { it.split(" ", limit = 3) }
|
||||
.filter { it.size == 3 }
|
||||
.map { LogEvent(LogEvent.Level.valueOf(it[1]), it[2], it[0].toLong()) }
|
||||
.toList()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
showSnackbarException(getString(R.string.open_log_failure), e.message)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.kr328.clash.adapter.LogFileAdapter
|
||||
import com.github.kr328.clash.common.utils.intent
|
||||
import com.github.kr328.clash.common.utils.startForegroundServiceCompat
|
||||
import com.github.kr328.clash.core.event.LogEvent
|
||||
import com.github.kr328.clash.design.common.Category
|
||||
import com.github.kr328.clash.design.view.CommonUiLayout
|
||||
import com.github.kr328.clash.model.LogFile
|
||||
@@ -24,12 +25,15 @@ import kotlinx.android.synthetic.main.activity_logs.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.FileInputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class LogsActivity : BaseActivity() {
|
||||
companion object {
|
||||
const val REQUEST_CODE = 50000
|
||||
|
||||
private val LOG_EXPORT_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
|
||||
private val LOG_EXPORT_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH)
|
||||
}
|
||||
|
||||
private var lastWriteFile: LogFile? = null
|
||||
@@ -102,9 +106,31 @@ class LogsActivity : BaseActivity() {
|
||||
|
||||
launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
contentResolver.openOutputStream(url)?.use { output ->
|
||||
FileInputStream(logsDir.resolve(file.fileName)).use { input ->
|
||||
input.copyTo(output)
|
||||
contentResolver.openOutputStream(url)?.bufferedWriter()?.use { output ->
|
||||
output.write("# Logcat on " + LOG_EXPORT_DATE_FORMAT.format(Date(file.date)) + "\n")
|
||||
|
||||
logsDir.resolve(file.fileName).bufferedReader().useLines { lines ->
|
||||
lines.map { it.trim() }
|
||||
.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||
.map { it.split(" ", limit = 3) }
|
||||
.filter { it.size == 3 }
|
||||
.map {
|
||||
LogEvent(
|
||||
LogEvent.Level.valueOf(it[1]),
|
||||
it[2],
|
||||
it[0].toLong()
|
||||
)
|
||||
}
|
||||
.forEach {
|
||||
output.write(
|
||||
String.format(
|
||||
"%s |%s| %s\n",
|
||||
LOG_EXPORT_TIME_FORMAT.format(Date(it.time)),
|
||||
it.level.toString(),
|
||||
it.message
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class MainApplication : Application() {
|
||||
if (!report.stackTrace.contains("DeadObjectException"))
|
||||
return mutableListOf()
|
||||
|
||||
val logcat = LogcatDumper.dump().joinToString(separator = "\n")
|
||||
val logcat = LogcatDumper.dumpCrash()
|
||||
|
||||
return mutableListOf(
|
||||
ErrorAttachmentLog.attachmentWithText(logcat, "logcat.txt")
|
||||
|
||||
@@ -119,11 +119,6 @@ class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
put(UiSettings.PROXY_GROUP_SORT, UiSettings.PROXY_SORT_NAME)
|
||||
}
|
||||
}
|
||||
R.id.groupDelay -> {
|
||||
uiSettings.commit {
|
||||
put(UiSettings.PROXY_GROUP_SORT, UiSettings.PROXY_SORT_DELAY)
|
||||
}
|
||||
}
|
||||
R.id.proxyDefault -> {
|
||||
uiSettings.commit {
|
||||
put(UiSettings.PROXY_PROXY_SORT, UiSettings.PROXY_SORT_DEFAULT)
|
||||
@@ -186,7 +181,7 @@ class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
UiSettings.PROXY_SORT_NAME ->
|
||||
findItem(R.id.groupName).isChecked = true
|
||||
UiSettings.PROXY_SORT_DELAY ->
|
||||
findItem(R.id.proxyDelay).isChecked = true
|
||||
findItem(R.id.proxyDefault).isChecked = true
|
||||
}
|
||||
when (uiSettings.get(UiSettings.PROXY_PROXY_SORT)) {
|
||||
UiSettings.PROXY_SORT_DEFAULT ->
|
||||
@@ -206,7 +201,7 @@ class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
private fun setGroupSelected(group: String, select: String) {
|
||||
launch {
|
||||
withClash {
|
||||
setSelectProxy(group, select)
|
||||
setSelector(group, select)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,7 +235,7 @@ class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
queryGeneral()
|
||||
}
|
||||
val proxies = withClash {
|
||||
queryAllProxyGroups()
|
||||
queryProxyGroups()
|
||||
}
|
||||
|
||||
val merged = Pipeline(proxies, uiSettings).mergePrefix()
|
||||
|
||||
@@ -4,9 +4,19 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.github.kr328.clash.dump.LogcatDumper
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.microsoft.appcenter.crashes.Crashes
|
||||
import com.microsoft.appcenter.crashes.ingestion.models.ErrorAttachmentLog
|
||||
import kotlinx.android.synthetic.main.activity_support.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class SupportActivity : BaseActivity() {
|
||||
class UserRequestTrackException: Exception()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -46,6 +56,20 @@ class SupportActivity : BaseActivity() {
|
||||
|
||||
category(text = getString(R.string.feedback))
|
||||
|
||||
option(
|
||||
title = getString(R.string.upload_logcat),
|
||||
summary = getString(R.string.upload_logcat_summary)
|
||||
) {
|
||||
onClick {
|
||||
AlertDialog.Builder(this@SupportActivity)
|
||||
.setTitle(R.string.upload_logcat)
|
||||
.setMessage(R.string.upload_logcat_warn)
|
||||
.setNegativeButton(R.string.cancel) {_, _ -> }
|
||||
.setPositiveButton(R.string.ok) {_, _ -> upload() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
option(
|
||||
title = getString(R.string.github_issues),
|
||||
summary = getString(R.string.github_issues_url)
|
||||
@@ -77,4 +101,19 @@ class SupportActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun upload() {
|
||||
launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val attachment = ErrorAttachmentLog
|
||||
.attachmentWithText(LogcatDumper.dumpAll(), "logcat.txt")
|
||||
|
||||
Crashes.trackError(UserRequestTrackException(), null, listOf(attachment))
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Snackbar.make(rootView, R.string.uploaded, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,37 @@
|
||||
package com.github.kr328.clash.dump
|
||||
|
||||
object LogcatDumper {
|
||||
fun dump(): List<String> {
|
||||
fun dumpCrash(): String {
|
||||
return try {
|
||||
val process =
|
||||
Runtime.getRuntime().exec(arrayOf("logcat", "-d", "-s", "-v", "raw", "Go"))
|
||||
Runtime.getRuntime().exec(arrayOf("logcat", "-d", "-s", "Go", "AndroidRuntime", "DEBUG"))
|
||||
|
||||
val result = process.inputStream.bufferedReader().useLines {
|
||||
var list = mutableListOf<String>()
|
||||
var capture = false
|
||||
|
||||
it.forEach { line ->
|
||||
if (line.startsWith("panic")) {
|
||||
capture = true
|
||||
|
||||
list = mutableListOf()
|
||||
}
|
||||
|
||||
if (capture)
|
||||
list.add(line)
|
||||
}
|
||||
|
||||
list
|
||||
val result = process.inputStream.use {
|
||||
it.reader().readText()
|
||||
}
|
||||
|
||||
process.waitFor()
|
||||
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
fun dumpAll(): String {
|
||||
return try {
|
||||
val process =
|
||||
Runtime.getRuntime().exec(arrayOf("logcat", "-d"))
|
||||
|
||||
val result = process.inputStream.use {
|
||||
it.reader().readText()
|
||||
}
|
||||
|
||||
process.waitFor()
|
||||
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ class ProfileEditFragment(
|
||||
private val type: Type,
|
||||
private val source: String?
|
||||
) : Fragment() {
|
||||
private var root: CommonUiLayout? = null
|
||||
|
||||
var isModified = false
|
||||
|
||||
companion object {
|
||||
@@ -46,6 +48,7 @@ class ProfileEditFragment(
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return CommonUiLayout(requireContext()).apply {
|
||||
root = this
|
||||
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
|
||||
build {
|
||||
@@ -111,12 +114,14 @@ class ProfileEditFragment(
|
||||
|
||||
if (s.isBlank()) {
|
||||
content = ""
|
||||
interval = 0
|
||||
return@onTextChanged
|
||||
}
|
||||
|
||||
val value = s.toIntOrNull()
|
||||
if (value == null || value < 15) {
|
||||
content = ""
|
||||
interval = 0
|
||||
Snackbar.make(view, R.string.invalid_interval, Snackbar.LENGTH_LONG)
|
||||
.show()
|
||||
return@onTextChanged
|
||||
@@ -143,16 +148,16 @@ class ProfileEditFragment(
|
||||
if (resultCode != Activity.RESULT_OK || data == null)
|
||||
return
|
||||
|
||||
val layout = view as CommonUiLayout
|
||||
root?.apply {
|
||||
data.data?.apply {
|
||||
screen.requireElement<TextInput>(KEY_URL).content = this.toString()
|
||||
}
|
||||
|
||||
data.data?.apply {
|
||||
layout.screen.requireElement<TextInput>(KEY_URL).content = this.toString()
|
||||
}
|
||||
|
||||
data.getStringExtra(Constants.URL_PROVIDER_INTENT_EXTRA_NAME)?.also {
|
||||
layout.screen.requireElement<TextInput>(KEY_NAME).apply {
|
||||
if (content.isBlank())
|
||||
content = it
|
||||
data.getStringExtra(Constants.URL_PROVIDER_INTENT_EXTRA_NAME)?.also {
|
||||
screen.requireElement<TextInput>(KEY_NAME).apply {
|
||||
if (content.isBlank())
|
||||
content = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +168,9 @@ class ProfileEditFragment(
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
(view as CommonUiLayout?)?.screen?.saveState(outState)
|
||||
root?.apply {
|
||||
screen.saveState(outState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openUrlProvider(): Boolean {
|
||||
@@ -182,11 +189,13 @@ class ProfileEditFragment(
|
||||
else -> return false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Snackbar.make(
|
||||
view as ViewGroup,
|
||||
R.string.start_url_provider_failure,
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
root?.apply {
|
||||
Snackbar.make(
|
||||
this,
|
||||
R.string.start_url_provider_failure,
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -11,13 +11,13 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ClashClient(val service: IClashManager) {
|
||||
suspend fun setSelectProxy(name: String, proxy: String): Boolean = withContext(Dispatchers.IO) {
|
||||
service.setSelectProxy(name, proxy)
|
||||
suspend fun setSelector(group: String, selected: String) = withContext(Dispatchers.IO) {
|
||||
service.setSelector(group, selected)
|
||||
}
|
||||
|
||||
suspend fun startHealthCheck(group: String) = withContext(Dispatchers.IO) {
|
||||
CompletableDeferred<Unit>().apply {
|
||||
service.startHealthCheck(group, object : IStreamCallback.Stub() {
|
||||
service.performHealthCheck(group, object : IStreamCallback.Stub() {
|
||||
override fun complete() {
|
||||
this@apply.complete(Unit)
|
||||
}
|
||||
@@ -31,8 +31,8 @@ class ClashClient(val service: IClashManager) {
|
||||
}
|
||||
}.await()
|
||||
|
||||
suspend fun queryAllProxyGroups(): List<ProxyGroup> = withContext(Dispatchers.IO) {
|
||||
service.queryAllProxies().list
|
||||
suspend fun queryProxyGroups(): List<ProxyGroup> = withContext(Dispatchers.IO) {
|
||||
service.queryProxyGroups().list
|
||||
}
|
||||
|
||||
suspend fun queryGeneral(): General = withContext(Dispatchers.IO) {
|
||||
|
||||
@@ -8,8 +8,10 @@ import android.content.ServiceConnection
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.util.Base64
|
||||
import androidx.core.content.edit
|
||||
import com.github.kr328.clash.ApkBrokenActivity
|
||||
import com.github.kr328.clash.BuildConfig
|
||||
import com.github.kr328.clash.Constants
|
||||
import com.github.kr328.clash.common.Global
|
||||
import com.github.kr328.clash.common.utils.intent
|
||||
@@ -88,7 +90,7 @@ object Remote {
|
||||
|
||||
val application = Global.application
|
||||
|
||||
if ( it ) {
|
||||
if (it) {
|
||||
handler.removeMessages(0)
|
||||
|
||||
GlobalScope.launch {
|
||||
@@ -152,11 +154,43 @@ object Remote {
|
||||
if (sp.getLong(Constants.PREFERENCE_KEY_LAST_INSTALL, 0) == pkg.lastUpdateTime)
|
||||
return true
|
||||
|
||||
val pkgName: String = try {
|
||||
application::class.java.getMethod(
|
||||
String(
|
||||
charArrayOf(
|
||||
'g',
|
||||
'e',
|
||||
't',
|
||||
'P',
|
||||
'a',
|
||||
'c',
|
||||
'k',
|
||||
'a',
|
||||
'g',
|
||||
'e',
|
||||
'N',
|
||||
'a',
|
||||
'm',
|
||||
'e'
|
||||
)
|
||||
)
|
||||
).invoke(application)?.toString()
|
||||
} catch (e: Exception) {
|
||||
Log.w("getPackageName failure", e)
|
||||
null
|
||||
} ?: application.packageName
|
||||
|
||||
val packageNameBase64 = Base64
|
||||
.encodeToString(pkgName.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
|
||||
|
||||
if (packageNameBase64 != BuildConfig.PACKAGE_NAME_BASE64)
|
||||
return false
|
||||
|
||||
val info = application.applicationInfo
|
||||
val sources =
|
||||
info.splitSourceDirs ?: arrayOf(info.sourceDir) ?: return false
|
||||
|
||||
val regexNativeLibrary = Regex("lib/(\\S+)/libgojni.so")
|
||||
val regexNativeLibrary = Regex("lib/(\\S+)/libclash.so")
|
||||
val availableAbi = Build.SUPPORTED_ABIS.toSet()
|
||||
val apkAbi =
|
||||
sources
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.github.kr328.clash.ApkBrokenActivity
|
||||
import com.github.kr328.clash.common.utils.intent
|
||||
import com.github.kr328.clash.service.Constants
|
||||
import com.github.kr328.clash.service.ServiceStatusProvider
|
||||
import java.lang.Exception
|
||||
|
||||
object RemoteUtils {
|
||||
fun detectClashRunning(context: Context): Boolean {
|
||||
@@ -24,7 +25,7 @@ object RemoteUtils {
|
||||
)
|
||||
|
||||
return pong != null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
} catch (e: Exception) {
|
||||
context.startActivity(ApkBrokenActivity::class.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
|
||||
return false
|
||||
@@ -46,7 +47,7 @@ object RemoteUtils {
|
||||
)
|
||||
|
||||
return pong?.getString("name")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
} catch (e: Exception) {
|
||||
context.startActivity(ApkBrokenActivity::class.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
|
||||
return null
|
||||
|
||||
@@ -24,10 +24,9 @@ class ProxySorter(private val groupOrder: Order, private val proxyOrder: Order)
|
||||
|
||||
val sortedGroup = when (groupOrder) {
|
||||
Order.DEFAULT -> groupSortWithDefault(global, other)
|
||||
Order.DELAY_INCREASE -> groupSortWithDelay(true, other)
|
||||
Order.DELAY_DECREASE -> groupSortWithDelay(false, other)
|
||||
Order.NAME_INCREASE -> groupSortWithName(true, other)
|
||||
Order.NAME_DECREASE -> groupSortWithName(false, other)
|
||||
else -> groupSortWithDefault(global, other)
|
||||
}
|
||||
|
||||
val sorted = if (global == null)
|
||||
@@ -73,16 +72,6 @@ class ProxySorter(private val groupOrder: Order, private val proxyOrder: Order)
|
||||
proxyGroup.sortedByDescending { it.name }
|
||||
}
|
||||
|
||||
private fun groupSortWithDelay(
|
||||
increase: Boolean,
|
||||
proxyGroup: List<ProxyGroup>
|
||||
): List<ProxyGroup> {
|
||||
return if (increase)
|
||||
proxyGroup.sortedBy { it.delay }
|
||||
else
|
||||
proxyGroup.sortedByDescending { it.delay }
|
||||
}
|
||||
|
||||
private fun proxySortWithName(
|
||||
increase: Boolean,
|
||||
proxies: List<Proxy>
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<group android:checkableBehavior="single">
|
||||
<item android:id="@+id/groupDefault" android:title="@string/default_" />
|
||||
<item android:id="@+id/groupName" android:title="@string/name" />
|
||||
<item android:id="@+id/groupDelay" android:title="@string/delay" />
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
20
app/src/main/res/values-zh-rHK/arrays.xml
Normal file
20
app/src/main/res/values-zh-rHK/arrays.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="access_control_mode_display">
|
||||
<item>允許所有應用</item>
|
||||
<item>僅允許已選擇的應用</item>
|
||||
<item>不允許已選擇的應用</item>
|
||||
</string-array>
|
||||
<string-array name="dark_mode">
|
||||
<item>自動</item>
|
||||
<item>暗黑</item>
|
||||
<item>明亮</item>
|
||||
</string-array>
|
||||
<string-array name="language">
|
||||
<item>自動</item>
|
||||
<item>英文</item>
|
||||
<item>簡體中文</item>
|
||||
<item>繁體中文-香港</item>
|
||||
<item>繁體中文-台灣</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
138
app/src/main/res/values-zh-rHK/strings.xml
Normal file
138
app/src/main/res/values-zh-rHK/strings.xml
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="launch_name">Clash</string>
|
||||
<string name="application_name">Clash for Android</string>
|
||||
<string name="stopped">已停止</string>
|
||||
<string name="tap_to_start">點此啟動</string>
|
||||
<string name="running">運行中</string>
|
||||
<string name="format_traffic_forwarded">%s 已轉發</string>
|
||||
<string name="proxy">代理</string>
|
||||
<string name="direct_mode">直連模式</string>
|
||||
<string name="rule_mode">規則模式</string>
|
||||
<string name="global_mode">全局模式</string>
|
||||
<string name="profiles">配置</string>
|
||||
<string name="not_selected">未選擇</string>
|
||||
<string name="format_profile_activated">%s 已激活</string>
|
||||
<string name="logs">日誌</string>
|
||||
<string name="settings">設置</string>
|
||||
<string name="support">支持</string>
|
||||
<string name="about">關於</string>
|
||||
<string name="unknown">未知</string>
|
||||
<string name="recently">近期</string>
|
||||
<string name="format_minutes">%d 分鐘</string>
|
||||
<string name="format_hours">%d 小時</string>
|
||||
<string name="format_days">%d 天</string>
|
||||
<string name="format_months">%d 月</string>
|
||||
<string name="format_years">%d 年</string>
|
||||
<string name="create_profile">創建配置</string>
|
||||
<string name="file">文件</string>
|
||||
<string name="import_from_file">從文件導入</string>
|
||||
<string name="url">URL</string>
|
||||
<string name="import_from_url">從 URL 導入</string>
|
||||
<string name="external">外部</string>
|
||||
<string name="application_broken">應用損壞</string>
|
||||
<string name="profile">配置</string>
|
||||
<string name="name">名稱</string>
|
||||
<string name="profile_name">配置名稱</string>
|
||||
<string name="profile_url">配置 URL</string>
|
||||
<string name="auto_update">自動更新 (分鐘)</string>
|
||||
<string name="more_than_15_minutes">大於15分鐘</string>
|
||||
<string name="invalid_interval">無效的間隔</string>
|
||||
<string name="download_failure">下載失敗</string>
|
||||
<string name="detail">詳情</string>
|
||||
<string name="update">更新</string>
|
||||
<string name="edit">編輯</string>
|
||||
<string name="delete">刪除</string>
|
||||
<string name="export">導出</string>
|
||||
<string name="properties">參數</string>
|
||||
<string name="duplicate">複製</string>
|
||||
<string name="reset_provider">重置外部引用</string>
|
||||
<string name="exit_without_save">退出而不保存</string>
|
||||
<string name="exit_without_save_warning">所有變更將會丟失</string>
|
||||
<string name="disabled">已禁用</string>
|
||||
<string name="empty_name">空名稱</string>
|
||||
<string name="invalid_url">無效的 URL</string>
|
||||
<string name="processing">處理中</string>
|
||||
<string name="loading">載入中</string>
|
||||
<string name="new_profile">新配置</string>
|
||||
<string name="clone_profile">複製配置</string>
|
||||
<string name="edit_profile">編輯配置</string>
|
||||
<string name="clash_start_failure">Clash 啟動失敗</string>
|
||||
<string name="refresh">刷新</string>
|
||||
<string name="delay">延遲</string>
|
||||
<string name="direct">直連</string>
|
||||
<string name="global">全局</string>
|
||||
<string name="rule">規則</string>
|
||||
<string name="mode">模式</string>
|
||||
<string name="sort_group">代理組排序</string>
|
||||
<string name="sort_proxy">代理排序</string>
|
||||
<string name="default_">默認</string>
|
||||
<string name="utils">實用工具</string>
|
||||
<string name="merge_prefix">前綴合並</string>
|
||||
<string name="start_url_provider_failure">打開 URL 提供程序失敗</string>
|
||||
<string name="clash_new_profile">新配置</string>
|
||||
<string name="ok">確認</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="clash_logcat">Clash 日誌捕捉工具</string>
|
||||
<string name="history">歷史</string>
|
||||
<string name="log_viewer">日誌查看器</string>
|
||||
<string name="format_export_log_name">%s.log</string>
|
||||
<string name="delete_all_logs">刪除所有日誌</string>
|
||||
<string name="delete_all_logs_warn">所有歷史日誌將丟失</string>
|
||||
<string name="delete_log">刪除日誌</string>
|
||||
<string name="delete_log_warn">%s 將被刪除</string>
|
||||
<string name="open_log_failure">打開日誌失敗</string>
|
||||
<string name="file_exported">文件已導出</string>
|
||||
<string name="behavior">行為</string>
|
||||
<string name="network">網絡</string>
|
||||
<string name="interface_">界面</string>
|
||||
<string name="restart">重啟</string>
|
||||
<string name="auto_restart">自動重啟</string>
|
||||
<string name="allow_clash_auto_restart">允許 Clash 自動重啟</string>
|
||||
<string name="notification">通知</string>
|
||||
<string name="show_traffic">顯示流量</string>
|
||||
<string name="show_traffic_summary">在通知中自動刷新流量</string>
|
||||
<string name="route_system_traffic">自動路由系統流量</string>
|
||||
<string name="routing_via_vpn_service">通過 VpnService 自動路由所有系統流量</string>
|
||||
<string name="vpn_service">VPN Service</string>
|
||||
<string name="bypass_private_network">繞過私有網絡</string>
|
||||
<string name="bypass_private_network_summary">繞過私有網路地址</string>
|
||||
<string name="dns_hijacking">DNS 劫持</string>
|
||||
<string name="dns_hijacking_summary">處理所有 DNS 數據包</string>
|
||||
<string name="dns_override">DNS 配置覆盖</string>
|
||||
<string name="dns_override_summary">強制使用內置 DNS 配置</string>
|
||||
<string name="append_system_dns">追加系統 DNS</string>
|
||||
<string name="append_system_dns_summary">自動追加系統 DNS 到 Clash</string>
|
||||
<string name="access_control_mode">訪問控制模式</string>
|
||||
<string name="access_control_packages">訪問控制應用包列表</string>
|
||||
<string name="access_control_packages_summary">為應用配置訪問權限</string>
|
||||
<string name="language">語言</string>
|
||||
<string name="dark_mode">暗黑模式</string>
|
||||
<string name="options_unavailable">選項在 Clash 運行時不可用</string>
|
||||
<string name="system_apps">系統應用</string>
|
||||
<string name="sort">排序</string>
|
||||
<string name="package_name">應用包名稱</string>
|
||||
<string name="update_time">更新時間</string>
|
||||
<string name="install_time">安裝時間</string>
|
||||
<string name="reverse">反轉</string>
|
||||
<string name="search">查找</string>
|
||||
<string name="sources">源代碼</string>
|
||||
<string name="clash">Clash</string>
|
||||
<string name="clash_for_android">Clash for Android</string>
|
||||
<string name="feedback">反饋</string>
|
||||
<string name="donate">捐贈</string>
|
||||
<string name="github_issues">Github Issues</string>
|
||||
<string name="telegram_channel">Telegram 頻道</string>
|
||||
<string name="tips_profile"><![CDATA[僅接受 <strong>Clash 配置文件</strong> <br />其中包含了 <strong>代理, 代理組 和 規則</strong>]]></string>
|
||||
<string name="tips_support"><![CDATA[Clash for Android 是一個<strong>免費開源</strong>的項目<br /> 我們<strong>不提供</strong>任何代理服務<br />請務必<strong>不要</strong>反饋非應用自身引起的問題]]></string>
|
||||
<string name="application_broken_description"><![CDATA[這通常意味着您使用來自 <strong>Google Play</strong> 的副本, 但是生成該副本的應用未能正確處理 <strong>分包機制</strong><br />這意味着您獲取的是 <strong>應用的一部分</strong>]]></string>
|
||||
<string name="learn_more_about_split_apks">瞭解更多關於分包機制</string>
|
||||
<string name="reinstall_from_google_play">重新從 Google Play 安裝</string>
|
||||
<string name="download_from_github_releases">從 Github Release 下載</string>
|
||||
<string name="missing_vpn_component">系統 VPN 組件缺失</string>
|
||||
<string name="profile_not_found">配置文件丢失</string>
|
||||
<string name="upload_logcat">上傳日誌</string>
|
||||
<string name="upload_logcat_summary">上傳日誌以幫助我們偵測問題</string>
|
||||
<string name="upload_logcat_warn">請注意, 上傳的日誌可能包含個人敏感信息, 仍要繼續嗎?</string>
|
||||
<string name="uploaded">已上傳</string>
|
||||
</resources>
|
||||
20
app/src/main/res/values-zh-rTW/arrays.xml
Normal file
20
app/src/main/res/values-zh-rTW/arrays.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="access_control_mode_display">
|
||||
<item>允許所有應用</item>
|
||||
<item>僅允許已選擇的應用</item>
|
||||
<item>不允許已選擇的應用</item>
|
||||
</string-array>
|
||||
<string-array name="dark_mode">
|
||||
<item>自動</item>
|
||||
<item>暗黑</item>
|
||||
<item>明亮</item>
|
||||
</string-array>
|
||||
<string-array name="language">
|
||||
<item>自動</item>
|
||||
<item>英文</item>
|
||||
<item>簡體中文</item>
|
||||
<item>繁體中文-香港</item>
|
||||
<item>繁體中文-臺灣</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
138
app/src/main/res/values-zh-rTW/strings.xml
Normal file
138
app/src/main/res/values-zh-rTW/strings.xml
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="launch_name">Clash</string>
|
||||
<string name="application_name">Clash for Android</string>
|
||||
<string name="stopped">已停止</string>
|
||||
<string name="tap_to_start">點此啟動</string>
|
||||
<string name="running">執行中</string>
|
||||
<string name="format_traffic_forwarded">%s 已轉發</string>
|
||||
<string name="proxy">代理</string>
|
||||
<string name="direct_mode">直連模式</string>
|
||||
<string name="rule_mode">規則模式</string>
|
||||
<string name="global_mode">全域性模式</string>
|
||||
<string name="profiles">配置</string>
|
||||
<string name="not_selected">未選擇</string>
|
||||
<string name="format_profile_activated">%s 已啟用</string>
|
||||
<string name="logs">日誌</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="support">支援</string>
|
||||
<string name="about">關於</string>
|
||||
<string name="unknown">未知</string>
|
||||
<string name="recently">近期</string>
|
||||
<string name="format_minutes">%d 分鐘</string>
|
||||
<string name="format_hours">%d 小時</string>
|
||||
<string name="format_days">%d 天</string>
|
||||
<string name="format_months">%d 月</string>
|
||||
<string name="format_years">%d 年</string>
|
||||
<string name="create_profile">建立配置</string>
|
||||
<string name="file">檔案</string>
|
||||
<string name="import_from_file">從檔案匯入</string>
|
||||
<string name="url">URL</string>
|
||||
<string name="import_from_url">從 URL 匯入</string>
|
||||
<string name="external">外部</string>
|
||||
<string name="application_broken">應用損壞</string>
|
||||
<string name="profile">配置</string>
|
||||
<string name="name">名稱</string>
|
||||
<string name="profile_name">配置名稱</string>
|
||||
<string name="profile_url">配置 URL</string>
|
||||
<string name="auto_update">自動更新 (分鐘)</string>
|
||||
<string name="more_than_15_minutes">大於15分鐘</string>
|
||||
<string name="invalid_interval">無效的間隔</string>
|
||||
<string name="download_failure">下載失敗</string>
|
||||
<string name="detail">詳情</string>
|
||||
<string name="update">更新</string>
|
||||
<string name="edit">編輯</string>
|
||||
<string name="delete">刪除</string>
|
||||
<string name="export">匯出</string>
|
||||
<string name="properties">引數</string>
|
||||
<string name="duplicate">複製</string>
|
||||
<string name="reset_provider">重置外部引用</string>
|
||||
<string name="exit_without_save">退出而不儲存</string>
|
||||
<string name="exit_without_save_warning">所有變更將會丟失</string>
|
||||
<string name="disabled">已禁用</string>
|
||||
<string name="empty_name">空名稱</string>
|
||||
<string name="invalid_url">無效的 URL</string>
|
||||
<string name="processing">處理中</string>
|
||||
<string name="loading">載入中</string>
|
||||
<string name="new_profile">新配置</string>
|
||||
<string name="clone_profile">複製配置</string>
|
||||
<string name="edit_profile">編輯配置</string>
|
||||
<string name="clash_start_failure">Clash 啟動失敗</string>
|
||||
<string name="refresh">重新整理</string>
|
||||
<string name="delay">延遲</string>
|
||||
<string name="direct">直連</string>
|
||||
<string name="global">全域性</string>
|
||||
<string name="rule">規則</string>
|
||||
<string name="mode">模式</string>
|
||||
<string name="sort_group">代理組排序</string>
|
||||
<string name="sort_proxy">代理排序</string>
|
||||
<string name="default_">預設</string>
|
||||
<string name="utils">實用工具</string>
|
||||
<string name="merge_prefix">前綴合並</string>
|
||||
<string name="start_url_provider_failure">開啟 URL 提供程式失敗</string>
|
||||
<string name="clash_new_profile">新配置</string>
|
||||
<string name="ok">確認</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="clash_logcat">Clash 日誌捕捉工具</string>
|
||||
<string name="history">歷史</string>
|
||||
<string name="log_viewer">日誌檢視器</string>
|
||||
<string name="format_export_log_name">%s.log</string>
|
||||
<string name="delete_all_logs">刪除所有日誌</string>
|
||||
<string name="delete_all_logs_warn">所有歷史日誌將丟失</string>
|
||||
<string name="delete_log">刪除日誌</string>
|
||||
<string name="delete_log_warn">%s 將被刪除</string>
|
||||
<string name="open_log_failure">開啟日誌失敗</string>
|
||||
<string name="file_exported">檔案已匯出</string>
|
||||
<string name="behavior">行為</string>
|
||||
<string name="network">網路</string>
|
||||
<string name="interface_">介面</string>
|
||||
<string name="restart">重啟</string>
|
||||
<string name="auto_restart">自動重啟</string>
|
||||
<string name="allow_clash_auto_restart">允許 Clash 自動重啟</string>
|
||||
<string name="notification">通知</string>
|
||||
<string name="show_traffic">顯示流量</string>
|
||||
<string name="show_traffic_summary">在通知中自動重新整理流量</string>
|
||||
<string name="route_system_traffic">自動路由系統流量</string>
|
||||
<string name="routing_via_vpn_service">通過 VpnService 自動路由所有系統流量</string>
|
||||
<string name="vpn_service">VPN Service</string>
|
||||
<string name="bypass_private_network">繞過私有網路</string>
|
||||
<string name="bypass_private_network_summary">繞過私有網路地址</string>
|
||||
<string name="dns_hijacking">DNS 劫持</string>
|
||||
<string name="dns_hijacking_summary">處理所有 DNS 資料包</string>
|
||||
<string name="dns_override">DNS 配置覆蓋</string>
|
||||
<string name="dns_override_summary">強制使用內建 DNS 配置</string>
|
||||
<string name="append_system_dns">追加系統 DNS</string>
|
||||
<string name="append_system_dns_summary">自動追加系統 DNS 到 Clash</string>
|
||||
<string name="access_control_mode">訪問控制模式</string>
|
||||
<string name="access_control_packages">訪問控制應用包列表</string>
|
||||
<string name="access_control_packages_summary">為應用配置訪問許可權</string>
|
||||
<string name="language">語言</string>
|
||||
<string name="dark_mode">暗黑模式</string>
|
||||
<string name="options_unavailable">選項在 Clash 執行時不可用</string>
|
||||
<string name="system_apps">系統應用</string>
|
||||
<string name="sort">排序</string>
|
||||
<string name="package_name">應用包名稱</string>
|
||||
<string name="update_time">更新時間</string>
|
||||
<string name="install_time">安裝時間</string>
|
||||
<string name="reverse">反轉</string>
|
||||
<string name="search">查詢</string>
|
||||
<string name="sources">原始碼</string>
|
||||
<string name="clash">Clash</string>
|
||||
<string name="clash_for_android">Clash for Android</string>
|
||||
<string name="feedback">反饋</string>
|
||||
<string name="donate">捐贈</string>
|
||||
<string name="github_issues">Github Issues</string>
|
||||
<string name="telegram_channel">Telegram 頻道</string>
|
||||
<string name="tips_profile"><![CDATA[僅接受 <strong>Clash 配置檔案</strong> <br />其中包含了 <strong>代理, 代理組 和 規則</strong>]]></string>
|
||||
<string name="tips_support"><![CDATA[Clash for Android 是一個<strong>免費開源</strong>的專案<br /> 我們<strong>不提供</strong>任何代理服務<br />請務必<strong>不要</strong>反饋非應用自身引起的問題]]></string>
|
||||
<string name="application_broken_description"><![CDATA[這通常意味著您使用來自 <strong>Google Play</strong> 的副本, 但是生成該副本的應用未能正確處理 <strong>分包機制</strong><br />這意味著您獲取的是 <strong>應用的一部分</strong>]]></string>
|
||||
<string name="learn_more_about_split_apks">瞭解更多關於分包機制</string>
|
||||
<string name="reinstall_from_google_play">重新從 Google Play 安裝</string>
|
||||
<string name="download_from_github_releases">從 Github Release 下載</string>
|
||||
<string name="missing_vpn_component">系統 VPN 元件缺失</string>
|
||||
<string name="profile_not_found">配置檔案丟失</string>
|
||||
<string name="upload_logcat">上傳日誌</string>
|
||||
<string name="upload_logcat_summary">上傳日誌以幫助我們偵測問題</string>
|
||||
<string name="upload_logcat_warn">請注意, 上傳的日誌可能包含個人敏感資訊, 仍要繼續嗎?</string>
|
||||
<string name="uploaded">已上傳</string>
|
||||
</resources>
|
||||
@@ -14,5 +14,7 @@
|
||||
<item>自动</item>
|
||||
<item>英文</item>
|
||||
<item>简体中文</item>
|
||||
<item>繁体中文-香港</item>
|
||||
<item>繁体中文-台湾</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -131,4 +131,8 @@
|
||||
<string name="tips_support"><![CDATA[Clash for Android 是一个<strong>免费开源</strong>的项目<br /> 我们<strong>不提供</strong>任何代理服务<br />请务必<strong>不要</strong>反馈非应用自身引起的问题]]></string>
|
||||
<string name="donate">捐赠</string>
|
||||
<string name="clone_profile">复制配置</string>
|
||||
<string name="upload_logcat">上传日志</string>
|
||||
<string name="upload_logcat_summary">上传日志以帮助我们侦测问题</string>
|
||||
<string name="upload_logcat_warn">请注意, 上传的日志可能包含个人敏感信息, 仍要继续吗?</string>
|
||||
<string name="uploaded">已上传</string>
|
||||
</resources>
|
||||
@@ -14,6 +14,8 @@
|
||||
<item>Auto</item>
|
||||
<item>English</item>
|
||||
<item>Simplified Chinese</item>
|
||||
<item>Traditional Chinese-Hong Kong</item>
|
||||
<item>Traditional Chinese-Taiwan</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dark_mode_value" translatable="false">
|
||||
@@ -30,5 +32,7 @@
|
||||
<item />
|
||||
<item>en</item>
|
||||
<item>zh-rCN</item>
|
||||
<item>zh-rHK</item>
|
||||
<item>zh-rTW</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -173,4 +173,9 @@
|
||||
<string name="missing_vpn_component">Missing VPN Components</string>
|
||||
|
||||
<string name="profile_not_found">Profile not found</string>
|
||||
|
||||
<string name="upload_logcat">Upload Logcat</string>
|
||||
<string name="upload_logcat_summary">Upload logcat to help us detect issues</string>
|
||||
<string name="upload_logcat_warn">Please note that the uploaded logs may contain personally sensitive information, do you still want to continue?</string>
|
||||
<string name="uploaded">Uploaded</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,38 +1,16 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
val kotlinVersion = "1.3.71"
|
||||
val gKotlinVersion: String by project
|
||||
|
||||
rootProject.extra.apply {
|
||||
this["gBuildToolsVersion"] = "29.0.3"
|
||||
|
||||
this["gCompileSdkVersion"] = 29
|
||||
this["gMinSdkVersion"] = 24
|
||||
this["gTargetSdkVersion"] = 29
|
||||
|
||||
this["gVersionCode"] = 10204
|
||||
this["gVersionName"] = "1.2.4"
|
||||
|
||||
this["gKotlinVersion"] = kotlinVersion
|
||||
this["gKotlinCoroutineVersion"] = "1.3.5"
|
||||
this["gKotlinSerializationVersion"] = "0.20.0"
|
||||
this["gRoomVersion"] = "2.2.5"
|
||||
this["gAppCenterVersion"] = "2.5.1"
|
||||
this["gAndroidKtxVersion"] = "1.2.0"
|
||||
this["gRecyclerviewVersion"] = "1.1.0"
|
||||
this["gAppCompatVersion"] = "1.1.0"
|
||||
this["gMaterialDesignVersion"] = "1.1.0"
|
||||
this["gShizukuPreferenceVersion"] = "4.2.0"
|
||||
this["gMultiprocessPreferenceVersion"] = "1.0.0"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:4.0.0-beta04")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||
classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion")
|
||||
classpath("com.android.tools.build:gradle:4.0.0")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$gKotlinVersion")
|
||||
classpath("org.jetbrains.kotlin:kotlin-serialization:$gKotlinVersion")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,21 +4,19 @@ plugins {
|
||||
id("kotlin-android-extensions")
|
||||
}
|
||||
|
||||
val rootExtra = rootProject.extra
|
||||
val gCompileSdkVersion: String by project
|
||||
val gBuildToolsVersion: String by project
|
||||
|
||||
val gCompileSdkVersion: Int by rootExtra
|
||||
val gBuildToolsVersion: String by rootExtra
|
||||
val gMinSdkVersion: String by project
|
||||
val gTargetSdkVersion: String by project
|
||||
|
||||
val gMinSdkVersion: Int by rootExtra
|
||||
val gTargetSdkVersion: Int by rootExtra
|
||||
val gVersionCode: String by project
|
||||
val gVersionName: String by project
|
||||
|
||||
val gVersionCode: Int by rootExtra
|
||||
val gVersionName: String by rootExtra
|
||||
|
||||
val gKotlinVersion: String by rootExtra
|
||||
val gKotlinCoroutineVersion: String by rootExtra
|
||||
val gAndroidKtxVersion: String by rootExtra
|
||||
val gKotlinSerializationVersion: String by rootExtra
|
||||
val gKotlinVersion: String by project
|
||||
val gKotlinCoroutineVersion: String by project
|
||||
val gAndroidKtxVersion: String by project
|
||||
val gKotlinSerializationVersion: String by project
|
||||
|
||||
android {
|
||||
compileSdkVersion(gCompileSdkVersion)
|
||||
@@ -28,14 +26,14 @@ android {
|
||||
minSdkVersion(gMinSdkVersion)
|
||||
targetSdkVersion(gTargetSdkVersion)
|
||||
|
||||
versionCode = gVersionCode
|
||||
versionCode = gVersionCode.toInt()
|
||||
versionName = gVersionName
|
||||
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
maybeCreate("release").apply {
|
||||
named("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apply(from = "clash.gradle.kts")
|
||||
import android.databinding.tool.ext.toCamelCase
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
@@ -7,23 +7,26 @@ plugins {
|
||||
id("kotlinx-serialization")
|
||||
}
|
||||
|
||||
val rootExtra = rootProject.extra
|
||||
apply(from = "clash.gradle.kts")
|
||||
|
||||
val gCompileSdkVersion: Int by rootExtra
|
||||
val gBuildToolsVersion: String by rootExtra
|
||||
val gCompileSdkVersion: String by project
|
||||
val gBuildToolsVersion: String by project
|
||||
|
||||
val gMinSdkVersion: Int by rootExtra
|
||||
val gTargetSdkVersion: Int by rootExtra
|
||||
val gMinSdkVersion: String by project
|
||||
val gTargetSdkVersion: String by project
|
||||
|
||||
val gVersionCode: Int by rootExtra
|
||||
val gVersionName: String by rootExtra
|
||||
val gVersionCode: String by project
|
||||
val gVersionName: String by project
|
||||
|
||||
val gKotlinVersion: String by rootExtra
|
||||
val gKotlinCoroutineVersion: String by rootExtra
|
||||
val gKotlinSerializationVersion: String by rootExtra
|
||||
val gAndroidKtxVersion: String by rootExtra
|
||||
val gKotlinVersion: String by project
|
||||
val gKotlinCoroutineVersion: String by project
|
||||
val gKotlinSerializationVersion: String by project
|
||||
val gAndroidKtxVersion: String by project
|
||||
|
||||
val clashCoreOutput = buildDir.resolve("extraSources")
|
||||
val geoipOutput = buildDir.resolve("outputs/geoip")
|
||||
val golangSource = file("src/main/golang")
|
||||
val golangOutput = buildDir.resolve("outputs/golang")
|
||||
val nativeAbis = listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
android {
|
||||
compileSdkVersion(gCompileSdkVersion)
|
||||
@@ -33,24 +36,30 @@ android {
|
||||
minSdkVersion(gMinSdkVersion)
|
||||
targetSdkVersion(gTargetSdkVersion)
|
||||
|
||||
versionCode = gVersionCode
|
||||
versionCode = gVersionCode.toInt()
|
||||
versionName = gVersionName
|
||||
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters(*nativeAbis.toTypedArray())
|
||||
arguments("-DCLASH_OUTPUT=$golangOutput", "-DCLASH_SOURCE=$golangSource")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
maybeCreate("release").apply {
|
||||
named("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
maybeCreate("main").apply {
|
||||
assets.srcDir(clashCoreOutput.resolve("assets"))
|
||||
jniLibs.srcDir(clashCoreOutput.resolve("jniLibs"))
|
||||
java.srcDir(clashCoreOutput.resolve("classes"))
|
||||
named("main") {
|
||||
assets.srcDir(geoipOutput)
|
||||
jniLibs.srcDir(golangOutput)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +71,12 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -77,6 +92,10 @@ repositories {
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks["clean"].dependsOn(tasks["resetGolangMode"])
|
||||
tasks["preBuild"].dependsOn(tasks["extractSources"], tasks["downloadGeoipDatabase"])
|
||||
android.buildTypes.forEach {
|
||||
val cName = it.name.toCamelCase()
|
||||
|
||||
tasks["externalNativeBuild${cName}"].dependsOn(tasks["compileClashCore"])
|
||||
tasks["package${cName}Assets"].dependsOn(tasks["downloadGeoipDatabase"])
|
||||
}
|
||||
}
|
||||
@@ -2,85 +2,110 @@ import org.apache.tools.ant.taskdefs.condition.Os
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import java.net.*
|
||||
import java.time.*
|
||||
|
||||
object Constants {
|
||||
const val GEOIP_DATABASE_URL = "https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb"
|
||||
const val GEOIP_INVALID_INTERVAL = 1000L * 60 * 60 * 24 * 7
|
||||
val gMinSdkVersion: String by project
|
||||
|
||||
const val SOURCE_PATH = "src/main/golang"
|
||||
const val OUTPUT_PATH = "extraSources"
|
||||
val geoipDatabaseUrl = "https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb"
|
||||
val geoipInvalidate = Duration.ofDays(7)
|
||||
val geoipOutput = buildDir.resolve("outputs/geoip")
|
||||
val golangSource = file("src/main/golang")
|
||||
val golangOutput = buildDir.resolve("outputs/golang")
|
||||
val nativeAbis = listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
const val GOLANG_BASE = "intermediates/golang"
|
||||
const val GOLANG_PATH = "$GOLANG_BASE/path"
|
||||
const val GOLANG_BIND = "$GOLANG_BASE/bind"
|
||||
const val GOLANG_BINARY = "$GOLANG_PATH/bin"
|
||||
const val GOLANG_OUTPUT = "$GOLANG_BASE/bridge.aar"
|
||||
const val GOLANG_OUTPUT_SOURCES = "$GOLANG_BASE/bridge-sources.jar"
|
||||
val String.exe: String
|
||||
get() {
|
||||
return if ( Os.isFamily(Os.FAMILY_WINDOWS) )
|
||||
"$this.exe"
|
||||
else
|
||||
this
|
||||
}
|
||||
|
||||
val STUB_GO_FILE_CONTENT = """
|
||||
package main
|
||||
fun generateGolangBuildEnvironment(abi: String): Map<String, String> {
|
||||
val properties = Properties().apply {
|
||||
load(FileInputStream(rootProject.file("local.properties")))
|
||||
}
|
||||
|
||||
import "github.com/kr328/cfa/bridge"
|
||||
|
||||
func main() {}
|
||||
""".trimIndent()
|
||||
val STUB_GO_MOD_CONTENT = """
|
||||
module github.com/kr328/cfa-bind
|
||||
|
||||
require github.com/kr328/cfa v0.0.0 // redirect
|
||||
|
||||
replace github.com/kr328/cfa => {SOURCE_PATH}
|
||||
|
||||
""".trimIndent()
|
||||
val ndk = properties.getProperty("ndk.dir")
|
||||
?: throw GradleScriptException("ndk.dir not found in local.properties",
|
||||
FileNotFoundException("ndk.dir not found in local.properties"))
|
||||
|
||||
val REGEX_REPLACE = Regex("replace\\s+(\\S+)\\s+(\\S*)\\s*=>\\s*(\\S+)\\s*(\\S*)\\s*")
|
||||
val REGEX_JNI = Regex("^jni/")
|
||||
}
|
||||
val host = when {
|
||||
Os.isFamily(Os.FAMILY_WINDOWS) ->
|
||||
"windows"
|
||||
Os.isFamily(Os.FAMILY_MAC) ->
|
||||
"darwin"
|
||||
Os.isFamily(Os.FAMILY_UNIX) ->
|
||||
"linux"
|
||||
else ->
|
||||
throw GradleScriptException("Unsupported host", FileNotFoundException("Unsupported host"))
|
||||
}
|
||||
|
||||
fun generateGolangBuildEnvironment(vararg pathAppend: String): Map<String, String> {
|
||||
val environment = TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER).apply { putAll(System.getenv()) }
|
||||
val properties = Properties().apply { load(rootProject.file("local.properties").inputStream()) }
|
||||
val minSdkVersion = gMinSdkVersion.removePrefix("android-")
|
||||
|
||||
val sdkPath = properties.getProperty("sdk.dir")
|
||||
?: throw GradleScriptException("sdk.dir not found", FileNotFoundException())
|
||||
val ndkPath = properties.getProperty("ndk.dir")
|
||||
?: throw GradleScriptException("ndk.dir not found", FileNotFoundException())
|
||||
val compilerBase = rootProject.file(ndk).resolve("toolchains/llvm/prebuilt/$host-x86_64/bin")
|
||||
|
||||
val pathSeparator = if ( Os.isFamily(Os.FAMILY_WINDOWS) ) ";" else ":"
|
||||
val cCompiler = when(abi) {
|
||||
"armeabi-v7a" ->
|
||||
"armv7a-linux-androideabi$minSdkVersion-clang"
|
||||
"arm64-v8a" ->
|
||||
"aarch64-linux-android$minSdkVersion-clang"
|
||||
"x86" ->
|
||||
"i686-linux-android$minSdkVersion-clang"
|
||||
"x86_64" ->
|
||||
"x86_64-linux-android$minSdkVersion-clang"
|
||||
else ->
|
||||
throw GradleScriptException("Unsupported abi $abi", FileNotFoundException("Unsupported abi $abi"))
|
||||
}
|
||||
|
||||
environment["GOPATH"] = listOf(buildDir.resolve(Constants.GOLANG_PATH).absolutePath, *pathAppend)
|
||||
.joinToString(separator = pathSeparator)
|
||||
environment["ANDROID_HOME"] = sdkPath
|
||||
environment["ANDROID_NDK_HOME"] = ndkPath
|
||||
environment["PATH"] += "$pathSeparator${buildDir.resolve(Constants.GOLANG_BINARY)}"
|
||||
val cppCompiler = when(abi) {
|
||||
"armeabi-v7a" ->
|
||||
"armv7a-linux-androideabi$minSdkVersion-clang++"
|
||||
"arm64-v8a" ->
|
||||
"aarch64-linux-android$minSdkVersion-clang++"
|
||||
"x86" ->
|
||||
"i686-linux-android$minSdkVersion-clang++"
|
||||
"x86_64" ->
|
||||
"x86_64-linux-android$minSdkVersion-clang++"
|
||||
else ->
|
||||
throw GradleScriptException("Unsupported abi $abi", FileNotFoundException("Unsupported abi $abi"))
|
||||
}
|
||||
|
||||
return environment
|
||||
}
|
||||
val linker = when(abi) {
|
||||
"armeabi-v7a" ->
|
||||
"arm-linux-androideabi-ld"
|
||||
"arm64-v8a" ->
|
||||
"aarch64-linux-android-ld"
|
||||
"x86" ->
|
||||
"i686-linux-android-ld"
|
||||
"x86_64" ->
|
||||
"x86_64-linux-android-ld"
|
||||
else ->
|
||||
throw GradleScriptException("Unsupported abi $abi", FileNotFoundException("Unsupported abi $abi"))
|
||||
}
|
||||
|
||||
fun generateGolangModule(): String {
|
||||
val moduleFile = file(Constants.SOURCE_PATH).resolve("go.mod")
|
||||
val golangArch = when(abi) {
|
||||
"armeabi-v7a" ->
|
||||
"arm"
|
||||
"arm64-v8a" ->
|
||||
"arm64"
|
||||
"x86" ->
|
||||
"386"
|
||||
"x86_64" ->
|
||||
"amd64"
|
||||
else ->
|
||||
throw GradleScriptException("Unsupported abi $abi", FileNotFoundException("Unsupported abi $abi"))
|
||||
}
|
||||
|
||||
val replaces = moduleFile
|
||||
.readLines()
|
||||
.asSequence()
|
||||
.map { line -> Constants.REGEX_REPLACE.matchEntire(line) }
|
||||
.filterNotNull()
|
||||
.map { match ->
|
||||
val source = match.groupValues[1].trim()
|
||||
val sVersion = match.groupValues[2].trim()
|
||||
val target = match.groupValues[3].trim()
|
||||
val tVersion = match.groupValues[4].trim()
|
||||
|
||||
val resolvedTarget = if ( target.startsWith("./") )
|
||||
moduleFile.parentFile!!.resolve(target).canonicalPath
|
||||
else
|
||||
target
|
||||
|
||||
"replace $source $sVersion => $resolvedTarget $tVersion"
|
||||
}.joinToString(separator = "\n")
|
||||
|
||||
return Constants.STUB_GO_MOD_CONTENT
|
||||
.replace("{SOURCE_PATH}", file(Constants.SOURCE_PATH).absolutePath) + replaces
|
||||
return mapOf(
|
||||
"CC" to compilerBase.resolve(cCompiler.exe).absolutePath,
|
||||
"CXX" to compilerBase.resolve(cppCompiler.exe).absolutePath,
|
||||
"LD" to compilerBase.resolve(linker.exe).absolutePath,
|
||||
"GOOS" to "android",
|
||||
"GOARCH" to golangArch,
|
||||
"CGO_ENABLED" to "1",
|
||||
"CFLAGS" to "-O3 -Werror"
|
||||
)
|
||||
}
|
||||
|
||||
fun String.exec(pwd: File = buildDir, env: Map<String, String> = System.getenv()): String {
|
||||
@@ -109,114 +134,51 @@ fun String.exec(pwd: File = buildDir, env: Map<String, String> = System.getenv()
|
||||
return outputStream.toString("utf-8")
|
||||
}
|
||||
|
||||
task("generateClashBindSources") {
|
||||
task("compileClashCore") {
|
||||
onlyIf {
|
||||
val lastModified = file(Constants.SOURCE_PATH).walk()
|
||||
.filter { it.extension == "go" || it.extension == "mod" }
|
||||
val sourceModified = golangSource.walk()
|
||||
.filter {
|
||||
when ( it.extension ) {
|
||||
"c", "cpp", "h", "go", "mod" -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
.map { it.lastModified() }
|
||||
.max() ?: 0L
|
||||
.max() ?: Long.MAX_VALUE
|
||||
val targetModified = golangOutput.walk()
|
||||
.filter { it.extension == "so" }
|
||||
.map { it.lastModified() }
|
||||
.min() ?: Long.MIN_VALUE
|
||||
|
||||
return@onlyIf lastModified > buildDir.resolve(Constants.GOLANG_OUTPUT).lastModified()
|
||||
}
|
||||
|
||||
doFirst {
|
||||
buildDir.resolve(Constants.GOLANG_BIND).apply {
|
||||
deleteRecursively()
|
||||
mkdirs()
|
||||
}
|
||||
sourceModified > targetModified
|
||||
}
|
||||
|
||||
doLast {
|
||||
val environment = generateGolangBuildEnvironment()
|
||||
nativeAbis.parallelStream().forEach {
|
||||
val env = generateGolangBuildEnvironment(it)
|
||||
val out = golangOutput.resolve(it).apply {
|
||||
mkdirs()
|
||||
}.resolve("libclash.so")
|
||||
|
||||
val bind = buildDir.resolve(Constants.GOLANG_BIND).apply {
|
||||
resolve("main.go").writeText(Constants.STUB_GO_FILE_CONTENT)
|
||||
resolve("go.mod").writeText(generateGolangModule())
|
||||
}
|
||||
|
||||
"go mod vendor".exec(pwd = bind, env = environment)
|
||||
|
||||
buildDir.resolve(Constants.GOLANG_BIND).apply {
|
||||
resolve("vendor").renameTo(resolve("src"))
|
||||
resolve("go.mod").delete()
|
||||
resolve("main.go").delete()
|
||||
resolve("go.sum").delete()
|
||||
"go build --buildmode=c-shared -trimpath -o \"$out\"".exec(pwd = golangSource, env = env)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task("bindClashCore") {
|
||||
dependsOn(tasks["generateClashBindSources"])
|
||||
|
||||
onlyIf {
|
||||
!tasks["generateClashBindSources"].state.skipped
|
||||
}
|
||||
|
||||
doFirst {
|
||||
val environment = generateGolangBuildEnvironment()
|
||||
|
||||
"go get golang.org/x/mobile/cmd/gomobile".exec(env = environment)
|
||||
}
|
||||
|
||||
doLast {
|
||||
val bind = buildDir.resolve(Constants.GOLANG_BIND)
|
||||
val environment = generateGolangBuildEnvironment(bind.absolutePath)
|
||||
|
||||
"gomobile init".exec(pwd = bind, env = environment)
|
||||
"gomobile bind -target=android -trimpath github.com/kr328/cfa/bridge"
|
||||
.exec(pwd = buildDir.resolve(Constants.GOLANG_BASE), env = environment)
|
||||
}
|
||||
}
|
||||
|
||||
task("extractSources", type = Copy::class) {
|
||||
dependsOn(tasks["bindClashCore"])
|
||||
|
||||
doFirst {
|
||||
buildDir.resolve(Constants.OUTPUT_PATH).apply {
|
||||
resolve("jniLibs").deleteRecursively()
|
||||
resolve("classes").deleteRecursively()
|
||||
}
|
||||
}
|
||||
from(zipTree(buildDir.resolve(Constants.GOLANG_OUTPUT))) {
|
||||
include("**/*.so")
|
||||
eachFile {
|
||||
path = path.replace(Constants.REGEX_JNI, "jniLibs/")
|
||||
}
|
||||
}
|
||||
from(zipTree(buildDir.resolve(Constants.GOLANG_OUTPUT_SOURCES))) {
|
||||
include("**/*.java")
|
||||
into("classes")
|
||||
}
|
||||
|
||||
destinationDir = buildDir.resolve(Constants.OUTPUT_PATH)
|
||||
}
|
||||
|
||||
task("downloadGeoipDatabase") {
|
||||
onlyIf {
|
||||
val file = buildDir.resolve(Constants.OUTPUT_PATH).resolve("assets/Country.mmdb")
|
||||
val geoipFile = geoipOutput.resolve("Country.mmdb")
|
||||
|
||||
System.currentTimeMillis() - file.lastModified() > Constants.GEOIP_INVALID_INTERVAL
|
||||
onlyIf {
|
||||
System.currentTimeMillis() - geoipFile.lastModified() > geoipInvalidate.toMillis()
|
||||
}
|
||||
|
||||
doLast {
|
||||
val assets = buildDir.resolve(Constants.OUTPUT_PATH).resolve("assets")
|
||||
geoipOutput.mkdirs()
|
||||
|
||||
assets.mkdirs()
|
||||
|
||||
URL(Constants.GEOIP_DATABASE_URL).openConnection().getInputStream().use { input ->
|
||||
FileOutputStream(assets.resolve("Country.mmdb")).use { output ->
|
||||
URL(geoipDatabaseUrl).openConnection().getInputStream().use { input ->
|
||||
FileOutputStream(geoipFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task("resetGolangMode", type = Exec::class) {
|
||||
onlyIf {
|
||||
!Os.isFamily(Os.FAMILY_WINDOWS)
|
||||
}
|
||||
|
||||
commandLine("chmod", "-R", "777", buildDir.resolve(Constants.GOLANG_PATH))
|
||||
|
||||
isIgnoreExitValue = true
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
-keep class go.* {
|
||||
*;
|
||||
}
|
||||
-keep class bridge.* {
|
||||
*;
|
||||
}
|
||||
11
core/src/main/cpp/CMakeLists.txt
Normal file
11
core/src/main/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(clash_bridge)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
include_directories(${CLASH_OUTPUT}/${ANDROID_ABI} ${CLASH_SOURCE})
|
||||
link_directories(${CLASH_OUTPUT}/${CMAKE_ANDROID_ARCH_ABI})
|
||||
link_libraries(log clash)
|
||||
|
||||
add_library(bridge SHARED main.cpp main.h init.cpp query.cpp patch.cpp tun.cpp defer.cpp log.cpp event_queue.cpp)
|
||||
112
core/src/main/cpp/defer.cpp
Normal file
112
core/src/main/cpp/defer.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "main.h"
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
static std::pair<jobject, uint64_t> completableFutureWithToken(Master::Context *context) {
|
||||
uint64_t token = EventQueue::getInstance()->obtainToken();
|
||||
jobject completableFuture = context->newGlobalReference(context->newCompletableFuture());
|
||||
|
||||
EventQueue::getInstance()->registerHandler(COMPLETE, token, [completableFuture](const event_t *event) {
|
||||
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
|
||||
Master::runWithContext<void>(env, [&](Master::Context *context) {
|
||||
if ( strlen(event->payload) == 0 ) {
|
||||
context->completeCompletableFuture(completableFuture, nullptr);
|
||||
} else {
|
||||
context->completeExceptionallyCompletableFuture(completableFuture, context->newClashException(event->payload));
|
||||
}
|
||||
|
||||
context->removeGlobalReference(completableFuture);
|
||||
});
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
EventQueue::getInstance()->unregisterHandler(COMPLETE, event->token);
|
||||
});
|
||||
|
||||
return {completableFuture, token};
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_downloadProfile__ILjava_lang_String_2Ljava_lang_String_2(
|
||||
JNIEnv *env, jclass clazz, jint fd, jstring base, jstring output) {
|
||||
UNUSED(clazz);
|
||||
|
||||
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
|
||||
const char *b = context->getString(base);
|
||||
const char *o = context->getString(output);
|
||||
|
||||
auto completableFuture = completableFutureWithToken(context);
|
||||
|
||||
downloadProfileFromFd(fd, b, o, completableFuture.second);
|
||||
|
||||
context->releaseString(base, b);
|
||||
context->releaseString(output, o);
|
||||
|
||||
return completableFuture.first;
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_downloadProfile__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2(
|
||||
JNIEnv *env, jclass clazz, jstring url, jstring base, jstring output) {
|
||||
UNUSED(clazz);
|
||||
|
||||
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
|
||||
const char *u = context->getString(url);
|
||||
const char *b = context->getString(base);
|
||||
const char *o = context->getString(output);
|
||||
|
||||
auto completableFuture = completableFutureWithToken(context);
|
||||
|
||||
downloadProfileFromUrl(u, b, o, completableFuture.second);
|
||||
|
||||
context->releaseString(url, u);
|
||||
context->releaseString(base, b);
|
||||
context->releaseString(output, o);
|
||||
|
||||
return completableFuture.first;
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_loadProfile(JNIEnv *env, jclass clazz, jstring path,
|
||||
jstring base) {
|
||||
UNUSED(clazz);
|
||||
|
||||
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
|
||||
const char *p = context->getString(path);
|
||||
const char *b = context->getString(base);
|
||||
|
||||
auto completableFuture = completableFutureWithToken(context);
|
||||
|
||||
loadProfile(p, b, completableFuture.second);
|
||||
|
||||
context->releaseString(path, p);
|
||||
context->releaseString(base, b);
|
||||
|
||||
return completableFuture.first;
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_performHealthCheck(JNIEnv *env, jclass clazz,
|
||||
jstring group) {
|
||||
UNUSED(clazz);
|
||||
|
||||
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
|
||||
const char *g = context->getString(group);
|
||||
|
||||
auto completableFuture = completableFutureWithToken(context);
|
||||
|
||||
performHealthCheck(g, completableFuture.second);
|
||||
|
||||
context->releaseString(group, g);
|
||||
|
||||
return completableFuture.first;
|
||||
});
|
||||
}
|
||||
101
core/src/main/cpp/event_queue.cpp
Normal file
101
core/src/main/cpp/event_queue.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "event_queue.h"
|
||||
|
||||
static void *handleEventQueue(void *context) {
|
||||
auto *queue = reinterpret_cast<EventQueue*>(context);
|
||||
|
||||
while ( !queue->isClosed() ) {
|
||||
const event_t *e = queue->dequeueEvent();
|
||||
|
||||
EventQueue::Handler h = queue->findHandler(e->type, e->token);
|
||||
|
||||
h(e);
|
||||
|
||||
answer_event(e);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EventQueue::EventQueue(): lock(), condition(), closed(false), currentToken(0) {
|
||||
instance = this;
|
||||
|
||||
pthread_mutex_init(&lock, nullptr);
|
||||
pthread_cond_init(&condition, nullptr);
|
||||
|
||||
for ( int i = 0 ; i < DEFAULT_EVENT_QUEUE_PROCESSES ; i++ ) {
|
||||
pthread_t tid = 0;
|
||||
|
||||
if ( pthread_create(&tid, nullptr, &handleEventQueue, this) < 0 )
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void EventQueue::enqueueEvent(const event_t *event) {
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
queue.push_back(event);
|
||||
|
||||
pthread_cond_signal(&condition);
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
const event_t *EventQueue::dequeueEvent() {
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
while ( queue.empty() )
|
||||
pthread_cond_wait(&condition, &lock);
|
||||
|
||||
auto *result = queue.back();
|
||||
|
||||
queue.pop_back();
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void EventQueue::registerHandler(event_type_t type, uint64_t token, const EventQueue::Handler& handler) {
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
handlers[type][token] = handler;
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
void EventQueue::unregisterHandler(event_type_t type, uint64_t token) {
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
handlers[type].erase(token);
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
EventQueue::Handler EventQueue::findHandler(event_type_t type, uint64_t token) {
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
Handler result = handlers[type][token];
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
if (result == nullptr)
|
||||
return [](const event_t*){};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EventQueue *EventQueue::getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
uint64_t EventQueue::obtainToken() {
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
uint64_t r = currentToken++;
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
EventQueue *EventQueue::instance;
|
||||
51
core/src/main/cpp/event_queue.h
Normal file
51
core/src/main/cpp/event_queue.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "event.h"
|
||||
|
||||
#define DEFAULT_EVENT_QUEUE_PROCESSES 8
|
||||
|
||||
class EventQueue {
|
||||
public:
|
||||
typedef std::function<void (const event_t *event)> Handler;
|
||||
|
||||
public:
|
||||
EventQueue();
|
||||
|
||||
public:
|
||||
void enqueueEvent(const event_t *event);
|
||||
const event_t *dequeueEvent();
|
||||
|
||||
public:
|
||||
void registerHandler(event_type_t type, uint64_t token, const Handler& handler);
|
||||
void unregisterHandler(event_type_t type, uint64_t token);
|
||||
Handler findHandler(event_type_t type, uint64_t token);
|
||||
|
||||
public:
|
||||
uint64_t obtainToken();
|
||||
|
||||
public:
|
||||
static EventQueue *getInstance();
|
||||
|
||||
public:
|
||||
inline bool isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
private:
|
||||
bool closed;
|
||||
std::vector<const event_t*> queue;
|
||||
std::map<event_type_t, std::map<uint64_t, Handler>> handlers;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t condition;
|
||||
uint64_t currentToken;
|
||||
|
||||
public:
|
||||
static EventQueue *instance;
|
||||
};
|
||||
30
core/src/main/cpp/init.cpp
Normal file
30
core/src/main/cpp/init.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "main.h"
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_initialize(JNIEnv *env, jclass clazz,
|
||||
jbyteArray database, jstring home,
|
||||
jstring version) {
|
||||
UNUSED(clazz);
|
||||
|
||||
Master::runWithContext<void>(env, [&](Master::Context *context) {
|
||||
const_buffer_t databaseBuffer = context->createConstBufferFromByteArray(database);
|
||||
const char *homeString = context->getString(home);
|
||||
const char *versionString = context->getString(version);
|
||||
|
||||
initialize(&databaseBuffer, homeString, versionString);
|
||||
|
||||
context->releaseConstBufferFromByteArray(database, databaseBuffer);
|
||||
context->releaseString(home, homeString);
|
||||
context->releaseString(version, versionString);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_reset(JNIEnv *env, jclass clazz) {
|
||||
UNUSED(env);
|
||||
UNUSED(clazz);
|
||||
|
||||
reset();
|
||||
}
|
||||
51
core/src/main/cpp/log.cpp
Normal file
51
core/src/main/cpp/log.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "libclash.h"
|
||||
#include "main.h"
|
||||
|
||||
static jobject logCallback;
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_setLogCallback(JNIEnv *env, jclass clazz,
|
||||
jobject callback) {
|
||||
UNUSED(clazz);
|
||||
|
||||
Master::runWithContext<void>(env, [&](Master::Context *context) {
|
||||
if ( logCallback != nullptr )
|
||||
context->removeGlobalReference(logCallback);
|
||||
|
||||
if ( callback == nullptr ) {
|
||||
logCallback = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
logCallback = context->newGlobalReference(callback);
|
||||
|
||||
EventQueue::getInstance()->registerHandler(LOG_RECEIVED, 0, [](const event_t *event) {
|
||||
Master::runWithAttached<int>([&](JNIEnv *env) {
|
||||
Master::runWithContext<void>(env, [&](Master::Context *context) {
|
||||
context->logCallbackMessage(logCallback, event->payload);
|
||||
});
|
||||
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_enableLogReport(JNIEnv *env, jclass clazz) {
|
||||
UNUSED(env);
|
||||
UNUSED(clazz);
|
||||
|
||||
enableLogReport();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_disableLogReport(JNIEnv *env, jclass clazz) {
|
||||
UNUSED(env);
|
||||
UNUSED(clazz);
|
||||
|
||||
disableLogReport();
|
||||
}
|
||||
257
core/src/main/cpp/main.cpp
Normal file
257
core/src/main/cpp/main.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "main.h"
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
Master *Master::master = nullptr;
|
||||
|
||||
template <class T>
|
||||
inline T g(JNIEnv *env, T object) {
|
||||
return reinterpret_cast<T>(env->NewGlobalRef(object));
|
||||
}
|
||||
|
||||
Master::Master(JavaVM *vm, JNIEnv *env): vm(vm) {
|
||||
master = this;
|
||||
|
||||
cClashException = g<jclass>(env, env->FindClass("com/github/kr328/clash/core/bridge/ClashException"));
|
||||
cTraffic = g<jclass>(env, env->FindClass("com/github/kr328/clash/core/model/Traffic"));
|
||||
cGeneral = g<jclass>(env, env->FindClass("com/github/kr328/clash/core/model/General"));
|
||||
cCompletableFuture = g<jclass>(env, env->FindClass("java/util/concurrent/CompletableFuture"));
|
||||
cProxyGroup = g<jclass>(env, env->FindClass("com/github/kr328/clash/core/model/ProxyGroup"));
|
||||
cProxy = g<jclass>(env, env->FindClass("com/github/kr328/clash/core/model/Proxy"));
|
||||
cLogEvent = g<jclass>(env, env->FindClass("com/github/kr328/clash/core/event/LogEvent"));
|
||||
iTunCallback = g<jclass>(env, env->FindClass("com/github/kr328/clash/core/bridge/TunCallback"));
|
||||
iLogCallback = g<jclass>(env, env->FindClass("com/github/kr328/clash/core/bridge/LogCallback"));
|
||||
cClashExceptionConstructor = env->GetMethodID(cClashException, "<init>",
|
||||
"(Ljava/lang/String;)V");
|
||||
cTrafficConstructor = env->GetMethodID(cTraffic, "<init>", "(JJ)V");
|
||||
cGeneralConstructor = env->GetMethodID(cGeneral, "<init>", "(Ljava/lang/String;IIII)V");
|
||||
cCompletableFutureConstructor = env->GetMethodID(cCompletableFuture, "<init>", "()V");
|
||||
cProxyGroupConstructor = env->GetMethodID(cProxyGroup, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Lcom/github/kr328/clash/core/model/Proxy;)V");
|
||||
cProxyConstructor = env->GetMethodID(cProxy, "<init>", "(Ljava/lang/String;Ljava/lang/String;J)V");
|
||||
cLogEventConstructor = env->GetMethodID(cLogEvent, "<init>", "(Ljava/lang/String;)V");
|
||||
mCompletableFutureComplete = env->GetMethodID(cCompletableFuture, "complete",
|
||||
"(Ljava/lang/Object;)Z");
|
||||
mCompletableFutureCompleteExceptionally = env->GetMethodID(cCompletableFuture,
|
||||
"completeExceptionally",
|
||||
"(Ljava/lang/Throwable;)Z");
|
||||
mTunCallbackOnNewSocket = env->GetMethodID(iTunCallback, "onNewSocket", "(I)V");
|
||||
mTunCallbackOnStop = env->GetMethodID(iTunCallback, "onStop", "()V");
|
||||
mLogCallbackOnMessage = env->GetMethodID(iLogCallback, "onMessage", "(Lcom/github/kr328/clash/core/event/LogEvent;)V");
|
||||
|
||||
sDirect = g<jstring>(env, env->NewStringUTF("Direct"));
|
||||
sReject = g<jstring>(env, env->NewStringUTF("Reject"));
|
||||
sShadowsocks = g<jstring>(env, env->NewStringUTF("Shadowsocks"));
|
||||
sSnell = g<jstring>(env, env->NewStringUTF("Snell"));
|
||||
sSocks5 = g<jstring>(env, env->NewStringUTF("Socks5"));
|
||||
sHttp = g<jstring>(env, env->NewStringUTF("Http"));
|
||||
sVmess = g<jstring>(env, env->NewStringUTF("Vmess"));
|
||||
sTrojan = g<jstring>(env, env->NewStringUTF("Trojan"));
|
||||
sRelay = g<jstring>(env, env->NewStringUTF("Relay"));
|
||||
sSelector = g<jstring>(env, env->NewStringUTF("Selector"));
|
||||
sFallback = g<jstring>(env, env->NewStringUTF("Fallback"));
|
||||
sURLTest = g<jstring>(env, env->NewStringUTF("URLTest"));
|
||||
sLoadBalance = g<jstring>(env, env->NewStringUTF("LoadBalance"));
|
||||
sUnknown = g<jstring>(env, env->NewStringUTF("Unknown"));
|
||||
}
|
||||
|
||||
Master::Context::Context(JNIEnv *env) {
|
||||
this->env = env;
|
||||
}
|
||||
|
||||
jthrowable Master::Context::newClashException(const char *reason) {
|
||||
return reinterpret_cast<jthrowable>(env->NewObject(master->cClashException,
|
||||
master->cClashExceptionConstructor,
|
||||
env->NewStringUTF(reason)));
|
||||
}
|
||||
|
||||
void Master::Context::throwThrowable(jthrowable throwable) {
|
||||
env->Throw(throwable);
|
||||
}
|
||||
|
||||
jobject Master::Context::newTraffic(jlong upload, jlong download) {
|
||||
return env->NewObject(master->cTraffic, master->cTrafficConstructor, upload, download);
|
||||
}
|
||||
|
||||
jobject Master::Context::newGeneral(char const *mode, jint http, jint socks, jint redirect,
|
||||
jint mixed) {
|
||||
return env->NewObject(master->cGeneral, master->cGeneralConstructor,
|
||||
env->NewStringUTF(mode), http, socks, redirect, mixed);
|
||||
}
|
||||
|
||||
jobject Master::Context::newCompletableFuture() {
|
||||
return env->NewObject(master->cCompletableFuture, master->cCompletableFutureConstructor);
|
||||
}
|
||||
|
||||
jobject Master::Context::newGlobalReference(jobject obj) {
|
||||
return env->NewGlobalRef(obj);
|
||||
}
|
||||
|
||||
jobject Master::Context::removeGlobalReference(jobject obj) {
|
||||
env->DeleteGlobalRef(obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool Master::Context::completeCompletableFuture(jobject completable, jobject object) {
|
||||
return env->CallBooleanMethod(completable, master->mCompletableFutureComplete, object);
|
||||
}
|
||||
|
||||
bool
|
||||
Master::Context::completeExceptionallyCompletableFuture(jobject completable, jthrowable throwable) {
|
||||
return env->CallBooleanMethod(completable, master->mCompletableFutureCompleteExceptionally,
|
||||
throwable);
|
||||
}
|
||||
|
||||
const_buffer_t Master::Context::createConstBufferFromByteArray(jbyteArray array) {
|
||||
return {
|
||||
.buffer = env->GetByteArrayElements(array, nullptr),
|
||||
.length = env->GetArrayLength(array)
|
||||
};
|
||||
}
|
||||
|
||||
void Master::Context::releaseConstBufferFromByteArray(jbyteArray array, const_buffer_t &buffer) {
|
||||
env->ReleaseByteArrayElements(array, const_cast<jbyte*>(reinterpret_cast<const jbyte*>(buffer.buffer)), JNI_ABORT);
|
||||
}
|
||||
|
||||
const char *Master::Context::getString(jstring str) {
|
||||
return env->GetStringUTFChars(str, nullptr);
|
||||
}
|
||||
|
||||
void Master::Context::releaseString(jstring str, const char *c) {
|
||||
env->ReleaseStringUTFChars(str, c);
|
||||
}
|
||||
|
||||
void Master::Context::tunCallbackNewSocket(jobject callback, jint fd) {
|
||||
env->CallVoidMethod(callback, master->mTunCallbackOnNewSocket, fd);
|
||||
}
|
||||
|
||||
void Master::Context::tunCallbackStop(jobject callback) {
|
||||
env->CallVoidMethod(callback, master->mTunCallbackOnStop);
|
||||
}
|
||||
|
||||
jobjectArray Master::Context::createProxyGroupArray(int size, jobject elements[]) {
|
||||
jobjectArray result = env->NewObjectArray(size, master->cProxyGroup, nullptr);
|
||||
|
||||
for ( int i = 0 ; i < size ; i++ )
|
||||
env->SetObjectArrayElement(result, i, elements[i]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
jobjectArray Master::Context::createProxyArray(int size, jobject elements[]) {
|
||||
jobjectArray result = env->NewObjectArray(size, master->cProxy, nullptr);
|
||||
|
||||
for ( int i = 0 ; i < size ; i++ )
|
||||
env->SetObjectArrayElement(result, i, elements[i]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
jobject Master::Context::createProxy(char const *name, proxy_type_t type, jlong delay) {
|
||||
jstring ts = nullptr;
|
||||
|
||||
switch (type) {
|
||||
case Direct:
|
||||
ts = master->sDirect;
|
||||
break;
|
||||
case Reject:
|
||||
ts = master->sReject;
|
||||
break;
|
||||
case Socks5:
|
||||
ts = master->sSocks5;
|
||||
break;
|
||||
case Http:
|
||||
ts = master->sHttp;
|
||||
break;
|
||||
case Shadowsocks:
|
||||
ts = master->sShadowsocks;
|
||||
break;
|
||||
case Vmess:
|
||||
ts = master->sVmess;
|
||||
break;
|
||||
case Snell:
|
||||
ts = master->sSnell;
|
||||
break;
|
||||
case Trojan:
|
||||
ts = master->sTrojan;
|
||||
break;
|
||||
case Selector:
|
||||
ts = master->sSelector;
|
||||
break;
|
||||
case Fallback:
|
||||
ts = master->sFallback;
|
||||
break;
|
||||
case LoadBalance:
|
||||
ts = master->sLoadBalance;
|
||||
break;
|
||||
case URLTest:
|
||||
ts = master->sURLTest;
|
||||
break;
|
||||
case Relay:
|
||||
ts = master->sRelay;
|
||||
break;
|
||||
case Unknown:
|
||||
ts = master->sUnknown;
|
||||
break;
|
||||
default:
|
||||
ts = master->sUnknown;
|
||||
}
|
||||
|
||||
return env->NewObject(master->cProxy, master->cProxyConstructor, env->NewStringUTF(name), ts, delay);
|
||||
}
|
||||
|
||||
jobject Master::Context::createProxyGroup(char const *name, proxy_type_t type,
|
||||
char const *current, jobjectArray proxies) {
|
||||
jstring ts = nullptr;
|
||||
|
||||
switch (type) {
|
||||
case Selector:
|
||||
ts = master->sSelector;
|
||||
break;
|
||||
case Fallback:
|
||||
ts = master->sFallback;
|
||||
break;
|
||||
case LoadBalance:
|
||||
ts = master->sLoadBalance;
|
||||
break;
|
||||
case URLTest:
|
||||
ts = master->sURLTest;
|
||||
break;
|
||||
case Relay:
|
||||
ts = master->sRelay;
|
||||
break;
|
||||
case Unknown:
|
||||
ts = master->sUnknown;
|
||||
break;
|
||||
default:
|
||||
ts = master->sUnknown;
|
||||
}
|
||||
|
||||
return env->NewObject(master->cProxyGroup, master->cProxyGroupConstructor, env->NewStringUTF(name), ts, env->NewStringUTF(current), proxies);
|
||||
}
|
||||
|
||||
void Master::Context::logCallbackMessage(jobject callback, const char *data) {
|
||||
jobject event = env->NewObject(master->cLogEvent, master->cLogEventConstructor, env->NewStringUTF(data));
|
||||
|
||||
env->CallVoidMethod(callback, master->mLogCallbackOnMessage, event);
|
||||
}
|
||||
|
||||
static void enqueue_event(const event_t *e) {
|
||||
EventQueue::getInstance()->enqueueEvent(e);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
|
||||
UNUSED(unused);
|
||||
|
||||
JNIEnv *env = nullptr;
|
||||
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
|
||||
return -1;
|
||||
|
||||
new Master(vm, env);
|
||||
new EventQueue();
|
||||
|
||||
set_event_handler(&enqueue_event);
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
129
core/src/main/cpp/main.h
Normal file
129
core/src/main/cpp/main.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
|
||||
#include "libclash.h"
|
||||
#include "event_queue.h"
|
||||
|
||||
#define UNUSED(v) ((void)v)
|
||||
|
||||
class Master {
|
||||
public:
|
||||
class Context;
|
||||
|
||||
public:
|
||||
Master(JavaVM *vm, JNIEnv *env);
|
||||
|
||||
public:
|
||||
template <class R> static R runWithContext(JNIEnv *env, const std::function<R (Context *)>& func);
|
||||
template <class R> static R runWithAttached(const std::function<R (JNIEnv *)> &func);
|
||||
|
||||
private:
|
||||
jclass cClashException;
|
||||
jclass cTraffic;
|
||||
jclass cGeneral;
|
||||
jclass cCompletableFuture;
|
||||
jclass cProxyGroup;
|
||||
jclass cProxy;
|
||||
jclass cLogEvent;
|
||||
jclass iTunCallback;
|
||||
jclass iLogCallback;
|
||||
jmethodID cClashExceptionConstructor;
|
||||
jmethodID cTrafficConstructor;
|
||||
jmethodID cGeneralConstructor;
|
||||
jmethodID cCompletableFutureConstructor;
|
||||
jmethodID cProxyGroupConstructor;
|
||||
jmethodID cProxyConstructor;
|
||||
jmethodID cLogEventConstructor;
|
||||
jmethodID mCompletableFutureComplete;
|
||||
jmethodID mCompletableFutureCompleteExceptionally;
|
||||
jmethodID mTunCallbackOnNewSocket;
|
||||
jmethodID mTunCallbackOnStop;
|
||||
jmethodID mLogCallbackOnMessage;
|
||||
|
||||
private:
|
||||
jstring sDirect;
|
||||
jstring sReject;
|
||||
jstring sShadowsocks;
|
||||
jstring sSnell;
|
||||
jstring sSocks5;
|
||||
jstring sHttp;
|
||||
jstring sVmess;
|
||||
jstring sTrojan;
|
||||
jstring sRelay;
|
||||
jstring sSelector;
|
||||
jstring sFallback;
|
||||
jstring sURLTest;
|
||||
jstring sLoadBalance;
|
||||
jstring sUnknown;
|
||||
|
||||
private:
|
||||
JavaVM *vm;
|
||||
|
||||
private:
|
||||
static Master *master;
|
||||
|
||||
private:
|
||||
friend class Context;
|
||||
};
|
||||
|
||||
class Master::Context {
|
||||
public:
|
||||
public:
|
||||
Context(JNIEnv *env);
|
||||
|
||||
public:
|
||||
jthrowable newClashException(const char *message);
|
||||
jobject newTraffic(jlong upload, jlong download);
|
||||
jobject newGeneral(char const *mode, jint http, jint socks, jint redirect, jint mixed);
|
||||
jobject newCompletableFuture();
|
||||
|
||||
public:
|
||||
void throwThrowable(jthrowable throwable);
|
||||
|
||||
public:
|
||||
jobject newGlobalReference(jobject obj);
|
||||
jobject removeGlobalReference(jobject obj);
|
||||
|
||||
public:
|
||||
bool completeCompletableFuture(jobject completable, jobject object);
|
||||
bool completeExceptionallyCompletableFuture(jobject completable, jthrowable throwable);
|
||||
void tunCallbackNewSocket(jobject callback, jint fd);
|
||||
void tunCallbackStop(jobject callback);
|
||||
void logCallbackMessage(jobject callback, const char *data);
|
||||
|
||||
public:
|
||||
jobject createProxy(char const *name, proxy_type_t type, jlong delay);
|
||||
jobject createProxyGroup(char const *name, proxy_type_t type, char const *current, jobjectArray proxies);
|
||||
jobjectArray createProxyArray(int size, jobject elements[]);
|
||||
jobjectArray createProxyGroupArray(int size, jobject elements[]);
|
||||
const_buffer_t createConstBufferFromByteArray(jbyteArray array);
|
||||
void releaseConstBufferFromByteArray(jbyteArray array, const_buffer_t &buffer);
|
||||
const char *getString(jstring str);
|
||||
void releaseString(jstring str, const char *c);
|
||||
|
||||
private:
|
||||
JNIEnv *env;
|
||||
};
|
||||
|
||||
template <class R>
|
||||
R Master::runWithContext(JNIEnv *env, const std::function<R (Master::Context *)>& func) {
|
||||
Master::Context context(env);
|
||||
|
||||
return func(&context);
|
||||
}
|
||||
|
||||
template <class R> R Master::runWithAttached(const std::function<R (JNIEnv *)> &func) {
|
||||
Master *m = Master::master;
|
||||
|
||||
JNIEnv *env;
|
||||
|
||||
m->vm->AttachCurrentThread(&env, nullptr);
|
||||
|
||||
R result = func(env);
|
||||
|
||||
m->vm->DetachCurrentThread();
|
||||
|
||||
return result;
|
||||
}
|
||||
70
core/src/main/cpp/patch.cpp
Normal file
70
core/src/main/cpp/patch.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "main.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_setProxyMode(JNIEnv *env, jclass clazz,
|
||||
jstring proxy_mode) {
|
||||
Master::runWithContext<void>(env, [&](Master::Context *context) {
|
||||
const char *m = context->getString(proxy_mode);
|
||||
int mode;
|
||||
|
||||
if ( strcmp(m, "Direct") == 0 )
|
||||
mode = MODE_DIRECT;
|
||||
else if ( strcmp(m, "Global") == 0 )
|
||||
mode = MODE_GLOBAL;
|
||||
else if ( strcmp(m, "Rule") == 0 )
|
||||
mode = MODE_RULE;
|
||||
else if ( strcmp(m, "Script") == 0 )
|
||||
mode = MODE_SCRIPT;
|
||||
else
|
||||
mode = MODE_UNKNOWN;
|
||||
|
||||
setProxyMode(mode);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_setDnsOverride(JNIEnv *env, jclass clazz,
|
||||
jboolean override_dns,
|
||||
jstring append_dns) {
|
||||
Master::runWithContext<void>(env, [&](Master::Context *context) {
|
||||
const char *appendDns = context->getString(append_dns);
|
||||
int override = 1;
|
||||
|
||||
if ( override_dns )
|
||||
override = 1;
|
||||
else
|
||||
override = 0;
|
||||
|
||||
dns_override_t dns = {
|
||||
.override_dns = override,
|
||||
.append_dns = appendDns
|
||||
};
|
||||
|
||||
setDnsOverride(&dns);
|
||||
|
||||
context->releaseString(append_dns, appendDns);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_setSelector(JNIEnv *env, jclass clazz, jstring group,
|
||||
jstring selected) {
|
||||
UNUSED(clazz);
|
||||
|
||||
return Master::runWithContext<bool>(env, [&](Master::Context *context) -> bool {
|
||||
const char *g = context->getString(group);
|
||||
const char *s = context->getString(selected);
|
||||
|
||||
int r = setSelector(g, s);
|
||||
|
||||
context->releaseString(group, g);
|
||||
context->releaseString(selected, s);
|
||||
|
||||
return r == 0;
|
||||
});
|
||||
}
|
||||
109
core/src/main/cpp/query.cpp
Normal file
109
core/src/main/cpp/query.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "main.h"
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_queryGeneral(JNIEnv *env, jclass clazz) {
|
||||
UNUSED(clazz);
|
||||
|
||||
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
|
||||
general_t general;
|
||||
|
||||
queryGeneral(&general);
|
||||
|
||||
const char *mode = nullptr;
|
||||
|
||||
switch (general.mode) {
|
||||
case MODE_DIRECT:
|
||||
mode = "Direct";
|
||||
break;
|
||||
case MODE_GLOBAL:
|
||||
mode = "Global";
|
||||
break;
|
||||
case MODE_RULE:
|
||||
mode = "Rule";
|
||||
break;
|
||||
case MODE_SCRIPT:
|
||||
mode = "Script";
|
||||
break;
|
||||
}
|
||||
|
||||
return context->newGeneral(mode,
|
||||
general.http_port, general.socks_port,
|
||||
general.redirect_port, general.mixed_port);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_queryBandwidth(JNIEnv *env, jclass clazz) {
|
||||
UNUSED(clazz);
|
||||
|
||||
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
|
||||
traffic_t traffic;
|
||||
|
||||
queryBandwidth(&traffic);
|
||||
|
||||
return context->newTraffic(traffic.upload, traffic.download);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_querySpeed(JNIEnv *env, jclass clazz) {
|
||||
UNUSED(clazz);
|
||||
|
||||
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
|
||||
traffic_t traffic;
|
||||
|
||||
querySpeed(&traffic);
|
||||
|
||||
return context->newTraffic(traffic.upload, traffic.download);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_queryProxyGroups(JNIEnv *env, jclass clazz) {
|
||||
UNUSED(clazz);
|
||||
|
||||
return Master::runWithContext<jobjectArray>(env, [&](Master::Context *context) -> jobjectArray {
|
||||
proxy_group_list_t *list = queryProxyGroups();
|
||||
auto *jgroups = new jobject[list->size];
|
||||
|
||||
for (int group_index = 0 ; group_index < list->size ; group_index++ ) {
|
||||
char const * now = "";
|
||||
proxy_group_t *group = list->groups[group_index];
|
||||
auto *jproxies = new jobject[group->proxies_size];
|
||||
const char *group_name = &list->string_pool[group->base.name_index];
|
||||
|
||||
for ( int proxy_index = 0 ; proxy_index < group->proxies_size ; proxy_index++ ) {
|
||||
proxy_t *proxy = &group->proxies[proxy_index];
|
||||
const char *name = &list->string_pool[proxy->name_index];
|
||||
|
||||
jproxies[proxy_index] = context->createProxy(name, proxy->proxy_type, proxy->delay);
|
||||
|
||||
if ( proxy_index == group->now )
|
||||
now = name;
|
||||
}
|
||||
|
||||
jgroups[group_index] = context->createProxyGroup(
|
||||
group_name,
|
||||
group->base.proxy_type,
|
||||
now,
|
||||
context->createProxyArray(group->proxies_size, jproxies));
|
||||
|
||||
delete[] jproxies;
|
||||
|
||||
free(group);
|
||||
}
|
||||
|
||||
jobjectArray result = context->createProxyGroupArray(list->size, jgroups);
|
||||
|
||||
delete[] jgroups;
|
||||
|
||||
free(list->string_pool);
|
||||
free(list);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
65
core/src/main/cpp/tun.cpp
Normal file
65
core/src/main/cpp/tun.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "main.h"
|
||||
|
||||
#include "event_queue.h"
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_startTunDevice(JNIEnv *env, jclass clazz, jint fd,
|
||||
jint mtu, jstring gateway,
|
||||
jstring mirror, jstring dns,
|
||||
jobject callback) {
|
||||
UNUSED(clazz);
|
||||
|
||||
Master::runWithContext<void>(env, [&](Master::Context *context) {
|
||||
const char *gatewayString = context->getString(gateway);
|
||||
const char *mirrorString = context->getString(mirror);
|
||||
const char *dnsString = context->getString(dns);
|
||||
|
||||
jobject callbackGlobal = context->newGlobalReference(callback);
|
||||
uint64_t token = EventQueue::getInstance()->obtainToken();
|
||||
|
||||
EventQueue::getInstance()->registerHandler(NEW_SOCKET, token, [callbackGlobal](const event_t *e) {
|
||||
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
|
||||
Master::runWithContext<void>(env, [&](Master::Context *context) {
|
||||
context->tunCallbackNewSocket(callbackGlobal, static_cast<int>(strtol(e->payload, nullptr, 10)));
|
||||
});
|
||||
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
EventQueue::getInstance()->registerHandler(TUN_STOP, token, [callbackGlobal](const event_t *e) {
|
||||
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
|
||||
Master::runWithContext<void>(env, [&](Master::Context *context) {
|
||||
context->tunCallbackStop(callbackGlobal);
|
||||
context->removeGlobalReference(callbackGlobal);
|
||||
});
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
auto queue = EventQueue::getInstance();
|
||||
|
||||
queue->unregisterHandler(NEW_SOCKET, e->token);
|
||||
queue->unregisterHandler(TUN_STOP, e->token);
|
||||
});
|
||||
|
||||
char *exception = startTunDevice(fd, mtu, gatewayString, mirrorString, dnsString, token);
|
||||
|
||||
context->releaseString(gateway, gatewayString);
|
||||
context->releaseString(mirror, mirrorString);
|
||||
context->releaseString(dns, dnsString);
|
||||
|
||||
if ( exception != nullptr ) {
|
||||
context->throwThrowable(context->newClashException(exception));
|
||||
context->removeGlobalReference(callbackGlobal);
|
||||
free(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_stopTunDevice(JNIEnv *env, jclass clazz) {
|
||||
stopTunDevice();
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package bridge
|
||||
|
||||
type DoneCallback interface {
|
||||
Done()
|
||||
DoneWithError(error)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type TunnelGeneral struct {
|
||||
Mode string
|
||||
HTTPPort int
|
||||
SocksPort int
|
||||
RedirectPort int
|
||||
}
|
||||
|
||||
func QueryGeneral() *TunnelGeneral {
|
||||
result := &TunnelGeneral{}
|
||||
|
||||
g := executor.GetGeneral()
|
||||
m := tunnel.Mode()
|
||||
|
||||
result.Mode = m.String()
|
||||
result.HTTPPort = g.Port
|
||||
result.SocksPort = g.SocksPort
|
||||
result.RedirectPort = g.RedirPort
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func SetProxyMode(mode string) {
|
||||
switch mode {
|
||||
case "Direct":
|
||||
tunnel.SetMode(tunnel.Direct)
|
||||
case "Global":
|
||||
tunnel.SetMode(tunnel.Global)
|
||||
case "Rule":
|
||||
tunnel.SetMode(tunnel.Rule)
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/kr328/cfa/config"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
logCallback LogCallback
|
||||
logSubscribe sync.Once
|
||||
)
|
||||
|
||||
type LogCallback interface {
|
||||
OnLogEvent(level, payload string)
|
||||
}
|
||||
|
||||
func InitCore(geoipDatabase[] byte, homeDir string, version string) {
|
||||
dataClone := make([]byte, len(geoipDatabase))
|
||||
copy(dataClone, geoipDatabase)
|
||||
|
||||
mmdb.LoadFromBytes(dataClone)
|
||||
C.SetHomeDir(homeDir)
|
||||
config.ApplicationVersion = version
|
||||
|
||||
Reset()
|
||||
|
||||
log.Infoln("Initialed")
|
||||
}
|
||||
|
||||
func Reset() {
|
||||
config.LoadDefault()
|
||||
tunnel.DefaultManager.ResetStatistic()
|
||||
}
|
||||
|
||||
func SetLogCallback(callback LogCallback) {
|
||||
logSubscribe.Do(func() {
|
||||
go func() {
|
||||
sub := log.Subscribe()
|
||||
defer log.UnSubscribe(sub)
|
||||
|
||||
for {
|
||||
elm := <-sub
|
||||
l := elm.(*log.Event)
|
||||
|
||||
if l.LogLevel < log.Level() {
|
||||
continue
|
||||
}
|
||||
|
||||
if cb := logCallback; cb != nil {
|
||||
cb.OnLogEvent(l.LogLevel.String(), l.Payload)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
logCallback = callback
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/kr328/cfa/config"
|
||||
)
|
||||
|
||||
func ResetDnsAppend(dns string) {
|
||||
if len(dns) == 0 {
|
||||
config.NameServersAppend = make([]string, 0)
|
||||
} else {
|
||||
config.NameServersAppend = strings.Split(dns, ",")
|
||||
}
|
||||
}
|
||||
|
||||
func SetDnsOverrideEnabled(enabled bool) {
|
||||
if enabled {
|
||||
config.DnsPatch = config.OptionalDnsPatch
|
||||
} else {
|
||||
config.DnsPatch = nil
|
||||
}
|
||||
}
|
||||
|
||||
func LoadProfileFile(path, baseDir string, callback DoneCallback) {
|
||||
go func() {
|
||||
call(config.LoadFromFile(path, baseDir), callback)
|
||||
}()
|
||||
}
|
||||
|
||||
func DownloadProfileAndCheck(url, output, baseDir string, callback DoneCallback) {
|
||||
go func() {
|
||||
call(config.PullRemote(url, output, baseDir), callback)
|
||||
}()
|
||||
}
|
||||
|
||||
func ReadProfileAndCheck(fd int, output, baseDir string, callback DoneCallback) {
|
||||
go func() {
|
||||
call(config.PullLocal(fd, output, baseDir), callback)
|
||||
}()
|
||||
}
|
||||
|
||||
func call(err error, callback DoneCallback) {
|
||||
if err != nil {
|
||||
callback.DoneWithError(err)
|
||||
} else {
|
||||
callback.Done()
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type ProxyItem struct {
|
||||
Name string
|
||||
Type string
|
||||
Delay int
|
||||
}
|
||||
|
||||
type ProxyGroupItem struct {
|
||||
Name string
|
||||
Type string
|
||||
Current string
|
||||
Delay int
|
||||
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
type ProxyGroupCollection interface {
|
||||
Add(proxy *ProxyGroupItem) bool
|
||||
}
|
||||
|
||||
type ProxyCollection interface {
|
||||
Add(proxy *ProxyItem) bool
|
||||
}
|
||||
|
||||
func (p *ProxyGroupItem) QueryAllProxies(collection ProxyCollection) {
|
||||
for _, v := range p.providers {
|
||||
for _, p := range v.Proxies() {
|
||||
collection.Add(
|
||||
&ProxyItem{
|
||||
Name: p.Name(),
|
||||
Type: p.Type().String(),
|
||||
Delay: int(p.LastDelay()),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StartUrlTest(group string, callback DoneCallback) {
|
||||
go func() {
|
||||
defer callback.Done()
|
||||
|
||||
p := tunnel.Proxies()[group]
|
||||
|
||||
pi, ok := p.(*outbound.Proxy)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
group, ok := pi.ProxyAdapter.(outboundgroup.ProxyGroup)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
providers := group.GetProxyProviders()
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(providers))
|
||||
|
||||
for _, v := range providers {
|
||||
go func(p provider.ProxyProvider) {
|
||||
p.HealthCheck()
|
||||
wg.Done()
|
||||
}(v)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
}
|
||||
|
||||
func QueryAllProxyGroups(collection ProxyGroupCollection) {
|
||||
ps := tunnel.Proxies()
|
||||
|
||||
for _, p := range ps {
|
||||
pi, ok := p.(*outbound.Proxy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
group, ok := pi.ProxyAdapter.(outboundgroup.ProxyGroup)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
collection.Add(&ProxyGroupItem{
|
||||
Name: group.Name(),
|
||||
Type: group.Type().String(),
|
||||
Current: group.Now(),
|
||||
Delay: 0,
|
||||
providers: group.GetProxyProviders(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func SetSelectedProxy(name, proxy string) bool {
|
||||
p := tunnel.Proxies()[name]
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
pb, ok := p.(*outbound.Proxy)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
selector, ok := pb.ProxyAdapter.(*outboundgroup.Selector)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := selector.Set(proxy); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
log.Infoln("Set " + name + " -> " + proxy)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type EventPoll struct {
|
||||
stop sync.Once
|
||||
|
||||
onStop func()
|
||||
}
|
||||
|
||||
func (e *EventPoll) Stop() {
|
||||
e.stop.Do(func() {
|
||||
e.onStop()
|
||||
})
|
||||
}
|
||||
|
||||
type Traffic struct {
|
||||
Download int64
|
||||
Upload int64
|
||||
}
|
||||
|
||||
type Logs interface {
|
||||
OnEvent(level, payload string)
|
||||
}
|
||||
|
||||
func QueryBandwidth() *Traffic {
|
||||
upload := tunnel.DefaultManager.UploadTotal()
|
||||
download := tunnel.DefaultManager.DownloadTotal()
|
||||
|
||||
return &Traffic{
|
||||
Upload: upload,
|
||||
Download: download,
|
||||
}
|
||||
}
|
||||
|
||||
func QueryTraffic() *Traffic {
|
||||
up, down := tunnel.DefaultManager.Now()
|
||||
|
||||
return &Traffic{
|
||||
Upload: up,
|
||||
Download: down,
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/kr328/cfa/tun"
|
||||
)
|
||||
|
||||
type TunCallback interface {
|
||||
OnCreateSocket(fd int)
|
||||
OnStop()
|
||||
}
|
||||
|
||||
var callback TunCallback
|
||||
|
||||
func init() {
|
||||
dialer.DialerHook = onNewDialer
|
||||
dialer.ListenConfigHook = onNewListenConfig
|
||||
}
|
||||
|
||||
func onNewDialer(dialer *net.Dialer) {
|
||||
dialer.Control = onNewSocket
|
||||
}
|
||||
|
||||
func onNewListenConfig(listen *net.ListenConfig) {
|
||||
listen.Control = onNewSocket
|
||||
}
|
||||
|
||||
func onNewSocket(_, _ string, c syscall.RawConn) error {
|
||||
if cb := callback; cb != nil {
|
||||
_ = c.Control(func(fd uintptr) {
|
||||
cb.OnCreateSocket(int(fd))
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartTunDevice(fd, mtu int, gateway, mirror, dns string, cb TunCallback) error {
|
||||
callback = cb
|
||||
|
||||
return tun.StartTunDevice(fd, mtu, gateway, mirror, dns)
|
||||
}
|
||||
|
||||
func StopTunDevice() {
|
||||
tun.StopTunDevice()
|
||||
|
||||
if c := callback; c != nil {
|
||||
c.OnStop()
|
||||
}
|
||||
|
||||
callback = nil
|
||||
}
|
||||
23
core/src/main/golang/buffer.h
Normal file
23
core/src/main/golang/buffer.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct buffer_t {
|
||||
void *buffer;
|
||||
int length;
|
||||
} buffer_t;
|
||||
|
||||
typedef struct const_buffer_t {
|
||||
const void *buffer;
|
||||
int length;
|
||||
} const_buffer_t;
|
||||
|
||||
typedef const char *const_string_t;
|
||||
|
||||
#if __cplusplus
|
||||
};
|
||||
#endif
|
||||
Submodule core/src/main/golang/clash updated: f48ce6fc8e...4c8a71ecee
28
core/src/main/golang/config.go
Normal file
28
core/src/main/golang/config.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
//#include "config.h"
|
||||
//#include "buffer.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/kr328/cfa/config"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//export setDnsOverride
|
||||
func setDnsOverride(override *C.dns_override_t) {
|
||||
overrideDns := override.override_dns != 0
|
||||
appendDns := C.GoString(override.append_dns)
|
||||
|
||||
if overrideDns {
|
||||
config.DnsPatch = config.OptionalDnsPatch
|
||||
} else {
|
||||
config.DnsPatch = nil
|
||||
}
|
||||
|
||||
if len(appendDns) == 0 {
|
||||
config.NameServersAppend = make([]string, 0)
|
||||
} else {
|
||||
config.NameServersAppend = strings.Split(appendDns, ",")
|
||||
}
|
||||
}
|
||||
14
core/src/main/golang/config.h
Normal file
14
core/src/main/golang/config.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct dns_override_t {
|
||||
int override_dns;
|
||||
const char *append_dns;
|
||||
} dns_override_t;
|
||||
|
||||
#if __cplusplus
|
||||
};
|
||||
#endif
|
||||
15
core/src/main/golang/config/defaults.go
Normal file
15
core/src/main/golang/config/defaults.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package config
|
||||
|
||||
var defaultFakeIPFilter = []string{
|
||||
// stun services
|
||||
"+.stun.*.*",
|
||||
"+.stun.*.*.*",
|
||||
"+.stun.*.*.*.*",
|
||||
|
||||
// Google Voices
|
||||
"lens.l.google.com",
|
||||
"stun.l.google.com",
|
||||
|
||||
// Nintendo Switch
|
||||
"*.n.n.srv.nintendo.net",
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
@@ -29,20 +30,29 @@ var client = &http.Client{
|
||||
|
||||
client, server := net.Pipe()
|
||||
|
||||
tunnel.Add(inbound.NewSocket(socks5.ParseAddr(address), server, constant.HTTP, constant.TCP))
|
||||
tunnel.Add(inbound.NewSocket(socks5.ParseAddr(address), server, constant.HTTP))
|
||||
|
||||
return client, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func fetchRemote(url string) ([]byte, error) {
|
||||
request, err := http.NewRequest("GET", url, nil)
|
||||
func fetchRemote(sUrl string) ([]byte, error) {
|
||||
uri, err := url.Parse(sUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request, err := http.NewRequest("GET", uri.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("User-Agent", "ClashForAndroid/"+ApplicationVersion)
|
||||
if user := uri.User; user != nil {
|
||||
password, _ := user.Password()
|
||||
request.SetBasicAuth(user.Username(), password)
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
@@ -68,7 +78,7 @@ func fetchLocal(fd int) ([]byte, error) {
|
||||
return ioutil.ReadAll(file)
|
||||
}
|
||||
|
||||
func PullRemote(url, output, baseDir string) error {
|
||||
func DownloadUrl(url, output, baseDir string) error {
|
||||
data, err := fetchRemote(url)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -77,7 +87,7 @@ func PullRemote(url, output, baseDir string) error {
|
||||
return save(data, output, baseDir)
|
||||
}
|
||||
|
||||
func PullLocal(fd int, output, baseDir string) error {
|
||||
func DownloadFd(fd int, output, baseDir string) error {
|
||||
data, err := fetchLocal(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -87,14 +97,10 @@ func PullLocal(fd int, output, baseDir string) error {
|
||||
}
|
||||
|
||||
func save(data []byte, output, baseDir string) error {
|
||||
cfg, err := parseConfig(data, baseDir)
|
||||
_, err := parseConfig(data, baseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range cfg.Providers {
|
||||
_ = v.Destroy()
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(output, data, defaultFileMode)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func init() {
|
||||
Listen: ":0",
|
||||
EnhancedMode: dns.FAKEIP,
|
||||
FakeIPRange: "198.18.0.0/16",
|
||||
FakeIPFilter: []string{},
|
||||
FakeIPFilter: defaultFakeIPFilter,
|
||||
DefaultNameserver: defaultNameServers,
|
||||
}
|
||||
}
|
||||
|
||||
112
core/src/main/golang/defer.go
Normal file
112
core/src/main/golang/defer.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/kr328/cfa/config"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//#include "buffer.h"
|
||||
//#include "event.h"
|
||||
import "C"
|
||||
|
||||
//export downloadProfileFromFd
|
||||
func downloadProfileFromFd(fd int, base C.const_string_t, output C.const_string_t, callbackId uint64) {
|
||||
b := C.GoString(base)
|
||||
o := C.GoString(output)
|
||||
|
||||
go func() {
|
||||
err := config.DownloadFd(fd, o, b)
|
||||
if err != nil {
|
||||
sendEvent(C.COMPLETE, callbackId, err.Error())
|
||||
} else {
|
||||
sendEvent(C.COMPLETE, callbackId, "")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//export downloadProfileFromUrl
|
||||
func downloadProfileFromUrl(url C.const_string_t, base C.const_string_t, output C.const_string_t, callbackId uint64) {
|
||||
u := C.GoString(url)
|
||||
b := C.GoString(base)
|
||||
o := C.GoString(output)
|
||||
|
||||
go func() {
|
||||
err := config.DownloadUrl(u, o, b)
|
||||
if err != nil {
|
||||
sendEvent(C.COMPLETE, callbackId, err.Error())
|
||||
} else {
|
||||
sendEvent(C.COMPLETE, callbackId, "")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//export loadProfile
|
||||
func loadProfile(path C.const_string_t, base C.const_string_t, callbackId uint64) {
|
||||
p := C.GoString(path)
|
||||
b := C.GoString(base)
|
||||
|
||||
go func() {
|
||||
err := config.LoadFromFile(p, b)
|
||||
if err != nil {
|
||||
sendEvent(C.COMPLETE, callbackId, err.Error())
|
||||
} else {
|
||||
sendEvent(C.COMPLETE, callbackId, "")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//export performHealthCheck
|
||||
func performHealthCheck(group C.const_string_t, callbackId uint64) {
|
||||
g := C.GoString(group)
|
||||
|
||||
go func() {
|
||||
p := tunnel.Proxies()[g]
|
||||
if p == nil {
|
||||
sendEvent(C.COMPLETE, callbackId, "No such proxy group")
|
||||
|
||||
log.Warnln("Perform health check failure: %s not found", g)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
pw, ok := p.(*outbound.Proxy)
|
||||
if !ok {
|
||||
sendEvent(C.COMPLETE, callbackId, "Invalid group")
|
||||
|
||||
log.Warnln("Perform health check failure: %s not valid group", g)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
adapter, ok := pw.ProxyAdapter.(outboundgroup.ProxyGroup)
|
||||
if !ok {
|
||||
sendEvent(C.COMPLETE, callbackId, "Invalid group")
|
||||
|
||||
log.Warnln("Perform health check failure: %s not valid group", g)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
providers := adapter.GetProxyProviders()
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
wg.Add(len(providers))
|
||||
|
||||
for _, p := range providers {
|
||||
go func(p provider.ProxyProvider) {
|
||||
p.HealthCheck()
|
||||
|
||||
wg.Done()
|
||||
}(p)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
sendEvent(C.COMPLETE, callbackId, "")
|
||||
}()
|
||||
}
|
||||
29
core/src/main/golang/event.c
Normal file
29
core/src/main/golang/event.c
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "event.h"
|
||||
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
|
||||
extern void answerEvent(int64_t id);
|
||||
|
||||
static event_handler_t event_handler;
|
||||
|
||||
void set_event_handler(event_handler_t handler) {
|
||||
event_handler = handler;
|
||||
}
|
||||
|
||||
void send_event(event_t *event, const void *payload, size_t payload_length) {
|
||||
event_handler_t h = event_handler;
|
||||
if ( h != NULL ) {
|
||||
memcpy(event->payload, payload, payload_length);
|
||||
h(event);
|
||||
}
|
||||
else {
|
||||
answer_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
void answer_event(const event_t *event) {
|
||||
answerEvent(event->id);
|
||||
|
||||
free((void*)event);
|
||||
}
|
||||
56
core/src/main/golang/event.go
Normal file
56
core/src/main/golang/event.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
//#include "event.h"
|
||||
import "C"
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type EventWaiter struct {
|
||||
result chan struct{}
|
||||
}
|
||||
|
||||
var idLock = sync.Mutex{}
|
||||
var currentId = int64(0)
|
||||
var ids = map[int64]*EventWaiter{}
|
||||
|
||||
//export answerEvent
|
||||
func answerEvent(id int64) {
|
||||
idLock.Lock()
|
||||
defer idLock.Unlock()
|
||||
|
||||
waiter, ok := ids[id]
|
||||
if ok {
|
||||
close(waiter.result)
|
||||
delete(ids, id)
|
||||
}
|
||||
}
|
||||
|
||||
func sendEvent(t C.event_type_t, token uint64, payload string) *EventWaiter {
|
||||
idLock.Lock()
|
||||
defer idLock.Unlock()
|
||||
|
||||
currentId++
|
||||
|
||||
id := currentId
|
||||
r := &EventWaiter{make(chan struct{})}
|
||||
|
||||
ids[id] = r
|
||||
|
||||
p := append([]byte(payload), 0)
|
||||
e := allocCEvent(len(p))
|
||||
|
||||
e.id = C.int64_t(id)
|
||||
e._type = t
|
||||
e.token = C.int64_t(token)
|
||||
|
||||
C.send_event(e, unsafe.Pointer(&p[0]), C.size_t(len(p)))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func allocCEvent(payloadLength int) *C.event_t {
|
||||
return (*C.event_t)(C.malloc(C.sizeof_event_t + C.size_t(payloadLength)))
|
||||
}
|
||||
|
||||
29
core/src/main/golang/event.h
Normal file
29
core/src/main/golang/event.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
NEW_SOCKET, TUN_STOP, COMPLETE, LOG_RECEIVED
|
||||
} event_type_t;
|
||||
|
||||
typedef struct event_t {
|
||||
int64_t id;
|
||||
int64_t token;
|
||||
event_type_t type;
|
||||
char payload[];
|
||||
} event_t;
|
||||
|
||||
typedef void (*event_handler_t)(const event_t *event);
|
||||
|
||||
void set_event_handler(event_handler_t handler);
|
||||
void send_event(event_t *event, const void *payload, size_t payload_length);
|
||||
void answer_event(const event_t *event);
|
||||
|
||||
#if __cplusplus
|
||||
};
|
||||
#endif
|
||||
42
core/src/main/golang/general.go
Normal file
42
core/src/main/golang/general.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
//#include "buffer.h"
|
||||
//#include "general.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/proxy"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
//export setProxyMode
|
||||
func setProxyMode(mode C.int) {
|
||||
switch mode {
|
||||
case C.MODE_DIRECT:
|
||||
tunnel.SetMode(tunnel.Direct)
|
||||
case C.MODE_GLOBAL:
|
||||
tunnel.SetMode(tunnel.Global)
|
||||
case C.MODE_RULE:
|
||||
tunnel.SetMode(tunnel.Rule)
|
||||
}
|
||||
}
|
||||
|
||||
//export queryGeneral
|
||||
func queryGeneral(general *C.general_t) {
|
||||
m := tunnel.Mode()
|
||||
ports := proxy.GetPorts()
|
||||
|
||||
switch m {
|
||||
case tunnel.Direct:
|
||||
general.mode = C.MODE_DIRECT
|
||||
case tunnel.Global:
|
||||
general.mode = C.MODE_GLOBAL
|
||||
case tunnel.Rule:
|
||||
general.mode = C.MODE_RULE
|
||||
}
|
||||
|
||||
general.http_port = C.int(ports.Port)
|
||||
general.socks_port = C.int(ports.SocksPort)
|
||||
general.mixed_port = C.int(ports.MixedPort)
|
||||
general.redirect_port = C.int(ports.RedirPort)
|
||||
}
|
||||
23
core/src/main/golang/general.h
Normal file
23
core/src/main/golang/general.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static const int MODE_UNKNOWN = -1;
|
||||
static const int MODE_DIRECT = 0;
|
||||
static const int MODE_GLOBAL = 1;
|
||||
static const int MODE_RULE = 2;
|
||||
static const int MODE_SCRIPT = 3;
|
||||
|
||||
typedef struct general_t {
|
||||
int mode;
|
||||
int http_port;
|
||||
int socks_port;
|
||||
int redirect_port;
|
||||
int mixed_port;
|
||||
} general_t;
|
||||
|
||||
#if __cplusplus
|
||||
};
|
||||
#endif
|
||||
@@ -1,10 +1,10 @@
|
||||
module github.com/kr328/cfa
|
||||
|
||||
go 1.13
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/clash v0.0.0 // local
|
||||
github.com/kr328/tun2socket v0.0.0-20200415021819-256b721ac9a4
|
||||
github.com/kr328/tun2socket v0.0.0-20200613032901-7ffeefc227e3
|
||||
github.com/miekg/dns v1.1.29
|
||||
)
|
||||
|
||||
|
||||
@@ -2,32 +2,31 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.5/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72I
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.0.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr328/tun2socket v0.0.0-20200415021819-256b721ac9a4/go.mod h1:FWfSixjrLgtK+dHkDoN6lHMNhvER24gnjUZd/wt8Z9o=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr328/tun2socket v0.0.0-20200613032901-7ffeefc227e3/go.mod h1:FWfSixjrLgtK+dHkDoN6lHMNhvER24gnjUZd/wt8Z9o=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -35,10 +34,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
58
core/src/main/golang/log.go
Normal file
58
core/src/main/golang/log.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
//#include "event.h"
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var logLocker sync.Mutex
|
||||
var closeChan chan struct{}
|
||||
|
||||
//export enableLogReport
|
||||
func enableLogReport() {
|
||||
logLocker.Lock()
|
||||
defer logLocker.Unlock()
|
||||
|
||||
|
||||
if closeChan != nil {
|
||||
close(closeChan)
|
||||
}
|
||||
|
||||
closeChan = make(chan struct{})
|
||||
|
||||
go func(closed chan struct{}) {
|
||||
subscriber := log.Subscribe()
|
||||
defer log.UnSubscribe(subscriber)
|
||||
defer log.Infoln("Log broadcast disabled")
|
||||
|
||||
for {
|
||||
select {
|
||||
case item := <-subscriber:
|
||||
msg := item.(*log.Event)
|
||||
|
||||
if msg.LogLevel < log.Level() {
|
||||
continue
|
||||
}
|
||||
|
||||
<- sendEvent(C.LOG_RECEIVED, 0, fmt.Sprintf("%s:%s", msg.LogLevel.String(), msg.Payload)).result
|
||||
case <-closeChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}(closeChan)
|
||||
}
|
||||
|
||||
//export disableLogReport
|
||||
func disableLogReport() {
|
||||
logLocker.Lock()
|
||||
defer logLocker.Unlock()
|
||||
|
||||
if closeChan != nil {
|
||||
close(closeChan)
|
||||
|
||||
closeChan = nil
|
||||
}
|
||||
}
|
||||
24
core/src/main/golang/main.c
Normal file
24
core/src/main/golang/main.c
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <android/log.h>
|
||||
|
||||
#define TAG "ClashForAndroid"
|
||||
|
||||
void log_info(const char *msg) {
|
||||
__android_log_write(ANDROID_LOG_INFO, TAG, msg);
|
||||
}
|
||||
|
||||
void log_error(const char *msg) {
|
||||
__android_log_write(ANDROID_LOG_ERROR, TAG, msg);
|
||||
}
|
||||
|
||||
void log_warn(const char *msg) {
|
||||
__android_log_write(ANDROID_LOG_WARN, TAG, msg);
|
||||
}
|
||||
|
||||
void log_debug(const char *msg) {
|
||||
__android_log_write(ANDROID_LOG_DEBUG, TAG, msg);
|
||||
}
|
||||
|
||||
void log_verbose(const char *msg) {
|
||||
__android_log_write(ANDROID_LOG_VERBOSE, TAG, msg);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,83 @@
|
||||
package main
|
||||
|
||||
func main() {}
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/kr328/cfa/config"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -O3
|
||||
#cgo LDFLAGS: -llog
|
||||
#include "buffer.h"
|
||||
#include "malloc.h"
|
||||
|
||||
extern void log_info(const char *msg);
|
||||
extern void log_error(const char *msg);
|
||||
extern void log_warn(const char *msg);
|
||||
extern void log_debug(const char *msg);
|
||||
extern void log_verbose(const char *msg);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func main() {
|
||||
panic("Only for linking")
|
||||
}
|
||||
|
||||
func init() {
|
||||
r := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
sub := log.Subscribe()
|
||||
defer log.UnSubscribe(sub)
|
||||
|
||||
close(r)
|
||||
|
||||
for item := range sub {
|
||||
msg := item.(*log.Event)
|
||||
|
||||
if msg.LogLevel < log.Level() {
|
||||
continue
|
||||
}
|
||||
|
||||
cPayload := C.CString(msg.Payload)
|
||||
|
||||
switch msg.LogLevel {
|
||||
case log.INFO:
|
||||
C.log_info(cPayload)
|
||||
case log.ERROR:
|
||||
C.log_error(cPayload)
|
||||
case log.WARNING:
|
||||
C.log_warn(cPayload)
|
||||
case log.DEBUG:
|
||||
C.log_debug(cPayload)
|
||||
case log.SILENT:
|
||||
C.log_verbose(cPayload)
|
||||
}
|
||||
|
||||
C.free(unsafe.Pointer(cPayload))
|
||||
}
|
||||
}()
|
||||
|
||||
<- r
|
||||
}
|
||||
|
||||
//export initialize
|
||||
func initialize(database *C.const_buffer_t, home, version C.const_string_t) {
|
||||
databaseData := C.GoBytes(database.buffer, database.length)
|
||||
homeData := C.GoString(home)
|
||||
versionData := C.GoString(version)
|
||||
|
||||
mmdb.LoadFromBytes(databaseData)
|
||||
constant.SetHomeDir(homeData)
|
||||
config.ApplicationVersion = versionData
|
||||
}
|
||||
|
||||
//export reset
|
||||
func reset() {
|
||||
config.LoadDefault()
|
||||
tunnel.DefaultManager.ResetStatistic()
|
||||
}
|
||||
198
core/src/main/golang/proxies.go
Normal file
198
core/src/main/golang/proxies.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror
|
||||
#include "buffer.h"
|
||||
#include "proxies.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
//export setSelector
|
||||
func setSelector(group C.const_string_t, selected C.const_string_t) C.int {
|
||||
g := C.GoString(group)
|
||||
s := C.GoString(selected)
|
||||
|
||||
p := tunnel.Proxies()[g]
|
||||
if p == nil {
|
||||
log.Warnln("Set selector failure: %s not found", g)
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
pw, ok := p.(*outbound.Proxy)
|
||||
if !ok {
|
||||
log.Warnln("Set selector failure: %s not valid group", g)
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
adapter, ok := pw.ProxyAdapter.(outboundgroup.ProxyGroup)
|
||||
if !ok {
|
||||
log.Warnln("Set selector failure: %s not valid group", g)
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
selector, ok := adapter.(*outboundgroup.Selector)
|
||||
if !ok {
|
||||
log.Warnln("Set selector failure: %s not selector", g)
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
if err := selector.Set(s); err != nil {
|
||||
log.Warnln("Set selector failure: %s not in %s", s, g)
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
log.Infoln("Set %s -> %s", g, s)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
//export queryProxyGroups
|
||||
func queryProxyGroups() *C.proxy_group_list_t {
|
||||
stringPool := make([]byte, 0, 4096)
|
||||
proxies := tunnel.Proxies()
|
||||
groups := make([]outboundgroup.ProxyGroup, 0, len(proxies))
|
||||
|
||||
for _, p := range proxies {
|
||||
pw, ok := p.(*outbound.Proxy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
adapter, ok := pw.ProxyAdapter.(outboundgroup.ProxyGroup)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
groups = append(groups, adapter)
|
||||
}
|
||||
|
||||
result := allocCProxyGroupList(len(groups))
|
||||
groupIndex := 0
|
||||
for _, group := range groups {
|
||||
ps := make([]constant.Proxy, 0)
|
||||
for _, provider := range group.GetProxyProviders() {
|
||||
ps = append(ps, provider.Proxies()...)
|
||||
}
|
||||
|
||||
g := allocCProxyGroup(len(ps))
|
||||
|
||||
g.base.name_index = C.long(len(stringPool))
|
||||
g.base.proxy_type = typeToProxyTypeC(group.Type())
|
||||
g.base.delay = 0
|
||||
|
||||
stringPool = append(stringPool, group.Name()...)
|
||||
stringPool = append(stringPool, 0)
|
||||
|
||||
proxyIndex := 0
|
||||
for _, proxy := range ps {
|
||||
p := indexCProxyGroupElement(g, proxyIndex)
|
||||
|
||||
p.name_index = C.long(len(stringPool))
|
||||
p.proxy_type = typeToProxyTypeC(proxy.Type())
|
||||
p.delay = C.long(proxy.LastDelay())
|
||||
|
||||
stringPool = append(stringPool, proxy.Name()...)
|
||||
stringPool = append(stringPool, 0)
|
||||
|
||||
if proxy.Name() == group.Now() {
|
||||
g.now = C.int(proxyIndex)
|
||||
}
|
||||
|
||||
proxyIndex++
|
||||
}
|
||||
|
||||
setCProxyGroupListElement(result, groupIndex, g)
|
||||
|
||||
groupIndex++
|
||||
}
|
||||
|
||||
result.string_pool = (*C.char)(C.CBytes(stringPool))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
|
||||
func typeToProxyTypeC(t constant.AdapterType) C.proxy_type_t {
|
||||
switch t {
|
||||
case constant.Direct:
|
||||
return C.Direct
|
||||
case constant.Reject:
|
||||
return C.Reject
|
||||
|
||||
case constant.Shadowsocks:
|
||||
return C.Shadowsocks
|
||||
case constant.Snell:
|
||||
return C.Snell
|
||||
case constant.Socks5:
|
||||
return C.Socks5
|
||||
case constant.Http:
|
||||
return C.Http
|
||||
case constant.Vmess:
|
||||
return C.Vmess
|
||||
case constant.Trojan:
|
||||
return C.Trojan
|
||||
|
||||
case constant.Relay:
|
||||
return C.Relay
|
||||
case constant.Selector:
|
||||
return C.Selector
|
||||
case constant.Fallback:
|
||||
return C.Fallback
|
||||
case constant.URLTest:
|
||||
return C.URLTest
|
||||
case constant.LoadBalance:
|
||||
return C.LoadBalance
|
||||
|
||||
default:
|
||||
return C.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
func allocCProxyGroup(proxiesSize int) *C.proxy_group_t {
|
||||
result := (*C.proxy_group_t)(C.malloc(C.sizeof_proxy_group_t + C.sizeof_proxy_t * C.size_t(proxiesSize)))
|
||||
|
||||
result.proxies_size = C.int(proxiesSize)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func allocCProxyGroupList(groupSize int) *C.proxy_group_list_t {
|
||||
result := (*C.proxy_group_list_t)(C.malloc(C.sizeof_proxy_group_list_t + C.sizeof_long * C.size_t(groupSize)))
|
||||
|
||||
result.size = C.int(groupSize)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
//noinspection GoVetUnsafePointer
|
||||
func setCProxyGroupListElement(list *C.proxy_group_list_t, index int, element *C.proxy_group_t) {
|
||||
address := uintptr(unsafe.Pointer(list))
|
||||
|
||||
offset := address + uintptr(C.sizeof_proxy_group_list_t) + uintptr(index) * uintptr(C.sizeof_long)
|
||||
|
||||
*(**C.proxy_group_t)(unsafe.Pointer(offset)) = element
|
||||
}
|
||||
|
||||
//noinspection GoVetUnsafePointer
|
||||
func indexCProxyGroupElement(group *C.proxy_group_t, index int) *C.proxy_t {
|
||||
address := uintptr(unsafe.Pointer(group))
|
||||
|
||||
offset := address + uintptr(C.sizeof_proxy_group_t) + uintptr(index) * uintptr(C.sizeof_proxy_t)
|
||||
|
||||
return (*C.proxy_t)(unsafe.Pointer(offset))
|
||||
}
|
||||
33
core/src/main/golang/proxies.h
Normal file
33
core/src/main/golang/proxies.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum proxy_type_t {
|
||||
Direct, Reject, Socks5, Http, Shadowsocks, Vmess, Snell, Trojan, Selector, Fallback, LoadBalance, URLTest, Relay, Unknown
|
||||
} proxy_type_t;
|
||||
|
||||
typedef struct proxy_t {
|
||||
long name_index;
|
||||
proxy_type_t proxy_type;
|
||||
long delay;
|
||||
} proxy_t;
|
||||
|
||||
typedef struct proxy_group_t {
|
||||
proxy_t base;
|
||||
int now;
|
||||
int proxies_size;
|
||||
proxy_t proxies[];
|
||||
} proxy_group_t;
|
||||
|
||||
typedef struct proxy_group_list_t {
|
||||
int size;
|
||||
char *string_pool;
|
||||
proxy_group_t *groups[];
|
||||
} proxy_group_list_t;
|
||||
|
||||
#if __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
24
core/src/main/golang/traffic.go
Normal file
24
core/src/main/golang/traffic.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include "traffic.h"
|
||||
*/
|
||||
import "C"
|
||||
import "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
//export querySpeed
|
||||
func querySpeed(r *C.traffic_t) {
|
||||
u, d := tunnel.DefaultManager.Now()
|
||||
|
||||
r.upload = C.int64_t(u)
|
||||
r.download = C.int64_t(d)
|
||||
}
|
||||
|
||||
//export queryBandwidth
|
||||
func queryBandwidth(r *C.traffic_t) {
|
||||
u := tunnel.DefaultManager.UploadTotal()
|
||||
d := tunnel.DefaultManager.DownloadTotal()
|
||||
|
||||
r.upload = C.int64_t(u)
|
||||
r.download = C.int64_t(d)
|
||||
}
|
||||
16
core/src/main/golang/traffic.h
Normal file
16
core/src/main/golang/traffic.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct traffic_t {
|
||||
int64_t upload;
|
||||
int64_t download;
|
||||
} traffic_t;
|
||||
|
||||
#if __cplusplus
|
||||
};
|
||||
#endif
|
||||
62
core/src/main/golang/tun.go
Normal file
62
core/src/main/golang/tun.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/kr328/cfa/tun"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
//#include "buffer.h"
|
||||
//#include "event.h"
|
||||
import "C"
|
||||
|
||||
var tunLock sync.Mutex
|
||||
|
||||
func init() {
|
||||
c := func(_, _ string, conn syscall.RawConn) error {
|
||||
return conn.Control(func(fd uintptr) {
|
||||
<- sendEvent(C.NEW_SOCKET, 0, strconv.Itoa(int(fd))).result
|
||||
})
|
||||
}
|
||||
|
||||
dialer.DialerHook = func(d *net.Dialer) error {
|
||||
d.Control = c
|
||||
return nil
|
||||
}
|
||||
dialer.ListenConfigHook = func(l *net.ListenConfig) error {
|
||||
l.Control = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//export startTunDevice
|
||||
func startTunDevice(fd, mtu int, gateway, mirror, dns C.const_string_t, callbackId uint64) *C.char {
|
||||
stopTunDevice()
|
||||
|
||||
tunLock.Lock()
|
||||
defer tunLock.Unlock()
|
||||
|
||||
g := C.GoString(gateway)
|
||||
m := C.GoString(mirror)
|
||||
d := C.GoString(dns)
|
||||
|
||||
err := tun.StartTunDevice(fd, mtu, g, m, d, func() {
|
||||
sendEvent(C.TUN_STOP, callbackId, "")
|
||||
})
|
||||
if err != nil {
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//export stopTunDevice
|
||||
func stopTunDevice() {
|
||||
tunLock.Lock()
|
||||
defer tunLock.Unlock()
|
||||
|
||||
tun.StopTunDevice()
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func hijackTCPDNS(conn net.Conn, endpoint *binding.Endpoint) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if !hijackAddress.Equal(net.IPv4zero) && !hijackAddress.Equal(net.IPv6zero) && !hijackAddress.Equal(endpoint.Target.IP) {
|
||||
if !hijackAddress.Equal(net.IPv4zero) && !hijackAddress.Equal(endpoint.Target.IP) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func hijackDNS(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSe
|
||||
return false
|
||||
}
|
||||
|
||||
if !hijackAddress.Equal(net.IPv4zero) && !hijackAddress.Equal(net.IPv6zero) && !hijackAddress.Equal(endpoint.Target.IP) {
|
||||
if !hijackAddress.Equal(net.IPv4zero) && !hijackAddress.Equal(endpoint.Target.IP) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -2,21 +2,20 @@ package tun
|
||||
|
||||
import "github.com/Dreamacro/clash/log"
|
||||
|
||||
type ClashLogger struct {}
|
||||
type ClashLogger struct{}
|
||||
|
||||
func (c *ClashLogger) D(format string, args ...interface{}) {
|
||||
log.Debugln(format, args)
|
||||
log.Debugln(format, args...)
|
||||
}
|
||||
|
||||
func (c *ClashLogger) I(format string, args ...interface{}) {
|
||||
log.Infoln(format, args)
|
||||
log.Infoln(format, args...)
|
||||
}
|
||||
|
||||
func (c *ClashLogger) W(format string, args ...interface{}) {
|
||||
log.Warnln(format, args)
|
||||
log.Warnln(format, args...)
|
||||
}
|
||||
|
||||
func (c *ClashLogger) E(format string, args ...interface{}) {
|
||||
log.Errorln(format, args)
|
||||
log.Errorln(format, args...)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ const (
|
||||
var adapter *tun2socket.Tun2Socket
|
||||
var mutex sync.Mutex
|
||||
|
||||
func StartTunDevice(fd, mtu int, gateway, mirror, dnsAddress string) error {
|
||||
func StartTunDevice(fd, mtu int, gateway, mirror, dnsAddress string, onStop func()) error {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
@@ -36,6 +36,7 @@ func StartTunDevice(fd, mtu int, gateway, mirror, dnsAddress string) error {
|
||||
}
|
||||
|
||||
gatewayIP, gatewayNet, err := net.ParseCIDR(gateway)
|
||||
_, ipv4Loopback, _ := net.ParseCIDR("127.0.0.0/8")
|
||||
mirrorIP := net.ParseIP(mirror)
|
||||
|
||||
if err != nil || mirrorIP == nil || !gatewayNet.Contains(mirrorIP) {
|
||||
@@ -59,6 +60,8 @@ func StartTunDevice(fd, mtu int, gateway, mirror, dnsAddress string) error {
|
||||
adapter.SetLogger(&ClashLogger{})
|
||||
adapter.SetClosedHandler(func() {
|
||||
StopTunDevice()
|
||||
|
||||
onStop()
|
||||
})
|
||||
adapter.SetAllocator(func(length int) []byte {
|
||||
if length <= maxUdpPacketSize {
|
||||
@@ -67,7 +70,8 @@ func StartTunDevice(fd, mtu int, gateway, mirror, dnsAddress string) error {
|
||||
return make([]byte, length)
|
||||
})
|
||||
adapter.SetTCPHandler(func(conn net.Conn, endpoint *binding.Endpoint) {
|
||||
if gatewayNet.Contains(endpoint.Target.IP) {
|
||||
if gatewayNet.Contains(endpoint.Target.IP) || ipv4Loopback.Contains(endpoint.Target.IP) {
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,10 +85,10 @@ func StartTunDevice(fd, mtu int, gateway, mirror, dnsAddress string) error {
|
||||
Zone: "",
|
||||
})
|
||||
|
||||
tunnel.Add(adapters.NewSocket(addr, conn, C.SOCKS, C.TCP))
|
||||
tunnel.Add(adapters.NewSocket(addr, conn, C.SOCKS))
|
||||
})
|
||||
adapter.SetUDPHandler(func(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender) {
|
||||
if gatewayNet.Contains(endpoint.Target.IP) {
|
||||
if gatewayNet.Contains(endpoint.Target.IP) || ipv4Loopback.Contains(endpoint.Target.IP) {
|
||||
udpRecycle(payload)
|
||||
return
|
||||
}
|
||||
@@ -101,7 +105,8 @@ func StartTunDevice(fd, mtu int, gateway, mirror, dnsAddress string) error {
|
||||
pkt := &udpPacket{
|
||||
payload: payload,
|
||||
endpoint: endpoint,
|
||||
sender: sender,
|
||||
send: sender,
|
||||
recycle: udpRecycle,
|
||||
}
|
||||
|
||||
tunnel.AddPacket(adapters.NewPacket(addr, pkt, C.SOCKS))
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
type udpPacket struct {
|
||||
payload []byte
|
||||
endpoint *binding.Endpoint
|
||||
sender redirect.UDPSender
|
||||
send redirect.UDPSender
|
||||
recycle func([]byte)
|
||||
}
|
||||
|
||||
func (conn *udpPacket) Data() []byte {
|
||||
@@ -39,11 +40,7 @@ func (conn *udpPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
Target: conn.endpoint.Source,
|
||||
}
|
||||
|
||||
return len(b), conn.sender(b, ep)
|
||||
}
|
||||
|
||||
func (conn *udpPacket) Close() error {
|
||||
return nil
|
||||
return len(b), conn.send(b, ep)
|
||||
}
|
||||
|
||||
func (conn *udpPacket) LocalAddr() net.Addr {
|
||||
@@ -53,3 +50,7 @@ func (conn *udpPacket) LocalAddr() net.Addr {
|
||||
Zone: "",
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *udpPacket) Drop() {
|
||||
conn.recycle(conn.payload)
|
||||
}
|
||||
|
||||
@@ -5,3 +5,4 @@ import "io"
|
||||
func CloseSilent(closer io.Closer) {
|
||||
_ = closer.Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package com.github.kr328.clash.core
|
||||
|
||||
import bridge.Bridge
|
||||
import bridge.TunCallback
|
||||
import android.os.ParcelFileDescriptor
|
||||
import com.github.kr328.clash.common.Global
|
||||
import com.github.kr328.clash.common.utils.Log
|
||||
import com.github.kr328.clash.core.bridge.Bridge
|
||||
import com.github.kr328.clash.core.bridge.TunCallback
|
||||
import com.github.kr328.clash.core.event.LogEvent
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.Proxy
|
||||
import com.github.kr328.clash.core.model.ProxyGroup
|
||||
import com.github.kr328.clash.core.model.Traffic
|
||||
import com.github.kr328.clash.core.transact.DoneCallbackImpl
|
||||
import com.github.kr328.clash.core.transact.ProxyCollectionImpl
|
||||
import com.github.kr328.clash.core.transact.ProxyGroupCollectionImpl
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
object Clash {
|
||||
private val logReceivers = mutableMapOf<String, (LogEvent) -> Unit>()
|
||||
@@ -24,8 +22,16 @@ object Clash {
|
||||
val bytes = context.assets.open("Country.mmdb")
|
||||
.use(InputStream::readBytes)
|
||||
|
||||
Bridge.initCore(bytes, context.cacheDir.absolutePath, BuildConfig.VERSION_NAME)
|
||||
Bridge.initialize(bytes, context.cacheDir.absolutePath, BuildConfig.VERSION_NAME)
|
||||
Bridge.reset()
|
||||
|
||||
Bridge.setLogCallback {
|
||||
synchronized(logReceivers) {
|
||||
logReceivers.forEach { (_, e) -> e(it) }
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("Clash core initialized")
|
||||
}
|
||||
|
||||
fun start() {
|
||||
@@ -45,11 +51,10 @@ object Clash {
|
||||
onNewSocket: (Int) -> Boolean,
|
||||
onTunStop: () -> Unit
|
||||
) {
|
||||
Bridge.startTunDevice(fd.toLong(), mtu.toLong(), gateway, mirror, dns, object: TunCallback {
|
||||
override fun onCreateSocket(fd: Long) {
|
||||
onNewSocket(fd.toInt())
|
||||
Bridge.startTunDevice(fd, mtu, gateway, mirror, dns, object: TunCallback {
|
||||
override fun onNewSocket(socket: Int) {
|
||||
onNewSocket(socket)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
onTunStop()
|
||||
}
|
||||
@@ -60,56 +65,32 @@ object Clash {
|
||||
Bridge.stopTunDevice()
|
||||
}
|
||||
|
||||
fun appendDns(dns: List<String>) {
|
||||
Bridge.resetDnsAppend(dns.joinToString(","))
|
||||
fun setDnsOverride(dnsOverride: Boolean, appendNameservers: List<String>) {
|
||||
Bridge.setDnsOverride(dnsOverride, appendNameservers.joinToString(","))
|
||||
}
|
||||
|
||||
fun setDnsOverrideEnabled(enabled: Boolean) {
|
||||
Bridge.setDnsOverrideEnabled(enabled)
|
||||
fun loadProfile(path: File, baseDir: File): CompletableFuture<Unit> {
|
||||
return Bridge.loadProfile(path.absolutePath, baseDir.absolutePath).thenApply { Unit }
|
||||
}
|
||||
|
||||
fun loadProfile(path: File, baseDir: File): CompletableDeferred<Unit> {
|
||||
return DoneCallbackImpl().apply {
|
||||
Bridge.loadProfileFile(path.absolutePath, baseDir.absolutePath, this)
|
||||
}
|
||||
fun downloadProfile(url: String, output: File, baseDir: File): CompletableFuture<Unit> {
|
||||
return Bridge.downloadProfile(url, baseDir.absolutePath, output.absolutePath).thenApply { Unit }
|
||||
}
|
||||
|
||||
fun downloadProfile(url: String, output: File, baseDir: File): CompletableDeferred<Unit> {
|
||||
return DoneCallbackImpl().apply {
|
||||
Bridge.downloadProfileAndCheck(url, output.absolutePath, baseDir.absolutePath, this)
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadProfile(fd: Int, output: File, baseDir: File): CompletableDeferred<Unit> {
|
||||
return DoneCallbackImpl().apply {
|
||||
Bridge.readProfileAndCheck(fd.toLong(), output.absolutePath, baseDir.absolutePath, this)
|
||||
}
|
||||
fun downloadProfile(fd: ParcelFileDescriptor, output: File, baseDir: File): CompletableFuture<Unit> {
|
||||
return Bridge.downloadProfile(fd.detachFd(), baseDir.absolutePath, output.absolutePath).thenApply { Unit }
|
||||
}
|
||||
|
||||
fun queryProxyGroups(): List<ProxyGroup> {
|
||||
return ProxyGroupCollectionImpl().also { Bridge.queryAllProxyGroups(it) }
|
||||
.filterNotNull()
|
||||
.map { group ->
|
||||
ProxyGroup(group.name,
|
||||
Proxy.Type.fromString(group.type),
|
||||
group.delay,
|
||||
group.current,
|
||||
ProxyCollectionImpl().also { pc ->
|
||||
group.queryAllProxies(pc)
|
||||
}.filterNotNull().map {
|
||||
Proxy(it.name, Proxy.Type.fromString(it.type), it.delay)
|
||||
})
|
||||
}
|
||||
return Bridge.queryProxyGroups().toList()
|
||||
}
|
||||
|
||||
fun setSelectedProxy(name: String, selected: String): Boolean {
|
||||
return Bridge.setSelectedProxy(name, selected)
|
||||
fun setSelector(name: String, selected: String): Boolean {
|
||||
return Bridge.setSelector(name, selected)
|
||||
}
|
||||
|
||||
fun startHealthCheck(name: String): CompletableDeferred<Unit> {
|
||||
return DoneCallbackImpl().apply {
|
||||
Bridge.startUrlTest(name, this)
|
||||
}
|
||||
fun performHealthCheck(group: String): CompletableFuture<Unit> {
|
||||
return Bridge.performHealthCheck(group).thenApply { Unit }
|
||||
}
|
||||
|
||||
fun setProxyMode(mode: String) {
|
||||
@@ -117,48 +98,30 @@ object Clash {
|
||||
}
|
||||
|
||||
fun queryGeneral(): General {
|
||||
val t = Bridge.queryGeneral()
|
||||
|
||||
return General(
|
||||
General.Mode.fromString(t.mode),
|
||||
t.httpPort.toInt(), t.socksPort.toInt(), t.redirectPort.toInt()
|
||||
)
|
||||
return Bridge.queryGeneral()
|
||||
}
|
||||
|
||||
fun queryTraffic(): Traffic {
|
||||
val data = Bridge.queryTraffic()
|
||||
|
||||
return Traffic(data.upload, data.download)
|
||||
fun querySpeed(): Traffic {
|
||||
return Bridge.querySpeed()
|
||||
}
|
||||
|
||||
fun queryBandwidth(): Traffic {
|
||||
val data = Bridge.queryBandwidth()
|
||||
|
||||
return Traffic(data.upload, data.download)
|
||||
return Bridge.queryBandwidth()
|
||||
}
|
||||
|
||||
fun registerLogReceiver(key: String, receiver: (LogEvent) -> Unit) {
|
||||
synchronized(logReceivers) {
|
||||
if ( logReceivers.isEmpty() )
|
||||
Bridge.enableLogReport()
|
||||
logReceivers[key] = receiver
|
||||
|
||||
Bridge.setLogCallback(this::onLogEvent)
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterLogReceiver(key: String) {
|
||||
synchronized(logReceivers) {
|
||||
logReceivers.remove(key)
|
||||
|
||||
if (logReceivers.isEmpty())
|
||||
Bridge.setLogCallback(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLogEvent(level: String, payload: String) {
|
||||
synchronized(logReceivers) {
|
||||
logReceivers.forEach {
|
||||
it.value(LogEvent(LogEvent.Level.fromString(level), payload))
|
||||
}
|
||||
if ( logReceivers.isEmpty() )
|
||||
Bridge.disableLogReport();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.github.kr328.clash.core.bridge;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import com.github.kr328.clash.core.model.General;
|
||||
import com.github.kr328.clash.core.model.ProxyGroup;
|
||||
import com.github.kr328.clash.core.model.Traffic;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Keep
|
||||
public final class Bridge {
|
||||
static {
|
||||
System.loadLibrary("bridge");
|
||||
}
|
||||
|
||||
private Bridge() {
|
||||
}
|
||||
|
||||
public static native void initialize(byte[] database, String home, String version);
|
||||
public static native void reset();
|
||||
|
||||
public static native General queryGeneral();
|
||||
public static native Traffic querySpeed();
|
||||
public static native Traffic queryBandwidth();
|
||||
public static native ProxyGroup[] queryProxyGroups();
|
||||
|
||||
public static native void startTunDevice(int fd, int mtu, String gateway, String mirror, String dns, TunCallback callback) throws ClashException;
|
||||
public static native void stopTunDevice();
|
||||
|
||||
public static native void setDnsOverride(boolean overrideDns, String appendNameservers);
|
||||
public static native void setProxyMode(String mode);
|
||||
public static native boolean setSelector(String group, String selected);
|
||||
|
||||
public static native CompletableFuture<Object> downloadProfile(String url, String base, String output);
|
||||
public static native CompletableFuture<Object> downloadProfile(int fd, String base, String output);
|
||||
|
||||
public static native CompletableFuture<Object> loadProfile(String path, String base);
|
||||
public static native CompletableFuture<Object> performHealthCheck(String group);
|
||||
|
||||
public static native void setLogCallback(LogCallback callback);
|
||||
public static native void enableLogReport();
|
||||
public static native void disableLogReport();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.github.kr328.clash.core.bridge;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
public class ClashException extends Exception {
|
||||
@Keep
|
||||
public ClashException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.github.kr328.clash.core.bridge;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import com.github.kr328.clash.core.event.LogEvent;
|
||||
|
||||
@Keep
|
||||
@SuppressWarnings("unused")
|
||||
public interface LogCallback {
|
||||
void onMessage(LogEvent event);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.github.kr328.clash.core.bridge;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
@SuppressWarnings("unused")
|
||||
public interface TunCallback {
|
||||
void onNewSocket(int socket);
|
||||
void onStop();
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.github.kr328.clash.core.event
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import com.github.kr328.clash.common.serialization.Parcels
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -12,6 +13,10 @@ data class LogEvent(
|
||||
val message: String,
|
||||
val time: Long = System.currentTimeMillis()
|
||||
) : Parcelable {
|
||||
private constructor(data: List<String>): this(Level.fromString(data[0]), data[1])
|
||||
@Keep
|
||||
constructor(data: String) : this(data.split(":", limit = 2))
|
||||
|
||||
companion object {
|
||||
const val DEBUG_VALUE = "debug"
|
||||
const val INFO_VALUE = "info"
|
||||
|
||||
@@ -2,11 +2,16 @@ package com.github.kr328.clash.core.model
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import com.github.kr328.clash.common.serialization.Parcels
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class General(val mode: Mode, val http: Int, val socks: Int, val redirect: Int) : Parcelable {
|
||||
data class General(val mode: Mode, val http: Int, val socks: Int, val redirect: Int, val mixed: Int) : Parcelable {
|
||||
@Keep
|
||||
constructor(mode: String, http: Int, socks: Int, redirect: Int, mixed: Int) :
|
||||
this(Mode.fromString(mode), http, socks, redirect, mixed)
|
||||
|
||||
@Serializable
|
||||
enum class Mode(val string: String) {
|
||||
DIRECT("Direct"), GLOBAL("Global"), RULE("Rule");
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Proxy(
|
||||
data class Proxy constructor(
|
||||
val name: String,
|
||||
val type: Type,
|
||||
val delay: Long
|
||||
) {
|
||||
@Keep
|
||||
constructor(name: String, type: String, delay: Long): this(name, Type.fromString(type), delay)
|
||||
|
||||
enum class Type(val text: String, val group: Boolean) {
|
||||
DIRECT("Direct", false),
|
||||
REJECT("Reject", false),
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ProxyGroup(
|
||||
val name: String,
|
||||
val type: Proxy.Type,
|
||||
val delay: Long,
|
||||
val current: String,
|
||||
val proxies: List<Proxy>
|
||||
)
|
||||
) {
|
||||
@Keep
|
||||
constructor(name: String, type: String, current: String, proxies: Array<Proxy>) :
|
||||
this(name, Proxy.Type.fromString(type), current, proxies.toList())
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import com.github.kr328.clash.common.serialization.MergedParcels
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ProxyGroupList(val list: List<ProxyGroup>) : Parcelable {
|
||||
data class ProxyGroupWrapper(val list: List<ProxyGroup>) : Parcelable {
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
MergedParcels.dump(serializer(), this, parcel)
|
||||
}
|
||||
@@ -15,12 +15,12 @@ data class ProxyGroupList(val list: List<ProxyGroup>) : Parcelable {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<ProxyGroupList> {
|
||||
override fun createFromParcel(parcel: Parcel): ProxyGroupList {
|
||||
companion object CREATOR : Parcelable.Creator<ProxyGroupWrapper> {
|
||||
override fun createFromParcel(parcel: Parcel): ProxyGroupWrapper {
|
||||
return MergedParcels.load(serializer(), parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<ProxyGroupList?> {
|
||||
override fun newArray(size: Int): Array<ProxyGroupWrapper?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
data class Traffic(val upload: Long, val download: Long)
|
||||
import androidx.annotation.Keep
|
||||
|
||||
data class Traffic @Keep constructor(val upload: Long, val download: Long)
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.github.kr328.clash.core.transact
|
||||
|
||||
import bridge.DoneCallback
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
|
||||
class DoneCallbackImpl : DoneCallback, CompletableDeferred<Unit> by CompletableDeferred() {
|
||||
override fun doneWithError(e: Exception?) {
|
||||
completeExceptionally(e ?: return done())
|
||||
}
|
||||
|
||||
override fun done() {
|
||||
complete(Unit)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.github.kr328.clash.core.transact
|
||||
|
||||
import bridge.ProxyCollection
|
||||
import bridge.ProxyGroupCollection
|
||||
import bridge.ProxyGroupItem
|
||||
import bridge.ProxyItem
|
||||
import java.util.*
|
||||
|
||||
class ProxyCollectionImpl : LinkedList<ProxyItem?>(), ProxyCollection
|
||||
class ProxyGroupCollectionImpl : LinkedList<ProxyGroupItem?>(), ProxyGroupCollection
|
||||
@@ -4,21 +4,19 @@ plugins {
|
||||
id("kotlin-android-extensions")
|
||||
}
|
||||
|
||||
val rootExtra = rootProject.extra
|
||||
val gCompileSdkVersion: String by project
|
||||
val gBuildToolsVersion: String by project
|
||||
|
||||
val gCompileSdkVersion: Int by rootExtra
|
||||
val gBuildToolsVersion: String by rootExtra
|
||||
val gMinSdkVersion: String by project
|
||||
val gTargetSdkVersion: String by project
|
||||
|
||||
val gMinSdkVersion: Int by rootExtra
|
||||
val gTargetSdkVersion: Int by rootExtra
|
||||
val gVersionCode: String by project
|
||||
val gVersionName: String by project
|
||||
|
||||
val gVersionCode: Int by rootExtra
|
||||
val gVersionName: String by rootExtra
|
||||
|
||||
val gKotlinVersion: String by rootExtra
|
||||
val gAndroidKtxVersion: String by rootExtra
|
||||
val gAppCompatVersion: String by rootExtra
|
||||
val gMaterialDesignVersion: String by rootExtra
|
||||
val gKotlinVersion: String by project
|
||||
val gAndroidKtxVersion: String by project
|
||||
val gAppCompatVersion: String by project
|
||||
val gMaterialDesignVersion: String by project
|
||||
|
||||
android {
|
||||
compileSdkVersion(gCompileSdkVersion)
|
||||
@@ -28,14 +26,14 @@ android {
|
||||
minSdkVersion(gMinSdkVersion)
|
||||
targetSdkVersion(gTargetSdkVersion)
|
||||
|
||||
versionCode = gVersionCode
|
||||
versionCode = gVersionCode.toInt()
|
||||
versionName = gVersionName
|
||||
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
maybeCreate("release").apply {
|
||||
named("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
|
||||
@@ -19,5 +19,26 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
kapt.incremental.apt=false
|
||||
org.gradle.parallel=true
|
||||
|
||||
kapt.incremental.apt=false
|
||||
# dependencies
|
||||
gBuildToolsVersion=29.0.3
|
||||
gCompileSdkVersion=android-29
|
||||
|
||||
gMinSdkVersion=24
|
||||
gTargetSdkVersion=29
|
||||
|
||||
gVersionCode=10302
|
||||
gVersionName=1.3.2
|
||||
gKotlinVersion=1.3.72
|
||||
gKotlinCoroutineVersion=1.3.7
|
||||
gKotlinSerializationVersion=0.20.0
|
||||
gRoomVersion=2.2.5
|
||||
gAppCenterVersion=2.5.1
|
||||
gAndroidKtxVersion=1.3.0
|
||||
gRecyclerviewVersion=1.1.0
|
||||
gAppCompatVersion=1.1.0
|
||||
gMaterialDesignVersion=1.1.0
|
||||
gShizukuPreferenceVersion=4.2.0
|
||||
gMultiprocessPreferenceVersion=1.0.0
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
|
||||
|
||||
@@ -6,23 +6,21 @@ plugins {
|
||||
id("kotlinx-serialization")
|
||||
}
|
||||
|
||||
val rootExtra = rootProject.extra
|
||||
val gCompileSdkVersion: String by project
|
||||
val gBuildToolsVersion: String by project
|
||||
|
||||
val gCompileSdkVersion: Int by rootExtra
|
||||
val gBuildToolsVersion: String by rootExtra
|
||||
val gMinSdkVersion: String by project
|
||||
val gTargetSdkVersion: String by project
|
||||
|
||||
val gMinSdkVersion: Int by rootExtra
|
||||
val gTargetSdkVersion: Int by rootExtra
|
||||
val gVersionCode: String by project
|
||||
val gVersionName: String by project
|
||||
|
||||
val gVersionCode: Int by rootExtra
|
||||
val gVersionName: String by rootExtra
|
||||
|
||||
val gKotlinVersion: String by rootExtra
|
||||
val gKotlinCoroutineVersion: String by rootExtra
|
||||
val gKotlinSerializationVersion: String by rootExtra
|
||||
val gRoomVersion: String by rootExtra
|
||||
val gAndroidKtxVersion: String by rootExtra
|
||||
val gMultiprocessPreferenceVersion: String by rootExtra
|
||||
val gKotlinVersion: String by project
|
||||
val gKotlinCoroutineVersion: String by project
|
||||
val gKotlinSerializationVersion: String by project
|
||||
val gRoomVersion: String by project
|
||||
val gAndroidKtxVersion: String by project
|
||||
val gMultiprocessPreferenceVersion: String by project
|
||||
|
||||
android {
|
||||
compileSdkVersion(gCompileSdkVersion)
|
||||
@@ -32,14 +30,14 @@ android {
|
||||
minSdkVersion(gMinSdkVersion)
|
||||
targetSdkVersion(gTargetSdkVersion)
|
||||
|
||||
versionCode = gVersionCode
|
||||
versionCode = gVersionCode.toInt()
|
||||
versionName = gVersionName
|
||||
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
maybeCreate("release").apply {
|
||||
named("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
@@ -66,6 +64,7 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$gKotlinSerializationVersion")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$gKotlinVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$gKotlinCoroutineVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$gKotlinCoroutineVersion")
|
||||
implementation("androidx.room:room-runtime:$gRoomVersion")
|
||||
implementation("androidx.room:room-ktx:$gRoomVersion")
|
||||
implementation("androidx.core:core-ktx:$gAndroidKtxVersion")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.github.kr328.clash.core.model;
|
||||
|
||||
parcelable ProxyGroupList;
|
||||
parcelable ProxyGroupWrapper;
|
||||
parcelable General;
|
||||
@@ -5,12 +5,12 @@ import com.github.kr328.clash.core.model.Packet;
|
||||
|
||||
interface IClashManager {
|
||||
// Control
|
||||
boolean setSelectProxy(String proxy, String selected);
|
||||
void startHealthCheck(String group, IStreamCallback callback);
|
||||
void setSelector(String group, String selected);
|
||||
void performHealthCheck(String group, IStreamCallback callback);
|
||||
void setProxyMode(String mode);
|
||||
|
||||
// Query
|
||||
ProxyGroupList queryAllProxies();
|
||||
ProxyGroupWrapper queryProxyGroups();
|
||||
General queryGeneral();
|
||||
long queryBandwidth();
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.github.kr328.clash.service
|
||||
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.ProxyGroupList
|
||||
import com.github.kr328.clash.core.model.ProxyGroupWrapper
|
||||
import com.github.kr328.clash.service.data.ProfileDao
|
||||
import com.github.kr328.clash.service.data.SelectedProxyDao
|
||||
import com.github.kr328.clash.service.data.SelectedProxyEntity
|
||||
@@ -18,15 +18,15 @@ class ClashManager(parent: CoroutineScope) :
|
||||
Clash.setProxyMode(requireNotNull(mode))
|
||||
}
|
||||
|
||||
override fun queryAllProxies(): ProxyGroupList {
|
||||
return ProxyGroupList(Clash.queryProxyGroups())
|
||||
override fun queryProxyGroups(): ProxyGroupWrapper {
|
||||
return ProxyGroupWrapper(Clash.queryProxyGroups())
|
||||
}
|
||||
|
||||
override fun queryGeneral(): General {
|
||||
return Clash.queryGeneral()
|
||||
}
|
||||
|
||||
override fun setSelectProxy(proxy: String?, selected: String?): Boolean {
|
||||
override fun setSelector(proxy: String?, selected: String?) {
|
||||
require(proxy != null && selected != null)
|
||||
|
||||
launch {
|
||||
@@ -35,7 +35,7 @@ class ClashManager(parent: CoroutineScope) :
|
||||
SelectedProxyDao.setSelectedForProfile(SelectedProxyEntity(current.id, proxy, selected))
|
||||
}
|
||||
|
||||
return Clash.setSelectedProxy(proxy, selected)
|
||||
Clash.setSelector(proxy, selected)
|
||||
}
|
||||
|
||||
override fun queryBandwidth(): Long {
|
||||
@@ -44,10 +44,10 @@ class ClashManager(parent: CoroutineScope) :
|
||||
return data.download + data.upload
|
||||
}
|
||||
|
||||
override fun startHealthCheck(group: String?, callback: IStreamCallback?) {
|
||||
override fun performHealthCheck(group: String?, callback: IStreamCallback?) {
|
||||
require(group != null && callback != null)
|
||||
|
||||
Clash.startHealthCheck(group).invokeOnCompletion { u ->
|
||||
Clash.performHealthCheck(group).whenComplete { _, u ->
|
||||
if (u != null)
|
||||
callback.completeExceptionally(u.message)
|
||||
else
|
||||
|
||||
@@ -4,10 +4,7 @@ import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import com.github.kr328.clash.service.clash.ClashRuntime
|
||||
import com.github.kr328.clash.service.clash.module.CloseModule
|
||||
import com.github.kr328.clash.service.clash.module.DynamicNotificationModule
|
||||
import com.github.kr328.clash.service.clash.module.ReloadModule
|
||||
import com.github.kr328.clash.service.clash.module.StaticNotificationModule
|
||||
import com.github.kr328.clash.service.clash.module.*
|
||||
import com.github.kr328.clash.service.settings.ServiceSettings
|
||||
import com.github.kr328.clash.service.util.broadcastClashStarted
|
||||
import com.github.kr328.clash.service.util.broadcastClashStopped
|
||||
@@ -36,9 +33,7 @@ class ClashService : BaseService() {
|
||||
runtime.install(ReloadModule(service)) {
|
||||
onLoaded {
|
||||
if (it != null) {
|
||||
reason = it.message
|
||||
|
||||
stopSelf()
|
||||
service.stopSelfForReason(it.message)
|
||||
} else {
|
||||
service.broadcastProfileLoaded()
|
||||
}
|
||||
@@ -46,9 +41,7 @@ class ClashService : BaseService() {
|
||||
}
|
||||
runtime.install(CloseModule()) {
|
||||
onClosed {
|
||||
reason = null
|
||||
|
||||
stopSelf()
|
||||
service.stopSelfForReason(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,4 +71,10 @@ class ClashService : BaseService() {
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun stopSelfForReason(reason: String?) {
|
||||
this.reason = reason
|
||||
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,12 @@ import com.github.kr328.clash.service.model.asEntity
|
||||
import com.github.kr328.clash.service.util.resolveBaseDir
|
||||
import com.github.kr328.clash.service.util.resolveProfileFile
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.Exception
|
||||
import java.lang.NullPointerException
|
||||
import java.util.*
|
||||
|
||||
object ProfileProcessor {
|
||||
@@ -56,14 +58,17 @@ object ProfileProcessor {
|
||||
source: Uri,
|
||||
target: File,
|
||||
baseDir: File
|
||||
) = withContext(Dispatchers.IO) {
|
||||
) {
|
||||
when (source.scheme?.toLowerCase(Locale.getDefault())) {
|
||||
"http", "https" ->
|
||||
Clash.downloadProfile(source.toString(), target, baseDir)
|
||||
"content", "file", "resource" -> {
|
||||
val fd = context.contentResolver.openFileDescriptor(source, "r")
|
||||
?: throw FileNotFoundException("$source not found")
|
||||
Clash.downloadProfile(fd.detachFd(), target, baseDir)
|
||||
val fd = withContext(Dispatchers.IO) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
context.contentResolver.openFileDescriptor(source, "r")
|
||||
} ?: throw FileNotFoundException("$source not found")
|
||||
|
||||
Clash.downloadProfile(fd, target, baseDir)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Invalid uri type")
|
||||
}.await()
|
||||
|
||||
@@ -45,11 +45,11 @@ class ProfileReceiver : BroadcastReceiver() {
|
||||
val metadata = ProfileDao.queryById(id)?.asProfile(context) ?: return
|
||||
val service = context.getSystemService<AlarmManager>() ?: return
|
||||
|
||||
val pendingIntent = cancelNextUpdate(context, id)
|
||||
|
||||
if (metadata.interval <= 0)
|
||||
return
|
||||
|
||||
val pendingIntent = cancelNextUpdate(context, id)
|
||||
|
||||
service.set(
|
||||
AlarmManager.RTC,
|
||||
metadata.lastModified + metadata.interval,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user