mirror of
https://github.com/MetaCubeX/ClashMetaForAndroid.git
synced 2026-05-09 18:11:26 +08:00
Compare commits
35 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 |
16
.github/ISSUE_TEMPLATE/01-bug-report-en.md
vendored
16
.github/ISSUE_TEMPLATE/01-bug-report-en.md
vendored
@@ -43,9 +43,15 @@ If applicable, add screenshots to help explain your problem.
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
<!--
|
||||
*Logs*
|
||||
if applicable, upload logs to help detect problem
|
||||
**Configure**
|
||||
If applicable, paste **removed server info** configure
|
||||
```yaml
|
||||
# paste here
|
||||
```
|
||||
|
||||
`Open App` -> `Support` -> `Feedback` -> `Upload Logcat`
|
||||
-->
|
||||
**Logs**
|
||||
If applicable, paste logs to help detect problem
|
||||
|
||||
```
|
||||
<paste here>
|
||||
```
|
||||
16
.github/ISSUE_TEMPLATE/03-bug-report-zh-cn.md
vendored
16
.github/ISSUE_TEMPLATE/03-bug-report-zh-cn.md
vendored
@@ -41,9 +41,15 @@ assignees: ''
|
||||
**附加信息**
|
||||
其他的可能与改错误相关的信息
|
||||
|
||||
<!--
|
||||
*日志*
|
||||
如果适用, 上传日志以帮助侦测错误
|
||||
**配置文件**
|
||||
如果适用, 在此粘贴 **去除服务器信息的** 的 **配置文件**
|
||||
```yaml
|
||||
# 在此粘贴
|
||||
```
|
||||
|
||||
**日志**
|
||||
如果适用, 粘贴日志以帮助侦测错误
|
||||
```
|
||||
<在此粘贴>
|
||||
```
|
||||
|
||||
`打开应用` -> `支持` -> `反馈` -> `上传日志`
|
||||
-->
|
||||
@@ -1,5 +1,4 @@
|
||||
import java.util.*
|
||||
import java.security.*
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
@@ -7,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)
|
||||
@@ -38,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")
|
||||
@@ -74,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"))
|
||||
@@ -82,7 +79,7 @@ android {
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
maybeCreate("release").apply {
|
||||
named("release") {
|
||||
this.signingConfig = signingConfigs.findByName("release")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -190,7 +190,7 @@ object Remote {
|
||||
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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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.72"
|
||||
val gKotlinVersion: String by project
|
||||
|
||||
rootProject.extra.apply {
|
||||
this["gBuildToolsVersion"] = "29.0.3"
|
||||
|
||||
this["gCompileSdkVersion"] = 29
|
||||
this["gMinSdkVersion"] = 24
|
||||
this["gTargetSdkVersion"] = 29
|
||||
|
||||
this["gVersionCode"] = 10214
|
||||
this["gVersionName"] = "1.2.14"
|
||||
|
||||
this["gKotlinVersion"] = kotlinVersion
|
||||
this["gKotlinCoroutineVersion"] = "1.3.6"
|
||||
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-rc01")
|
||||
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["resetGolangPathMode"])
|
||||
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,110 +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("assembleClashCore") {
|
||||
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["assembleClashCore"])
|
||||
|
||||
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") {
|
||||
dependsOn(tasks["extractSources"])
|
||||
val geoipFile = geoipOutput.resolve("Country.mmdb")
|
||||
|
||||
onlyIf {
|
||||
val file = buildDir.resolve(Constants.OUTPUT_PATH).resolve("assets/Country.mmdb")
|
||||
|
||||
System.currentTimeMillis() - file.lastModified() > Constants.GEOIP_INVALID_INTERVAL
|
||||
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("resetGolangPathMode", 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,148 +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 {
|
||||
log.Infoln("Set %s: Not such proxy group", name)
|
||||
return false
|
||||
}
|
||||
|
||||
pb, ok := p.(*outbound.Proxy)
|
||||
if !ok {
|
||||
log.Infoln("Set %s: Not a proxy object", name)
|
||||
return false
|
||||
}
|
||||
|
||||
selector, ok := pb.ProxyAdapter.(*outboundgroup.Selector)
|
||||
if !ok {
|
||||
log.Infoln("Set %s: Not a selector group", name)
|
||||
return false
|
||||
}
|
||||
|
||||
selected := selector.Now()
|
||||
if selected == proxy {
|
||||
log.Infoln("Set " + name + " -> " + proxy)
|
||||
return true
|
||||
}
|
||||
|
||||
if err := selector.Set(proxy); err != nil {
|
||||
log.Infoln("Set %s: %s", name, err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
for _, conn := range tunnel.DefaultManager.Snapshot().Connections {
|
||||
for _, p := range conn.Chain() {
|
||||
if p == name {
|
||||
_ = conn.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,57 +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) error {
|
||||
dialer.Control = onNewSocket
|
||||
return nil
|
||||
}
|
||||
|
||||
func onNewListenConfig(listen *net.ListenConfig) error {
|
||||
listen.Control = onNewSocket
|
||||
return nil
|
||||
}
|
||||
|
||||
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: ff4ad6d96a...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",
|
||||
}
|
||||
@@ -78,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
|
||||
@@ -87,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
|
||||
|
||||
@@ -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
|
||||
@@ -4,7 +4,7 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/clash v0.0.0 // local
|
||||
github.com/kr328/tun2socket v0.0.0-20200511061008-edd2b9608763
|
||||
github.com/kr328/tun2socket v0.0.0-20200613032901-7ffeefc227e3
|
||||
github.com/miekg/dns v1.1.29
|
||||
)
|
||||
|
||||
|
||||
@@ -1,84 +1,45 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.5 h1:BizWSjmwzAyQoslz6YhJYMiAGT99j9cnm9zlxVr+kyI=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.5/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
|
||||
github.com/cenkalti/backoff v0.0.0-20190506075156-2146c9339422/go.mod h1:b6Nc7NRH5C4aCISLry0tLnTjcuTEvoiqcWDdsU0sOGM=
|
||||
github.com/comzyh/gvisor v0.0.0-20200510171600-c4d4be34b573/go.mod h1:h+6z+LgXLDMOwidDk/XF0VPA+V6HLTWBWkwmRjgWwF8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
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/flock v0.6.1-0.20180915234121-886344bea079/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/subcommands v0.0.0-20190508160503-636abe8753b8/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr328/tun2socket v0.0.0-20200511040303-5c6e74fe4a3c h1:o0m4oU/loVTbzdt9SopN0d4WRn19LP6VBdEjy0vRBQo=
|
||||
github.com/kr328/tun2socket v0.0.0-20200511040303-5c6e74fe4a3c/go.mod h1:FWfSixjrLgtK+dHkDoN6lHMNhvER24gnjUZd/wt8Z9o=
|
||||
github.com/kr328/tun2socket v0.0.0-20200511061008-edd2b9608763 h1:VniQVXI2Nfa9RrqkoGpjUHnVh1wIvJWVXGDhzs/kvcA=
|
||||
github.com/kr328/tun2socket v0.0.0-20200511061008-edd2b9608763/go.mod h1:FWfSixjrLgtK+dHkDoN6lHMNhvER24gnjUZd/wt8Z9o=
|
||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
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/opencontainers/runtime-spec v0.1.2-0.20171211145439-b2d941ef6a78/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
||||
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
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/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 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/vishvananda/netlink v1.0.1-0.20190318003149-adb577d4a45e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
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-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU=
|
||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/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-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI=
|
||||
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
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 h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
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/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
|
||||
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 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
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()
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -60,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 {
|
||||
|
||||
@@ -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,7 +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
|
||||
|
||||
org.gradle.parallel=true
|
||||
# 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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -19,7 +19,6 @@ class TunService : VpnService(), CoroutineScope by MainScope() {
|
||||
private const val PRIVATE_VLAN4_CLIENT = "172.31.255.253"
|
||||
private const val PRIVATE_VLAN4_MIRROR = "172.31.255.254"
|
||||
private const val PRIVATE_VLAN_DNS = "198.18.0.1"
|
||||
private const val VLAN_ANY = "0.0.0.0/0"
|
||||
}
|
||||
|
||||
private val service = this
|
||||
|
||||
@@ -5,23 +5,23 @@ import com.github.kr328.clash.core.Clash
|
||||
class DnsInjectModule : Module() {
|
||||
var dnsOverride: Boolean = false
|
||||
set(value) {
|
||||
Clash.setDnsOverrideEnabled(value)
|
||||
field = value
|
||||
|
||||
Clash.setDnsOverride(value, appendDns)
|
||||
}
|
||||
var appendDns: List<String> = emptyList()
|
||||
set(value) {
|
||||
Clash.appendDns(value)
|
||||
field = value
|
||||
|
||||
Clash.setDnsOverride(dnsOverride, value)
|
||||
}
|
||||
|
||||
override suspend fun onStart() {
|
||||
Clash.setDnsOverrideEnabled(dnsOverride)
|
||||
Clash.appendDns(appendDns)
|
||||
Clash.setDnsOverride(dnsOverride, appendDns)
|
||||
}
|
||||
|
||||
override suspend fun onStop() {
|
||||
Clash.setDnsOverrideEnabled(false)
|
||||
Clash.appendDns(emptyList())
|
||||
Clash.setDnsOverride(false, emptyList())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -61,7 +61,7 @@ class DynamicNotificationModule(private val service: Service) : Module() {
|
||||
}
|
||||
|
||||
override suspend fun onTick() {
|
||||
val traffic = Clash.queryTraffic()
|
||||
val traffic = Clash.querySpeed()
|
||||
val bandwidth = Clash.queryBandwidth()
|
||||
|
||||
val uploading = traffic.upload.asSpeedString()
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.github.kr328.clash.service.data.ProfileDao
|
||||
import com.github.kr328.clash.service.data.SelectedProxyDao
|
||||
import com.github.kr328.clash.service.util.resolveBaseDir
|
||||
import com.github.kr328.clash.service.util.resolveProfileFile
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
||||
class ReloadModule(private val context: Context) : Module() {
|
||||
@@ -50,7 +51,7 @@ class ReloadModule(private val context: Context) : Module() {
|
||||
).await()
|
||||
|
||||
val remove = SelectedProxyDao.querySelectedForProfile(active.id)
|
||||
.filterNot { Clash.setSelectedProxy(it.proxy, it.selected) }
|
||||
.filterNot { Clash.setSelector(it.proxy, it.selected) }
|
||||
.map { it.selected }
|
||||
|
||||
SelectedProxyDao.removeSelectedForProfile(active.id, remove)
|
||||
|
||||
Reference in New Issue
Block a user