mirror of
https://github.com/MetaCubeX/ClashMetaForAndroid.git
synced 2026-05-09 18:11:26 +08:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73992dca54 | ||
|
|
53dc20109d | ||
|
|
e7fef0a767 | ||
|
|
c73beabf7e | ||
|
|
c7409d7ac6 | ||
|
|
5e238ab5d3 | ||
|
|
a8f502ef4f | ||
|
|
c48ce82640 | ||
|
|
5594485bec | ||
|
|
f967bd299a | ||
|
|
a75c8f5dfc | ||
|
|
271d56c01c | ||
|
|
12220789a3 | ||
|
|
ca85a688cc | ||
|
|
0e3ed548c9 | ||
|
|
27dc5395e0 | ||
|
|
4f05ba1ac6 | ||
|
|
1a36218c80 | ||
|
|
3125b90efe | ||
|
|
2a7425700a | ||
|
|
f8a3308b46 | ||
|
|
93aed001af | ||
|
|
44461b67ee | ||
|
|
8a55dc1945 | ||
|
|
3af16bcf34 | ||
|
|
9adf0f00ae | ||
|
|
a2f3330455 | ||
|
|
7cfefbca19 | ||
|
|
e8eddb3917 | ||
|
|
634c25894c | ||
|
|
42f5331a0c | ||
|
|
e2383df06b | ||
|
|
7381b787f3 | ||
|
|
eff310ac40 | ||
|
|
c245fc6dfa | ||
|
|
b8f0c32ad5 |
24
.github/workflows/build-unsigned.yaml
vendored
Normal file
24
.github/workflows/build-unsigned.yaml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Build Unsigned
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
BuildUnsigned:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v2
|
||||
- name: Setup Cmake & Ninja
|
||||
uses: lukka/get-cmake@latest
|
||||
- name: Build
|
||||
run: ./gradlew --no-daemon app:assembleRelease
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -4,3 +4,6 @@
|
||||
[submodule "kaidl"]
|
||||
path = kaidl
|
||||
url = https://github.com/Kr328/kaidl.git
|
||||
[submodule "core/src/main/golang/tun2socket"]
|
||||
path = core/src/main/golang/tun2socket
|
||||
url = https://github.com/Kr328/tun2socket-lwip.git
|
||||
|
||||
@@ -36,13 +36,12 @@ See also [PRIVACY_POLICY.md](./PRIVACY_POLICY.md)
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
2. Install `JDK 1.8`, `Android SDK` and `Golang`
|
||||
2. Install **OpenJDK 11**, **Android SDK**, **CMake** and **Golang**
|
||||
|
||||
3. Create `local.properties` in project root with
|
||||
|
||||
```properties
|
||||
sdk.dir=/path/to/android-sdk
|
||||
appcenter.key=<AppCenter Key> # Optional, from "appcenter.ms"
|
||||
```
|
||||
|
||||
4. Create `keystore.properties` in project root with
|
||||
@@ -57,7 +56,7 @@ See also [PRIVACY_POLICY.md](./PRIVACY_POLICY.md)
|
||||
5. Build
|
||||
|
||||
```bash
|
||||
./gradlew app:assembleRelease
|
||||
./gradlew app:assembleFossRelease
|
||||
```
|
||||
|
||||
6. Pick `app-release-<arch>.apk` in `app/build/outputs/apks`
|
||||
@@ -47,13 +47,22 @@ android {
|
||||
dimension = "premium"
|
||||
versionNameSuffix = ".premium"
|
||||
|
||||
val appCenterKey = rootProject.file("local.properties").inputStream()
|
||||
.use { Properties().apply { load(it) } }
|
||||
.getProperty("appcenter.key", null)
|
||||
if (buildFlavor == "premium") {
|
||||
val localFile = rootProject.file("local.properties")
|
||||
if (localFile.exists()) {
|
||||
val appCenterKey = localFile.inputStream()
|
||||
.use { Properties().apply { load(it) } }
|
||||
.getProperty("appcenter.key", null)
|
||||
|
||||
Objects.requireNonNull(appCenterKey)
|
||||
|
||||
buildConfigField("String", "APP_CENTER_KEY", "\"$appCenterKey\"")
|
||||
if (appCenterKey != null) {
|
||||
buildConfigField("String", "APP_CENTER_KEY", "\"$appCenterKey\"")
|
||||
} else {
|
||||
buildConfigField("String", "APP_CENTER_KEY", "null")
|
||||
}
|
||||
} else {
|
||||
buildConfigField("String", "APP_CENTER_KEY", "null")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +122,7 @@ dependencies {
|
||||
|
||||
implementation(kotlin("stdlib-jdk7"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
|
||||
implementation("androidx.core:core-ktx:$ktxVersion")
|
||||
implementation("androidx.core:core-ktx:$coreVersion")
|
||||
implementation("androidx.activity:activity:$activityVersion")
|
||||
implementation("androidx.appcompat:appcompat:$appcompatVersion")
|
||||
implementation("androidx.coordinatorlayout:coordinatorlayout:$coordinatorlayoutVersion")
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="true"
|
||||
android:label="@string/launch_name">
|
||||
android:label="@string/launch_name"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ class AppCrashedActivity : BaseActivity<AppCrashedDesign>() {
|
||||
SystemLogcat.dumpCrash()
|
||||
}
|
||||
|
||||
Tracker.uploadLogcat(logs)
|
||||
|
||||
design.setAppLogs(logs)
|
||||
|
||||
while (isActive) {
|
||||
|
||||
@@ -114,7 +114,7 @@ abstract class BaseActivity<D : Design<*>> :
|
||||
|
||||
Remote.broadcasts.addObserver(this)
|
||||
|
||||
events.offer(Event.ActivityStart)
|
||||
events.trySend(Event.ActivityStart)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
@@ -124,7 +124,7 @@ abstract class BaseActivity<D : Design<*>> :
|
||||
|
||||
Remote.broadcasts.removeObserver(this)
|
||||
|
||||
events.offer(Event.ActivityStop)
|
||||
events.trySend(Event.ActivityStop)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -174,23 +174,23 @@ abstract class BaseActivity<D : Design<*>> :
|
||||
}
|
||||
|
||||
override fun onProfileChanged() {
|
||||
events.offer(Event.ProfileChanged)
|
||||
events.trySend(Event.ProfileChanged)
|
||||
}
|
||||
|
||||
override fun onProfileLoaded() {
|
||||
events.offer(Event.ProfileLoaded)
|
||||
events.trySend(Event.ProfileLoaded)
|
||||
}
|
||||
|
||||
override fun onServiceRecreated() {
|
||||
events.offer(Event.ServiceRecreated)
|
||||
events.trySend(Event.ServiceRecreated)
|
||||
}
|
||||
|
||||
override fun onStarted() {
|
||||
events.offer(Event.ClashStart)
|
||||
events.trySend(Event.ClashStart)
|
||||
}
|
||||
|
||||
override fun onStopped(cause: String?) {
|
||||
events.offer(Event.ClashStop)
|
||||
events.trySend(Event.ClashStop)
|
||||
|
||||
if (cause != null && activityStarted) {
|
||||
launch {
|
||||
|
||||
@@ -141,7 +141,7 @@ class FilesActivity : BaseActivity<FilesDesign>() {
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
design?.requests?.offer(FilesDesign.Request.PopStack)
|
||||
design?.requests?.trySend(FilesDesign.Request.PopStack)
|
||||
}
|
||||
|
||||
private suspend fun FilesDesign.fetch(client: FilesClient, stack: Stack<String>, root: String) {
|
||||
|
||||
@@ -20,9 +20,9 @@ import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.core.model.LogMessage
|
||||
import com.github.kr328.clash.log.LogcatCache
|
||||
import com.github.kr328.clash.log.LogcatWriter
|
||||
import com.github.kr328.clash.service.ClashManager
|
||||
import com.github.kr328.clash.service.remote.IClashManager
|
||||
import com.github.kr328.clash.service.RemoteService
|
||||
import com.github.kr328.clash.service.remote.ILogObserver
|
||||
import com.github.kr328.clash.service.remote.IRemoteService
|
||||
import com.github.kr328.clash.service.remote.unwrap
|
||||
import com.github.kr328.clash.util.logsDir
|
||||
import kotlinx.coroutines.*
|
||||
@@ -52,7 +52,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
|
||||
showNotification()
|
||||
|
||||
bindService(ClashManager::class.intent, connection, Context.BIND_AUTO_CREATE)
|
||||
bindService(RemoteService::class.intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -88,7 +88,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
return stopSelf()
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
val service = binder.unwrap(IClashManager::class)
|
||||
val service = binder.unwrap(IRemoteService::class).clash()
|
||||
val channel = Channel<LogMessage>(CACHE_CAPACITY)
|
||||
|
||||
try {
|
||||
@@ -97,7 +97,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
LogcatWriter(this@LogcatService).use {
|
||||
val observer = object : ILogObserver {
|
||||
override fun newItem(log: LogMessage) {
|
||||
channel.offer(log)
|
||||
channel.trySend(log)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class LogsActivity : BaseActivity<LogsDesign>() {
|
||||
deleteAllLogs()
|
||||
}
|
||||
|
||||
events.offer(Event.ActivityStart)
|
||||
events.trySend(Event.ActivityStart)
|
||||
}
|
||||
}
|
||||
is LogsDesign.Request.OpenFile -> {
|
||||
|
||||
@@ -32,4 +32,8 @@ class MainApplication : Application() {
|
||||
sendServiceRecreated()
|
||||
}
|
||||
}
|
||||
|
||||
fun finalize() {
|
||||
Global.destroy()
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
}
|
||||
ProxyDesign.Request.ReloadAll -> {
|
||||
names.indices.forEach { idx ->
|
||||
design.requests.offer(ProxyDesign.Request.Reload(idx))
|
||||
design.requests.trySend(ProxyDesign.Request.Reload(idx))
|
||||
}
|
||||
}
|
||||
is ProxyDesign.Request.Reload -> {
|
||||
|
||||
@@ -8,7 +8,8 @@ object SystemLogcat {
|
||||
"Go",
|
||||
"DEBUG",
|
||||
"AndroidRuntime",
|
||||
"ClashForAndroid"
|
||||
"ClashForAndroid",
|
||||
"LwIP",
|
||||
)
|
||||
|
||||
fun dumpCrash(): String {
|
||||
|
||||
@@ -10,13 +10,12 @@ import com.github.kr328.clash.store.AppStore
|
||||
import com.github.kr328.clash.util.ApplicationObserver
|
||||
import com.github.kr328.clash.util.verifyApk
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object Remote {
|
||||
val broadcasts: Broadcasts = Broadcasts(Global.application)
|
||||
val services: Services = Services(Global.application) {
|
||||
val service: Service = Service(Global.application) {
|
||||
ApplicationObserver.createdActivities.forEach { it.finish() }
|
||||
|
||||
val intent = AppCrashedActivity::class.intent
|
||||
@@ -30,9 +29,9 @@ object Remote {
|
||||
fun launch() {
|
||||
ApplicationObserver.attach(Global.application)
|
||||
|
||||
ApplicationObserver.onVisibleChanged(visible::offer)
|
||||
ApplicationObserver.onVisibleChanged { visible.trySend(it) }
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
Global.launch(Dispatchers.IO) {
|
||||
run()
|
||||
}
|
||||
}
|
||||
@@ -57,10 +56,10 @@ object Remote {
|
||||
|
||||
while (true) {
|
||||
if (visible.receive()) {
|
||||
services.bind()
|
||||
service.bind()
|
||||
broadcasts.register()
|
||||
} else {
|
||||
services.unbind()
|
||||
service.unbind()
|
||||
broadcasts.unregister()
|
||||
}
|
||||
}
|
||||
|
||||
64
app/src/main/java/com/github/kr328/clash/remote/Service.kt
Normal file
64
app/src/main/java/com/github/kr328/clash/remote/Service.kt
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.github.kr328.clash.remote
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import com.github.kr328.clash.Tracker
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.log.SystemLogcat
|
||||
import com.github.kr328.clash.service.RemoteService
|
||||
import com.github.kr328.clash.service.remote.IRemoteService
|
||||
import com.github.kr328.clash.service.remote.unwrap
|
||||
import com.github.kr328.clash.util.unbindServiceSilent
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class Service(private val context: Application, val crashed: () -> Unit) {
|
||||
val remote = Resource<IRemoteService>()
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
private var lastCrashed: Long = -1
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder) {
|
||||
remote.set(service.unwrap(IRemoteService::class))
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
remote.set(null)
|
||||
|
||||
Tracker.uploadLogcat(SystemLogcat.dumpCrash())
|
||||
|
||||
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
|
||||
lastCrashed = System.currentTimeMillis()
|
||||
|
||||
Log.w("RemoteManager crashed")
|
||||
}
|
||||
}
|
||||
|
||||
fun bind() {
|
||||
try {
|
||||
context.bindService(RemoteService::class.intent, connection, Context.BIND_AUTO_CREATE)
|
||||
} catch (e: Exception) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
context.unbindServiceSilent(connection)
|
||||
|
||||
remote.set(null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TOGGLE_CRASHED_INTERVAL = TimeUnit.SECONDS.toMillis(10)
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.github.kr328.clash.remote
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.service.ClashManager
|
||||
import com.github.kr328.clash.service.ProfileService
|
||||
import com.github.kr328.clash.service.remote.IClashManager
|
||||
import com.github.kr328.clash.service.remote.IProfileManager
|
||||
import com.github.kr328.clash.service.remote.unwrap
|
||||
import com.github.kr328.clash.util.unbindServiceSilent
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class Services(private val context: Application, val crashed: () -> Unit) {
|
||||
val clash = Resource<IClashManager>()
|
||||
val profile = Resource<IProfileManager>()
|
||||
|
||||
private val clashConnection = object : ServiceConnection {
|
||||
private var lastCrashed: Long = -1
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
clash.set(service?.unwrap(IClashManager::class))
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
clash.set(null)
|
||||
|
||||
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
|
||||
lastCrashed = System.currentTimeMillis()
|
||||
|
||||
Log.w("ClashManager crashed")
|
||||
}
|
||||
}
|
||||
|
||||
private val profileConnection = object : ServiceConnection {
|
||||
private var lastCrashed: Long = -1
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
profile.set(service?.unwrap(IProfileManager::class))
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
profile.set(null)
|
||||
|
||||
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
|
||||
lastCrashed = System.currentTimeMillis()
|
||||
|
||||
Log.w("ProfileService crashed")
|
||||
}
|
||||
}
|
||||
|
||||
fun bind() {
|
||||
try {
|
||||
context.bindService(ClashManager::class.intent, clashConnection, Context.BIND_AUTO_CREATE)
|
||||
context.bindService(ProfileService::class.intent, profileConnection, Context.BIND_AUTO_CREATE)
|
||||
} catch (e: Exception) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
context.unbindServiceSilent(clashConnection)
|
||||
context.unbindServiceSilent(profileConnection)
|
||||
|
||||
clash.set(null)
|
||||
profile.set(null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TOGGLE_CRASHED_INTERVAL = TimeUnit.SECONDS.toMillis(10)
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,15 @@ suspend fun <T> withClash(
|
||||
block: suspend IClashManager.() -> T
|
||||
): T {
|
||||
while (true) {
|
||||
val client = Remote.services.clash.get()
|
||||
val remote = Remote.service.remote.get()
|
||||
val client = remote.clash()
|
||||
|
||||
try {
|
||||
return withContext(context) { client.block() }
|
||||
} catch (e: DeadObjectException) {
|
||||
Log.w("Remote services panic")
|
||||
|
||||
Remote.services.clash.reset(client)
|
||||
Remote.service.remote.reset(remote)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,14 +32,15 @@ suspend fun <T> withProfile(
|
||||
block: suspend IProfileManager.() -> T
|
||||
): T {
|
||||
while (true) {
|
||||
val client = Remote.services.profile.get()
|
||||
val remote = Remote.service.remote.get()
|
||||
val client = remote.profile()
|
||||
|
||||
try {
|
||||
return withContext(context) { client.block() }
|
||||
} catch (e: DeadObjectException) {
|
||||
Log.w("Remote services panic")
|
||||
|
||||
Remote.services.profile.reset(client)
|
||||
Remote.service.remote.reset(remote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ dependencies {
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("golang") {
|
||||
id = "library-golang"
|
||||
implementationClass = "LibraryGolangPlugin"
|
||||
id = "clash-build"
|
||||
implementationClass = "com.github.kr328.clash.tools.ClashBuildPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import org.gradle.api.Project
|
||||
|
||||
const val buildVersionCode = 204001
|
||||
const val buildVersionName = "2.4.1"
|
||||
const val buildVersionCode = 204004
|
||||
const val buildVersionName = "2.4.4"
|
||||
|
||||
const val buildMinSdkVersion = 21
|
||||
const val buildTargetSdkVersion = 30
|
||||
|
||||
const val buildNdkVersion = "23.0.7123448"
|
||||
const val buildNdkVersion = "22.1.7171670"
|
||||
|
||||
val Project.buildFlavor: String
|
||||
get() {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const val activityVersion = "1.2.3"
|
||||
const val coroutineVersion = "1.4.3"
|
||||
const val coroutineVersion = "1.5.0"
|
||||
const val roomVersion = "2.3.0"
|
||||
const val ktxVersion = "1.3.2"
|
||||
const val appcompatVersion = "1.2.0"
|
||||
const val coreVersion = "1.5.0"
|
||||
const val appcompatVersion = "1.3.0"
|
||||
const val muiltprocessVersion = "1.0.0"
|
||||
const val appcenterVersion = "4.1.1"
|
||||
const val serializationVersion = "1.2.1"
|
||||
const val materialVersion = "1.3.0"
|
||||
const val coordinatorlayoutVersion = "1.1.0"
|
||||
const val recyclerviewVersion = "1.2.0"
|
||||
const val fragmentVersion = "1.3.3"
|
||||
const val fragmentVersion = "1.3.4"
|
||||
const val viewpagerVersion = "1.0.0"
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleScriptException
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.tasks.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
|
||||
abstract class GolangBuildTask : DefaultTask() {
|
||||
abstract val debug: Property<Boolean>
|
||||
@Input get
|
||||
|
||||
abstract val premium: Property<Boolean>
|
||||
@Input get
|
||||
|
||||
abstract val nativeAbis: SetProperty<String>
|
||||
@Input get
|
||||
|
||||
abstract val minSdkVersion: Property<Int>
|
||||
@Input get
|
||||
|
||||
abstract val cCompilerBasePath: DirectoryProperty
|
||||
@InputDirectory get
|
||||
|
||||
abstract val inputDirectory: DirectoryProperty
|
||||
@InputDirectory get
|
||||
|
||||
abstract val outputDirectory: DirectoryProperty
|
||||
@OutputDirectory get
|
||||
|
||||
@TaskAction
|
||||
fun build() {
|
||||
val src = inputDirectory.get().asFile
|
||||
|
||||
val cmd = if (debug.get()) {
|
||||
"""
|
||||
go build --buildmode=c-shared -trimpath -o "%s" -tags "without_gvisor,without_system,debug${if (premium.get()) ",premium" else ""}"
|
||||
""".trimIndent().trim()
|
||||
} else {
|
||||
"""
|
||||
go build --buildmode=c-shared -trimpath -o "%s" -tags "without_gvisor,without_system${if (premium.get()) ",premium" else ""}" -ldflags "-s -w"
|
||||
""".trimIndent().trim()
|
||||
}
|
||||
|
||||
"go mod tidy".exec(pwd = src)
|
||||
|
||||
nativeAbis.get().parallelStream().forEach {
|
||||
val out = outputDirectory.get().file("$it/libclash.so")
|
||||
|
||||
cmd.format(out).exec(pwd = src, env = generateGolangBuildEnvironment(it))
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateGolangBuildEnvironment(abi: String): Map<String, String> {
|
||||
val (goArch, goArm) = when (abi) {
|
||||
"arm64-v8a" -> "arm64" to ""
|
||||
"armeabi-v7a" -> "arm" to "7"
|
||||
"x86" -> "386" to ""
|
||||
"x86_64" -> "amd64" to ""
|
||||
else -> throw UnsupportedOperationException("unsupported abi: $abi")
|
||||
}
|
||||
|
||||
val compiler = when (abi) {
|
||||
"armeabi-v7a" ->
|
||||
"armv7a-linux-androideabi${minSdkVersion.get()}-clang"
|
||||
"arm64-v8a" ->
|
||||
"aarch64-linux-android${minSdkVersion.get()}-clang"
|
||||
"x86" ->
|
||||
"i686-linux-android${minSdkVersion.get()}-clang"
|
||||
"x86_64" ->
|
||||
"x86_64-linux-android${minSdkVersion.get()}-clang"
|
||||
else ->
|
||||
throw GradleScriptException(
|
||||
"Unsupported abi $abi",
|
||||
FileNotFoundException("Unsupported abi $abi")
|
||||
)
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
"CC" to cCompilerBasePath.get().asFile.resolve(compiler).absolutePath,
|
||||
"GOOS" to "android",
|
||||
"GOARCH" to goArch,
|
||||
"GOARM" to goArm,
|
||||
"CGO_ENABLED" to "1",
|
||||
"CFLAGS" to "-O3 -Werror",
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.exec(
|
||||
pwd: File,
|
||||
env: Map<String, String> = System.getenv()
|
||||
): String {
|
||||
val process = ProcessBuilder().run {
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS))
|
||||
command("cmd.exe", "/c", this@exec)
|
||||
else
|
||||
command("bash", "-c", this@exec)
|
||||
|
||||
environment().putAll(env)
|
||||
directory(pwd)
|
||||
|
||||
redirectErrorStream(true)
|
||||
|
||||
start()
|
||||
}
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
process.inputStream.copyTo(outputStream)
|
||||
|
||||
if (process.waitFor() != 0) {
|
||||
println(outputStream.toString("utf-8"))
|
||||
throw GradleScriptException("Exec $this failure", IOException())
|
||||
}
|
||||
|
||||
return outputStream.toString("utf-8")
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
import org.gradle.api.GradleScriptException
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.*
|
||||
|
||||
class LibraryGolangPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
target.extensions.getByType(LibraryExtension::class.java).apply {
|
||||
target.afterEvaluate {
|
||||
libraryVariants.forEach { variant ->
|
||||
val abis = defaultConfig.externalNativeBuild.cmake.abiFilters +
|
||||
defaultConfig.externalNativeBuild.ndkBuild.abiFilters
|
||||
|
||||
val nameCapitalize = variant.name.capitalize(Locale.getDefault())
|
||||
val golangBuildDir = target.golangBuild.resolve(variant.name)
|
||||
|
||||
val task = target.tasks.register(
|
||||
"externalGolangBuild$nameCapitalize",
|
||||
GolangBuildTask::class.java
|
||||
) {
|
||||
it.premium.set(variant.flavorName == "premium")
|
||||
it.debug.set(variant.name == "debug")
|
||||
it.nativeAbis.set(abis)
|
||||
it.minSdkVersion.set(defaultConfig.minSdk!!)
|
||||
it.cCompilerBasePath.set(compilerBasePath)
|
||||
it.inputDirectory.set(target.golangSource)
|
||||
it.outputDirectory.set(golangBuildDir)
|
||||
}
|
||||
|
||||
sourceSets.named(variant.name) {
|
||||
it.jniLibs {
|
||||
srcDir(golangBuildDir)
|
||||
}
|
||||
}
|
||||
|
||||
variant.externalNativeBuildProviders.forEach {
|
||||
it.get().dependsOn(task)
|
||||
}
|
||||
target.tasks.filter { it.name.startsWith("buildCMake") }.forEach {
|
||||
it.mustRunAfter(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val LibraryExtension.compilerBasePath: File
|
||||
get() {
|
||||
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")
|
||||
)
|
||||
}
|
||||
|
||||
return ndkDirectory.resolve("toolchains/llvm/prebuilt/$host-x86_64/bin")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.github.kr328.clash.tools
|
||||
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.build.gradle.api.BaseVariant
|
||||
import java.io.Serializable
|
||||
|
||||
data class BuildConfig(
|
||||
val debug: Boolean,
|
||||
val premium: Boolean,
|
||||
val abis: List<NativeAbi>,
|
||||
val minSdkVersion: Int,
|
||||
) : Serializable {
|
||||
companion object {
|
||||
fun of(abis: List<NativeAbi>, minSdkVersion: Int, variant: BaseVariant): BuildConfig {
|
||||
return BuildConfig(
|
||||
debug = variant.buildType.isDebuggable,
|
||||
premium = variant.flavorName == "premium",
|
||||
abis = abis,
|
||||
minSdkVersion = minSdkVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.github.kr328.clash.tools
|
||||
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
import golangBuild
|
||||
import golangSource
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import java.util.*
|
||||
|
||||
class ClashBuildPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
target.afterEvaluate {
|
||||
target.extensions.getByType(LibraryExtension::class.java).apply {
|
||||
val abis = defaultConfig.externalNativeBuild.cmake.abiFilters
|
||||
.map { NativeAbi.parse(it) }
|
||||
.distinct()
|
||||
val minSdkVersion = defaultConfig.minSdkVersion!!.apiLevel
|
||||
|
||||
target.tasks.register("cleanGolang", ClashCleanTask::class.java) {
|
||||
it.applyFrom(target, abis)
|
||||
|
||||
target.tasks.getByName("clean").dependsOn(it)
|
||||
}
|
||||
|
||||
libraryVariants.forEach { variant ->
|
||||
val config = BuildConfig.of(abis, minSdkVersion, variant)
|
||||
val buildDir = target.golangBuild.resolve(variant.name)
|
||||
val capitalize = variant.name.capitalize(Locale.getDefault())
|
||||
|
||||
val task = target.tasks.register(
|
||||
"externalGolangBuild$capitalize",
|
||||
ClashBuildTask::class.java
|
||||
) {
|
||||
it.config.set(config)
|
||||
it.ndkDirectory.set(ndkDirectory)
|
||||
it.inputDirectory.set(target.golangSource)
|
||||
it.outputDirectory.set(buildDir)
|
||||
}
|
||||
|
||||
sourceSets.named(variant.name) {
|
||||
it.jniLibs {
|
||||
srcDir(buildDir)
|
||||
}
|
||||
}
|
||||
|
||||
variant.externalNativeBuildProviders.forEach {
|
||||
it.get().dependsOn(task)
|
||||
}
|
||||
target.tasks.filter { it.name.startsWith("buildCMake") }.forEach {
|
||||
it.mustRunAfter(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.github.kr328.clash.tools
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
|
||||
abstract class ClashBuildTask : DefaultTask() {
|
||||
abstract val config: Property<BuildConfig>
|
||||
@Input get
|
||||
|
||||
abstract val ndkDirectory: DirectoryProperty
|
||||
@InputDirectory get
|
||||
|
||||
abstract val inputDirectory: DirectoryProperty
|
||||
@InputDirectory get
|
||||
|
||||
abstract val outputDirectory: DirectoryProperty
|
||||
@OutputDirectory get
|
||||
|
||||
@TaskAction
|
||||
fun build() {
|
||||
val input = inputDirectory.file
|
||||
val output = outputDirectory.file
|
||||
|
||||
val config = config.get()
|
||||
val environment = Environment(ndkDirectory.file, config.minSdkVersion)
|
||||
|
||||
val tags = listOf("without_gvisor", "without_system") +
|
||||
(if (config.debug) listOf("debug") else emptyList()) +
|
||||
(if (config.premium) listOf("premium") else emptyList())
|
||||
|
||||
Command.ofGoModuleTidy(input).exec()
|
||||
|
||||
config.abis.forEach {
|
||||
Command.ofGoRun(
|
||||
"make/make.go",
|
||||
listOf("tun2socket", ".", "android", it.goArch),
|
||||
input.resolve("tun2socket"),
|
||||
environment.ofLwipBuild(it)
|
||||
).exec()
|
||||
|
||||
Command.ofGoBuild(
|
||||
"c-shared",
|
||||
output.resolve("${it.value}/libclash.so"),
|
||||
tags,
|
||||
!config.debug,
|
||||
input,
|
||||
environment.ofCoreBuild(it)
|
||||
).exec()
|
||||
}
|
||||
}
|
||||
|
||||
private val DirectoryProperty.file: File
|
||||
get() = get().asFile
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.github.kr328.clash.tools
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.Delete
|
||||
import golangSource
|
||||
|
||||
abstract class ClashCleanTask : Delete() {
|
||||
fun applyFrom(project: Project, abis: List<NativeAbi>) {
|
||||
val bridge = project.golangSource.resolve("tun2socket")
|
||||
|
||||
delete(bridge.resolve("build"))
|
||||
|
||||
abis.forEach {
|
||||
delete(bridge.resolve("build_android_${it.goArch}.go"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.github.kr328.clash.tools
|
||||
|
||||
import org.gradle.api.GradleException
|
||||
import java.io.File
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class Command(
|
||||
private val command: Array<String>,
|
||||
workingDir: File,
|
||||
environments: Map<String, String>
|
||||
) {
|
||||
private val processBuilder: ProcessBuilder = ProcessBuilder(*command)
|
||||
.redirectErrorStream(true)
|
||||
.directory(workingDir)
|
||||
.apply { environment().putAll(environments) }
|
||||
|
||||
fun exec() {
|
||||
val process = processBuilder.start()
|
||||
|
||||
thread {
|
||||
process.inputStream.copyTo(System.out)
|
||||
}
|
||||
|
||||
val result = process.waitFor()
|
||||
|
||||
if (result != 0) {
|
||||
throw GradleException("exec ${command.joinToString(" ")}: exit with $result")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ofGoModuleTidy(workingDir: File): Command {
|
||||
return Command(arrayOf("go", "mod", "tidy"), workingDir, System.getenv())
|
||||
}
|
||||
|
||||
fun ofGoBuild(
|
||||
mode: String,
|
||||
output: File,
|
||||
tags: List<String>,
|
||||
strip: Boolean,
|
||||
workingDir: File,
|
||||
environments: Map<String, String>
|
||||
): Command {
|
||||
val command = mutableListOf("go", "build")
|
||||
|
||||
// go build mode
|
||||
command += "-buildmode"
|
||||
command += mode
|
||||
|
||||
// output file
|
||||
command += "-o"
|
||||
command += output.absolutePath
|
||||
|
||||
// trim path prefix
|
||||
command += "-trimpath"
|
||||
|
||||
if (tags.isNotEmpty()) {
|
||||
command += "-tags"
|
||||
command += tags.joinToString(",")
|
||||
}
|
||||
|
||||
if (strip) {
|
||||
command += "-ldflags"
|
||||
command += "-s -w"
|
||||
}
|
||||
|
||||
return Command(command.toTypedArray(), workingDir, environments)
|
||||
}
|
||||
|
||||
fun ofGoRun(
|
||||
file: String,
|
||||
args: List<String>,
|
||||
workingDir: File,
|
||||
environments: Map<String, String>
|
||||
): Command {
|
||||
val command = mutableListOf("go", "run")
|
||||
|
||||
command += file
|
||||
command += args
|
||||
|
||||
return Command(command.toTypedArray(), workingDir, environments)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.github.kr328.clash.tools
|
||||
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
import org.gradle.api.GradleException
|
||||
import java.io.File
|
||||
|
||||
class Environment(
|
||||
private val ndkDirectory: File,
|
||||
private val minSdkVersion: Int,
|
||||
) {
|
||||
fun ofCoreBuild(abi: NativeAbi): Map<String, String> {
|
||||
val host = when {
|
||||
Os.isFamily(Os.FAMILY_WINDOWS) ->
|
||||
"windows"
|
||||
Os.isFamily(Os.FAMILY_MAC) ->
|
||||
"darwin"
|
||||
Os.isFamily(Os.FAMILY_UNIX) ->
|
||||
"linux"
|
||||
else ->
|
||||
throw GradleException("Unsupported host: ${System.getProperty("os.name")}")
|
||||
}
|
||||
|
||||
val compiler = ndkDirectory.resolve("toolchains/llvm/prebuilt/$host-x86_64/bin")
|
||||
.resolve("${abi.compiler}${minSdkVersion}-clang")
|
||||
|
||||
return mapOf(
|
||||
"CC" to compiler.absolutePath,
|
||||
"GOOS" to "android",
|
||||
"GOARCH" to abi.goArch,
|
||||
"GOARM" to abi.goArm,
|
||||
"CGO_ENABLED" to "1",
|
||||
"CFLAGS" to "-O3 -Werror",
|
||||
)
|
||||
}
|
||||
|
||||
fun ofLwipBuild(abi: NativeAbi): Map<String, String> {
|
||||
val host = when {
|
||||
Os.isFamily(Os.FAMILY_WINDOWS) ->
|
||||
"windows"
|
||||
Os.isFamily(Os.FAMILY_MAC) ->
|
||||
"darwin"
|
||||
Os.isFamily(Os.FAMILY_UNIX) ->
|
||||
"linux"
|
||||
else ->
|
||||
throw GradleException("Unsupported host: ${System.getProperty("os.name")}")
|
||||
}
|
||||
|
||||
val compiler = ndkDirectory.resolve("toolchains/llvm/prebuilt/$host-x86_64/bin")
|
||||
.resolve("${abi.compiler}${minSdkVersion}-clang")
|
||||
val ar = ndkDirectory.resolve("toolchains/llvm/prebuilt/$host-x86_64/bin")
|
||||
.resolve("${abi.archiver}-ar")
|
||||
|
||||
return mapOf(
|
||||
"CC" to compiler.absolutePath,
|
||||
"AR" to ar.absolutePath,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.kr328.clash.tools
|
||||
|
||||
enum class NativeAbi(
|
||||
val value: String,
|
||||
val compiler: String,
|
||||
val archiver: String,
|
||||
val goArch: String,
|
||||
val goArm: String
|
||||
) {
|
||||
ArmeabiV7a("armeabi-v7a", "armv7a-linux-androideabi", "arm-linux-androideabi", "arm", "7"),
|
||||
Arm64V8a("arm64-v8a", "aarch64-linux-android", "aarch64-linux-android", "arm64", ""),
|
||||
X86("x86", "i686-linux-android", "i686-linux-android", "386", ""),
|
||||
X64("x86_64", "x86_64-linux-android", "x86_64-linux-android", "amd64", "");
|
||||
|
||||
companion object {
|
||||
fun parse(value: String): NativeAbi {
|
||||
return when (value) {
|
||||
ArmeabiV7a.value -> ArmeabiV7a
|
||||
Arm64V8a.value -> Arm64V8a
|
||||
X86.value -> X86
|
||||
X64.value -> X64
|
||||
else -> throw IllegalArgumentException("unsupported abi $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ dependencies {
|
||||
compileOnly(project(":hideapi"))
|
||||
|
||||
implementation(kotlin("stdlib-jdk7"))
|
||||
implementation("androidx.core:core-ktx:$ktxVersion")
|
||||
implementation("androidx.core:core-ktx:$coreVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.github.kr328.clash.common
|
||||
|
||||
import android.app.Application
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
|
||||
object Global {
|
||||
object Global : CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||
val application: Application
|
||||
get() = application_
|
||||
|
||||
@@ -11,4 +14,8 @@ object Global {
|
||||
fun init(application: Application) {
|
||||
this.application_ = application
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import android.content.ComponentName
|
||||
import com.github.kr328.clash.common.util.packageName
|
||||
|
||||
object Components {
|
||||
val MAIN_ACTIVITY = ComponentName(packageName, "$packageName.MainActivity")
|
||||
val PROPERTIES_ACTIVITY = ComponentName(packageName, "$packageName.PropertiesActivity")
|
||||
private const val componentsPackageName = "com.github.kr328.clash"
|
||||
|
||||
val MAIN_ACTIVITY = ComponentName(packageName, "$componentsPackageName.MainActivity")
|
||||
val PROPERTIES_ACTIVITY = ComponentName(packageName, "$componentsPackageName.PropertiesActivity")
|
||||
}
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
id("kotlinx-serialization")
|
||||
id("library-golang")
|
||||
id("clash-build")
|
||||
}
|
||||
|
||||
val geoipDatabaseUrl =
|
||||
@@ -81,7 +81,7 @@ dependencies {
|
||||
api(project(":common"))
|
||||
|
||||
implementation(kotlin("stdlib-jdk7"))
|
||||
implementation("androidx.core:core-ktx:$ktxVersion")
|
||||
implementation("androidx.core:core-ktx:$coreVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
project(clash-bridge C)
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE on)
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
|
||||
|
||||
set(GO_OUTPUT_BASE ${GO_OUTPUT}/${FLAVOR_NAME})
|
||||
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
@@ -16,6 +19,7 @@ endif ()
|
||||
|
||||
include_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}")
|
||||
include_directories("${GO_SOURCE}")
|
||||
include_directories("${GO_SOURCE}/tun2socket/bridge/native")
|
||||
|
||||
link_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}")
|
||||
|
||||
|
||||
@@ -97,17 +97,14 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyInstalledAppChanged(J
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobject thiz, jint fd,
|
||||
jint mtu, jstring gateway,
|
||||
jstring mirror, jstring dns,
|
||||
jint mtu, jstring dns,
|
||||
jobject cb) {
|
||||
TRACE_METHOD();
|
||||
|
||||
scoped_string _gateway = get_string(gateway);
|
||||
scoped_string _mirror = get_string(mirror);
|
||||
scoped_string _dns = get_string(dns);
|
||||
jobject _interface = new_global(cb);
|
||||
|
||||
startTun(fd, mtu, _gateway, _mirror, _dns, _interface);
|
||||
startTun(fd, mtu, _dns, _interface);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
|
||||
Submodule core/src/main/golang/clash updated: f8d0239b1e...043e34f700
@@ -7,6 +7,10 @@ var (
|
||||
"8.8.8.8",
|
||||
"1.1.1.1",
|
||||
}
|
||||
defaultFallback = []string{
|
||||
"https://1.1.1.1/dns-query",
|
||||
"https://doh.pub/dns-query",
|
||||
}
|
||||
defaultFakeIPFilter = []string{
|
||||
// stun services
|
||||
"+.stun.*.*",
|
||||
@@ -21,7 +25,7 @@ var (
|
||||
"*.n.n.srv.nintendo.net",
|
||||
}
|
||||
localNetwork = []string{
|
||||
"0.0.0.0/32",
|
||||
"0.0.0.0/8",
|
||||
"127.0.0.0/8",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -58,10 +58,10 @@ func patchDns(cfg *config.RawConfig, _ string) error {
|
||||
cfg.DNS.Enable = true
|
||||
cfg.DNS.IPv6 = false
|
||||
cfg.DNS.NameServer = defaultNameServers
|
||||
cfg.DNS.Fallback = []string{}
|
||||
cfg.DNS.Fallback = defaultFallback
|
||||
cfg.DNS.FallbackFilter.GeoIP = false
|
||||
cfg.DNS.FallbackFilter.IPCIDR = localNetwork
|
||||
cfg.DNS.EnhancedMode = dns.FAKEIP
|
||||
cfg.DNS.EnhancedMode = dns.MAPPING
|
||||
cfg.DNS.FakeIPRange = "198.18.0.0/16"
|
||||
cfg.DNS.DefaultNameserver = defaultNameServers
|
||||
cfg.DNS.FakeIPFilter = defaultFakeIPFilter
|
||||
|
||||
@@ -6,7 +6,7 @@ require (
|
||||
cfa/blob v0.0.0 // local generated
|
||||
github.com/Dreamacro/clash v0.0.0 // local
|
||||
github.com/dlclark/regexp2 v1.4.0
|
||||
github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99
|
||||
github.com/kr328/tun2socket v0.0.0 // local
|
||||
github.com/miekg/dns v1.1.42
|
||||
github.com/oschwald/geoip2-golang v1.5.0
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
@@ -15,4 +15,6 @@ require (
|
||||
|
||||
replace github.com/Dreamacro/clash => ./clash
|
||||
|
||||
replace github.com/kr328/tun2socket => ./tun2socket
|
||||
|
||||
replace cfa/blob => ../../../build/intermediates/golang_blob
|
||||
|
||||
@@ -56,17 +56,14 @@ func (t *remoteTun) stop() {
|
||||
}
|
||||
|
||||
//export startTun
|
||||
func startTun(fd, mtu C.int, gateway, mirror, dns C.c_string, callback unsafe.Pointer) C.int {
|
||||
func startTun(fd, mtu C.int, dns C.c_string, callback unsafe.Pointer) C.int {
|
||||
f := int(fd)
|
||||
m := int(mtu)
|
||||
|
||||
g := C.GoString(gateway)
|
||||
mr := C.GoString(mirror)
|
||||
d := C.GoString(dns)
|
||||
|
||||
remote := &remoteTun{callback: callback, closed: false, limit: semaphore.NewWeighted(4)}
|
||||
|
||||
if tun.Start(f, m, g, mr, d, remote.stop) != nil {
|
||||
if tun.Start(f, m, d) != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
@@ -7,24 +7,22 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/kr328/tun2socket"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
"github.com/kr328/tun2socket/redirect"
|
||||
)
|
||||
|
||||
const defaultDnsReadTimeout = time.Second * 30
|
||||
|
||||
func shouldHijackDns(dnsAddr binding.Address, targetAddr binding.Address) bool {
|
||||
if targetAddr.Port != 53 {
|
||||
func shouldHijackDns(dns net.IP, target net.IP, targetPort int) bool {
|
||||
if targetPort != 53 {
|
||||
return false
|
||||
}
|
||||
|
||||
return dnsAddr.IP.Equal(net.IPv4zero) || dnsAddr.IP.Equal(targetAddr.IP)
|
||||
return net.IPv4zero.Equal(dns) || target.Equal(dns)
|
||||
}
|
||||
|
||||
func hijackUDPDns(pkt []byte, ep *binding.Endpoint, sender redirect.UDPSender) {
|
||||
func hijackUDPDns(pkt []byte, lAddr, rAddr net.Addr, udp tun2socket.UDP) {
|
||||
go func() {
|
||||
answer, err := relayDnsPacket(pkt)
|
||||
|
||||
@@ -32,10 +30,9 @@ func hijackUDPDns(pkt []byte, ep *binding.Endpoint, sender redirect.UDPSender) {
|
||||
return
|
||||
}
|
||||
|
||||
_ = sender(answer, &binding.Endpoint{
|
||||
Source: ep.Target,
|
||||
Target: ep.Source,
|
||||
})
|
||||
_, _ = udp.WriteTo(answer, lAddr, rAddr)
|
||||
|
||||
recycleUDP(pkt)
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package tun
|
||||
|
||||
import "github.com/Dreamacro/clash/log"
|
||||
|
||||
type logger struct{}
|
||||
|
||||
func (l *logger) D(format string, args ...interface{}) {
|
||||
log.Debugln(format, args...)
|
||||
}
|
||||
|
||||
func (l *logger) I(format string, args ...interface{}) {
|
||||
log.Infoln(format, args...)
|
||||
}
|
||||
|
||||
func (l *logger) W(format string, args ...interface{}) {
|
||||
log.Warnln(format, args...)
|
||||
}
|
||||
|
||||
func (l *logger) E(format string, args ...interface{}) {
|
||||
log.Errorln(format, args...)
|
||||
}
|
||||
@@ -4,37 +4,24 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
CTX "github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
func handleTCP(conn net.Conn, endpoint *binding.Endpoint) {
|
||||
src := &net.TCPAddr{
|
||||
IP: endpoint.Source.IP,
|
||||
Port: int(endpoint.Source.Port),
|
||||
Zone: "",
|
||||
}
|
||||
dst := &net.TCPAddr{
|
||||
IP: endpoint.Target.IP,
|
||||
Port: int(endpoint.Target.Port),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
func handleTCP(conn net.Conn, source *net.TCPAddr, target *net.TCPAddr) {
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Type: C.SOCKS,
|
||||
SrcIP: src.IP,
|
||||
DstIP: dst.IP,
|
||||
SrcPort: strconv.Itoa(src.Port),
|
||||
DstPort: strconv.Itoa(dst.Port),
|
||||
SrcIP: source.IP,
|
||||
DstIP: target.IP,
|
||||
SrcPort: strconv.Itoa(source.Port),
|
||||
DstPort: strconv.Itoa(target.Port),
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
RawSrcAddr: src,
|
||||
RawDstAddr: dst,
|
||||
RawSrcAddr: source,
|
||||
RawDstAddr: target,
|
||||
}
|
||||
|
||||
tunnel.Add(context.NewConnContext(conn, metadata))
|
||||
tunnel.Add(CTX.NewConnContext(conn, metadata))
|
||||
}
|
||||
|
||||
@@ -3,67 +3,135 @@ package tun
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
"github.com/kr328/tun2socket/redirect"
|
||||
|
||||
"github.com/kr328/tun2socket"
|
||||
)
|
||||
|
||||
var lock sync.Mutex
|
||||
var tun *tun2socket.Tun2Socket
|
||||
type context struct {
|
||||
device *os.File
|
||||
stack tun2socket.Stack
|
||||
}
|
||||
|
||||
func Start(fd, mtu int, gateway, mirror, dns string, onStop func()) error {
|
||||
var lock sync.Mutex
|
||||
var tun *context
|
||||
|
||||
func (ctx *context) close() {
|
||||
_ = ctx.stack.Close()
|
||||
_ = ctx.device.Close()
|
||||
}
|
||||
|
||||
func Start(fd, mtu int, dns string) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
stopLocked()
|
||||
|
||||
dnsHost, dnsPort, err := net.SplitHostPort(dns)
|
||||
dnsIP := net.ParseIP(dns)
|
||||
|
||||
device := os.NewFile(uintptr(fd), "/dev/tun")
|
||||
|
||||
stack, err := tun2socket.NewStack(mtu)
|
||||
if err != nil {
|
||||
_ = device.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
dnsP, err := strconv.Atoi(dnsPort)
|
||||
if err != nil {
|
||||
return err
|
||||
ctx := &context{
|
||||
device: device,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
dnsAddr := binding.Address{
|
||||
IP: net.ParseIP(dnsHost),
|
||||
Port: uint16(dnsP),
|
||||
}
|
||||
go func() {
|
||||
// device -> lwip
|
||||
|
||||
t := tun2socket.NewTun2Socket(os.NewFile(uintptr(fd), "/dev/tun"), mtu, net.ParseIP(gateway), net.ParseIP(mirror))
|
||||
defer ctx.close()
|
||||
|
||||
t.SetAllocator(allocUDP)
|
||||
t.SetClosedHandler(onStop)
|
||||
t.SetLogger(&logger{})
|
||||
buf := make([]byte, mtu)
|
||||
|
||||
t.SetTCPHandler(func(conn net.Conn, endpoint *binding.Endpoint) {
|
||||
if shouldHijackDns(dnsAddr, endpoint.Target) {
|
||||
hijackTCPDns(conn)
|
||||
for {
|
||||
n, err := device.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
_, _ = stack.Link().Write(buf[:n])
|
||||
}
|
||||
}()
|
||||
|
||||
handleTCP(conn, endpoint)
|
||||
})
|
||||
t.SetUDPHandler(func(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender) {
|
||||
if shouldHijackDns(dnsAddr, endpoint.Target) {
|
||||
hijackUDPDns(payload, endpoint, sender)
|
||||
go func() {
|
||||
// lwip -> device
|
||||
|
||||
return
|
||||
defer ctx.close()
|
||||
|
||||
buf := make([]byte, mtu)
|
||||
|
||||
for {
|
||||
n, err := stack.Link().Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = device.Write(buf[:n])
|
||||
}
|
||||
}()
|
||||
|
||||
handleUDP(payload, endpoint, sender)
|
||||
})
|
||||
go func() {
|
||||
// lwip tcp
|
||||
|
||||
t.Start()
|
||||
defer ctx.close()
|
||||
|
||||
tun = t
|
||||
for {
|
||||
conn, err := stack.TCP().Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
source := conn.LocalAddr().(*net.TCPAddr)
|
||||
target := conn.RemoteAddr().(*net.TCPAddr)
|
||||
|
||||
if shouldHijackDns(dnsIP, target.IP, target.Port) {
|
||||
hijackTCPDns(conn)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
handleTCP(conn, source, target)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// lwip udp
|
||||
|
||||
defer ctx.close()
|
||||
|
||||
for {
|
||||
buf := allocUDP(mtu)
|
||||
|
||||
n, lAddr, rAddr, err := stack.UDP().ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
source := lAddr.(*net.UDPAddr)
|
||||
target := rAddr.(*net.UDPAddr)
|
||||
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if shouldHijackDns(dnsIP, target.IP, target.Port) {
|
||||
hijackUDPDns(buf[:n], source, target, stack.UDP())
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
handleUDP(buf[:n], source, target, stack.UDP())
|
||||
}
|
||||
}()
|
||||
|
||||
tun = ctx
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -77,7 +145,7 @@ func Stop() {
|
||||
|
||||
func stopLocked() {
|
||||
if tun != nil {
|
||||
tun.Close()
|
||||
tun.close()
|
||||
}
|
||||
|
||||
tun = nil
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
"github.com/kr328/tun2socket/redirect"
|
||||
"github.com/kr328/tun2socket"
|
||||
|
||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
@@ -15,10 +13,9 @@ import (
|
||||
)
|
||||
|
||||
type udpPacket struct {
|
||||
metadata *C.Metadata
|
||||
source binding.Address
|
||||
source *net.UDPAddr
|
||||
data []byte
|
||||
send redirect.UDPSender
|
||||
udp tun2socket.UDP
|
||||
}
|
||||
|
||||
func (u *udpPacket) Data() []byte {
|
||||
@@ -26,15 +23,7 @@ func (u *udpPacket) Data() []byte {
|
||||
}
|
||||
|
||||
func (u *udpPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
uAddr, ok := addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
return len(b), u.send(b, &binding.Endpoint{
|
||||
Source: binding.Address{IP: uAddr.IP, Port: uint16(uAddr.Port)},
|
||||
Target: u.source,
|
||||
})
|
||||
return u.udp.WriteTo(b, u.source, addr)
|
||||
}
|
||||
|
||||
func (u *udpPacket) Drop() {
|
||||
@@ -44,25 +33,19 @@ func (u *udpPacket) Drop() {
|
||||
func (u *udpPacket) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
IP: u.source.IP,
|
||||
Port: int(u.source.Port),
|
||||
Port: u.source.Port,
|
||||
Zone: "",
|
||||
}
|
||||
}
|
||||
|
||||
func handleUDP(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender) {
|
||||
func handleUDP(payload []byte, source *net.UDPAddr, target *net.UDPAddr, udp tun2socket.UDP) {
|
||||
pkt := &udpPacket{
|
||||
source: endpoint.Source,
|
||||
data: payload,
|
||||
send: sender,
|
||||
source: source,
|
||||
data: payload,
|
||||
udp: udp,
|
||||
}
|
||||
|
||||
rAddr := &net.UDPAddr{
|
||||
IP: endpoint.Target.IP,
|
||||
Port: int(endpoint.Target.Port),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
adapter := adapters.NewPacket(socks5.ParseAddrToSocksAddr(rAddr), pkt, C.SOCKS)
|
||||
adapter := adapters.NewPacket(socks5.ParseAddrToSocksAddr(target), pkt, C.SOCKS)
|
||||
|
||||
tunnel.AddPacket(adapter)
|
||||
}
|
||||
|
||||
1
core/src/main/golang/tun2socket
Submodule
1
core/src/main/golang/tun2socket
Submodule
Submodule core/src/main/golang/tun2socket added at 8542f77f66
@@ -61,13 +61,11 @@ object Clash {
|
||||
fun startTun(
|
||||
fd: Int,
|
||||
mtu: Int,
|
||||
gateway: String,
|
||||
mirror: String,
|
||||
dns: String,
|
||||
markSocket: (Int) -> Boolean,
|
||||
querySocketUid: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress) -> Int
|
||||
) {
|
||||
Bridge.nativeStartTun(fd, mtu, gateway, mirror, "$dns:53", object : TunInterface {
|
||||
Bridge.nativeStartTun(fd, mtu, dns, object : TunInterface {
|
||||
override fun markSocket(fd: Int) {
|
||||
markSocket(fd)
|
||||
}
|
||||
@@ -220,7 +218,7 @@ object Clash {
|
||||
return Channel<LogMessage>(32).apply {
|
||||
Bridge.nativeSubscribeLogcat(object : LogcatInterface {
|
||||
override fun received(jsonPayload: String) {
|
||||
offer(Json.decodeFromString(LogMessage.serializer(), jsonPayload))
|
||||
trySend(Json.decodeFromString(LogMessage.serializer(), jsonPayload))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,15 +17,7 @@ object Bridge {
|
||||
external fun nativeQueryTrafficTotal(): Long
|
||||
external fun nativeNotifyDnsChanged(dnsList: String)
|
||||
external fun nativeNotifyInstalledAppChanged(uidList: String)
|
||||
external fun nativeStartTun(
|
||||
fd: Int,
|
||||
mtu: Int,
|
||||
gateway: String,
|
||||
mirror: String,
|
||||
dns: String,
|
||||
cb: TunInterface
|
||||
)
|
||||
|
||||
external fun nativeStartTun(fd: Int, mtu: Int, dns: String, cb: TunInterface)
|
||||
external fun nativeStopTun()
|
||||
external fun nativeStartHttp(listenAt: String): String?
|
||||
external fun nativeStopHttp()
|
||||
|
||||
@@ -80,7 +80,10 @@ data class ConfigurationOverride(
|
||||
var fakeIpFilter: List<String>? = null,
|
||||
|
||||
@SerialName("fallback-filter")
|
||||
val fallbackFilter: DnsFallbackFilter = DnsFallbackFilter()
|
||||
val fallbackFilter: DnsFallbackFilter = DnsFallbackFilter(),
|
||||
|
||||
@SerialName("nameserver-policy")
|
||||
var nameserverPolicy: Map<String, String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -59,7 +59,7 @@ dependencies {
|
||||
|
||||
implementation(kotlin("stdlib-jdk7"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
|
||||
implementation("androidx.core:core-ktx:$ktxVersion")
|
||||
implementation("androidx.core:core-ktx:$coreVersion")
|
||||
implementation("androidx.appcompat:appcompat:$appcompatVersion")
|
||||
implementation("androidx.activity:activity:$activityVersion")
|
||||
implementation("com.google.android.material:material:$materialVersion")
|
||||
|
||||
@@ -236,7 +236,7 @@ class OverrideSettingsDesign(
|
||||
summary = R.string.sideload_geoip_summary
|
||||
) {
|
||||
clicked {
|
||||
requests.offer(Request.EditSideloadGeoip)
|
||||
requests.trySend(Request.EditSideloadGeoip)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,6 +378,15 @@ class OverrideSettingsDesign(
|
||||
configure = dnsDependencies::add,
|
||||
)
|
||||
|
||||
editableTextMap(
|
||||
value = configuration.dns::nameserverPolicy,
|
||||
keyAdapter = TextAdapter.String,
|
||||
valueAdapter = TextAdapter.String,
|
||||
title = R.string.name_server_policy,
|
||||
placeholder = R.string.dont_modify,
|
||||
configure = dnsDependencies::add,
|
||||
)
|
||||
|
||||
dns.listener?.onChanged()
|
||||
}
|
||||
|
||||
@@ -385,6 +394,6 @@ class OverrideSettingsDesign(
|
||||
}
|
||||
|
||||
fun requestClear() {
|
||||
requests.offer(Request.ResetOverride)
|
||||
requests.trySend(Request.ResetOverride)
|
||||
}
|
||||
}
|
||||
@@ -32,48 +32,48 @@ class ProxyMenu(
|
||||
R.id.not_selectable -> {
|
||||
uiStore.proxyExcludeNotSelectable = item.isChecked
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReLaunch)
|
||||
requests.trySend(ProxyDesign.Request.ReLaunch)
|
||||
}
|
||||
R.id.single -> {
|
||||
uiStore.proxySingleLine = true
|
||||
|
||||
updateConfig()
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.multiple -> {
|
||||
uiStore.proxySingleLine = false
|
||||
|
||||
updateConfig()
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.default_ -> {
|
||||
uiStore.proxySort = ProxySort.Default
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.name -> {
|
||||
uiStore.proxySort = ProxySort.Title
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.delay -> {
|
||||
uiStore.proxySort = ProxySort.Delay
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.dont_modify -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(null))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(null))
|
||||
}
|
||||
R.id.direct_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Direct))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Direct))
|
||||
}
|
||||
R.id.global_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Global))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Global))
|
||||
}
|
||||
R.id.rule_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Rule))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Rule))
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class AccessControlDesign(
|
||||
binding.surface = dialog.surface
|
||||
binding.mainList.applyLinearAdapter(context, adapter)
|
||||
binding.keywordView.addTextChangedListener {
|
||||
filter.offer(Unit)
|
||||
filter.trySend(Unit)
|
||||
}
|
||||
binding.closeView.setOnClickListener {
|
||||
dialog.dismiss()
|
||||
|
||||
@@ -38,7 +38,7 @@ class ApkBrokenDesign(context: Context) : Design<ApkBrokenDesign.Request>(contex
|
||||
summary = R.string.google_play_url
|
||||
) {
|
||||
clicked {
|
||||
requests.offer(Request(context.getString(R.string.google_play_url)))
|
||||
requests.trySend(Request(context.getString(R.string.google_play_url)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class ApkBrokenDesign(context: Context) : Design<ApkBrokenDesign.Request>(contex
|
||||
summary = R.string.github_releases_url
|
||||
) {
|
||||
clicked {
|
||||
requests.offer(Request(context.getString(R.string.github_releases_url)))
|
||||
requests.trySend(Request(context.getString(R.string.github_releases_url)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class AppSettingsDesign(
|
||||
title = R.string.dark_mode
|
||||
) {
|
||||
listener = OnChangedListener {
|
||||
requests.offer(Request.ReCreateAllActivities)
|
||||
requests.trySend(Request.ReCreateAllActivities)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,38 +72,38 @@ class FilesDesign(context: Context) : Design<FilesDesign.Request>(context) {
|
||||
|
||||
private fun requestOpen(file: File) {
|
||||
if (file.isDirectory) {
|
||||
requests.offer(Request.OpenDirectory(file))
|
||||
requests.trySend(Request.OpenDirectory(file))
|
||||
} else {
|
||||
requests.offer(Request.OpenFile(file))
|
||||
requests.trySend(Request.OpenFile(file))
|
||||
}
|
||||
}
|
||||
|
||||
fun requestRename(dialog: Dialog, file: File) {
|
||||
requests.offer(Request.RenameFile(file))
|
||||
requests.trySend(Request.RenameFile(file))
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
fun requestImport(dialog: Dialog, file: File) {
|
||||
requests.offer(Request.ImportFile(file))
|
||||
requests.trySend(Request.ImportFile(file))
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
fun requestExport(dialog: Dialog, file: File) {
|
||||
requests.offer(Request.ExportFile(file))
|
||||
requests.trySend(Request.ExportFile(file))
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
fun requestDelete(dialog: Dialog, file: File) {
|
||||
requests.offer(Request.DeleteFile(file))
|
||||
requests.trySend(Request.DeleteFile(file))
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
fun requestNew() {
|
||||
requests.offer(Request.ImportFile(null))
|
||||
requests.trySend(Request.ImportFile(null))
|
||||
}
|
||||
|
||||
private fun requestMore(file: File) {
|
||||
|
||||
@@ -23,7 +23,7 @@ class LogsDesign(context: Context) : Design<LogsDesign.Request>(context) {
|
||||
private val binding = DesignLogsBinding
|
||||
.inflate(context.layoutInflater, context.root, false)
|
||||
private val adapter = LogFileAdapter(context) {
|
||||
requests.offer(Request.OpenFile(it))
|
||||
requests.trySend(Request.OpenFile(it))
|
||||
}
|
||||
|
||||
override val root: View
|
||||
|
||||
@@ -97,6 +97,6 @@ class MainDesign(context: Context) : Design<MainDesign.Request>(context) {
|
||||
}
|
||||
|
||||
fun request(request: Request) {
|
||||
requests.offer(request)
|
||||
requests.trySend(request)
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class NetworkSettingsDesign(
|
||||
summary = R.string.access_control_packages_summary,
|
||||
) {
|
||||
clicked {
|
||||
requests.offer(Request.StartAccessControlList)
|
||||
requests.trySend(Request.StartAccessControlList)
|
||||
}
|
||||
|
||||
vpnDependencies.add(this)
|
||||
|
||||
@@ -38,13 +38,13 @@ class NewProfileDesign(context: Context) : Design<NewProfileDesign.Request>(cont
|
||||
}
|
||||
|
||||
private fun requestCreate(provider: ProfileProvider) {
|
||||
requests.offer(Request.Create(provider))
|
||||
requests.trySend(Request.Create(provider))
|
||||
}
|
||||
|
||||
private fun requestDetail(provider: ProfileProvider): Boolean {
|
||||
if (provider !is ProfileProvider.External) return false
|
||||
|
||||
requests.offer(Request.OpenDetail(provider))
|
||||
requests.trySend(Request.OpenDetail(provider))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class ProfilesDesign(context: Context) : Design<ProfilesDesign.Request>(context)
|
||||
suspend fun requestSave(profile: Profile) {
|
||||
showToast(R.string.active_unsaved_tips, ToastDuration.Long) {
|
||||
setAction(R.string.edit) {
|
||||
requests.offer(Request.Edit(profile))
|
||||
requests.trySend(Request.Edit(profile))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,37 +84,37 @@ class ProfilesDesign(context: Context) : Design<ProfilesDesign.Request>(context)
|
||||
}
|
||||
|
||||
fun requestUpdateAll() {
|
||||
requests.offer(Request.UpdateAll)
|
||||
requests.trySend(Request.UpdateAll)
|
||||
}
|
||||
|
||||
fun requestCreate() {
|
||||
requests.offer(Request.Create)
|
||||
requests.trySend(Request.Create)
|
||||
}
|
||||
|
||||
private fun requestActive(profile: Profile) {
|
||||
requests.offer(Request.Active(profile))
|
||||
requests.trySend(Request.Active(profile))
|
||||
}
|
||||
|
||||
fun requestUpdate(dialog: Dialog, profile: Profile) {
|
||||
requests.offer(Request.Update(profile))
|
||||
requests.trySend(Request.Update(profile))
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
fun requestEdit(dialog: Dialog, profile: Profile) {
|
||||
requests.offer(Request.Edit(profile))
|
||||
requests.trySend(Request.Edit(profile))
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
fun requestDuplicate(dialog: Dialog, profile: Profile) {
|
||||
requests.offer(Request.Duplicate(profile))
|
||||
requests.trySend(Request.Duplicate(profile))
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
fun requestDelete(dialog: Dialog, profile: Profile) {
|
||||
requests.offer(Request.Delete(profile))
|
||||
requests.trySend(Request.Delete(profile))
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
@@ -142,11 +142,11 @@ class PropertiesDesign(context: Context) : Design<PropertiesDesign.Request>(cont
|
||||
}
|
||||
|
||||
fun requestCommit() {
|
||||
requests.offer(Request.Commit)
|
||||
requests.trySend(Request.Commit)
|
||||
}
|
||||
|
||||
fun requestBrowseFiles() {
|
||||
requests.offer(Request.BrowseFiles)
|
||||
requests.trySend(Request.BrowseFiles)
|
||||
}
|
||||
|
||||
private fun ModelProgressBarConfigure.applyFrom(status: FetchStatus) {
|
||||
|
||||
@@ -24,7 +24,7 @@ class ProvidersDesign(
|
||||
get() = binding.root
|
||||
|
||||
private val adapter = ProviderAdapter(context, providers) { index, provider ->
|
||||
requests.offer(Request.Update(index, provider))
|
||||
requests.trySend(Request.Update(index, provider))
|
||||
}
|
||||
|
||||
fun updateElapsed() {
|
||||
@@ -56,7 +56,7 @@ class ProvidersDesign(
|
||||
adapter.states.filter { !it.updating }.forEachIndexed { index, state ->
|
||||
state.updating = true
|
||||
|
||||
requests.offer(Request.Update(index, state.provider))
|
||||
requests.trySend(Request.Update(index, state.provider))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ class ProxyDesign(
|
||||
config,
|
||||
List(groupNames.size) { index ->
|
||||
ProxyAdapter(config) { name ->
|
||||
requests.offer(Request.Select(index, name))
|
||||
requests.trySend(Request.Select(index, name))
|
||||
}
|
||||
}
|
||||
) {
|
||||
@@ -171,7 +171,7 @@ class ProxyDesign(
|
||||
fun requestUrlTesting() {
|
||||
urlTesting = true
|
||||
|
||||
requests.offer(Request.UrlTest(binding.pagesView.currentItem))
|
||||
requests.trySend(Request.UrlTest(binding.pagesView.currentItem))
|
||||
|
||||
updateUrlTestButtonStatus()
|
||||
}
|
||||
|
||||
@@ -28,6 +28,6 @@ class SettingsDesign(context: Context) : Design<SettingsDesign.Request>(context)
|
||||
}
|
||||
|
||||
fun request(request: Request) {
|
||||
requests.offer(request)
|
||||
requests.trySend(request)
|
||||
}
|
||||
}
|
||||
@@ -28,46 +28,46 @@ class AccessControlMenu(
|
||||
|
||||
when (item.itemId) {
|
||||
R.id.select_all ->
|
||||
requests.offer(Request.SelectAll)
|
||||
requests.trySend(Request.SelectAll)
|
||||
R.id.select_none ->
|
||||
requests.offer(Request.SelectNone)
|
||||
requests.trySend(Request.SelectNone)
|
||||
R.id.select_invert ->
|
||||
requests.offer(Request.SelectInvert)
|
||||
requests.trySend(Request.SelectInvert)
|
||||
R.id.system_apps -> {
|
||||
uiStore.accessControlSystemApp = !item.isChecked
|
||||
|
||||
requests.offer(Request.ReloadApps)
|
||||
requests.trySend(Request.ReloadApps)
|
||||
}
|
||||
R.id.name -> {
|
||||
uiStore.accessControlSort = AppInfoSort.Label
|
||||
|
||||
requests.offer(Request.ReloadApps)
|
||||
requests.trySend(Request.ReloadApps)
|
||||
}
|
||||
R.id.package_name -> {
|
||||
uiStore.accessControlSort = AppInfoSort.PackageName
|
||||
|
||||
requests.offer(Request.ReloadApps)
|
||||
requests.trySend(Request.ReloadApps)
|
||||
}
|
||||
R.id.install_time -> {
|
||||
uiStore.accessControlSort = AppInfoSort.InstallTime
|
||||
|
||||
requests.offer(Request.ReloadApps)
|
||||
requests.trySend(Request.ReloadApps)
|
||||
}
|
||||
R.id.update_time -> {
|
||||
uiStore.accessControlSort = AppInfoSort.UpdateTime
|
||||
|
||||
requests.offer(Request.ReloadApps)
|
||||
requests.trySend(Request.ReloadApps)
|
||||
}
|
||||
R.id.reverse -> {
|
||||
uiStore.accessControlReverse = item.isChecked
|
||||
|
||||
requests.offer(Request.ReloadApps)
|
||||
requests.trySend(Request.ReloadApps)
|
||||
}
|
||||
R.id.import_from_clipboard -> {
|
||||
requests.offer(Request.Import)
|
||||
requests.trySend(Request.Import)
|
||||
}
|
||||
R.id.export_to_clipboard -> {
|
||||
requests.offer(Request.Export)
|
||||
requests.trySend(Request.Export)
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.github.kr328.clash.design.ui.Insets
|
||||
fun View.setOnInsertsChangedListener(adaptLandscape: Boolean = true, listener: (Insets) -> Unit) {
|
||||
setOnApplyWindowInsetsListener { v, ins ->
|
||||
val compat = WindowInsetsCompat.toWindowInsetsCompat(ins)
|
||||
val insets = compat.systemWindowInsets
|
||||
val insets = compat.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
val rInsets = if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
Insets(
|
||||
@@ -28,7 +28,7 @@ fun View.setOnInsertsChangedListener(adaptLandscape: Boolean = true, listener: (
|
||||
|
||||
listener(if (adaptLandscape) rInsets.landscape(v.context) else rInsets)
|
||||
|
||||
compat.consumeStableInsets().toWindowInsets()
|
||||
compat.toWindowInsets()
|
||||
}
|
||||
|
||||
requestApplyInsets()
|
||||
|
||||
@@ -208,4 +208,7 @@
|
||||
<string name="active_unsaved_tips">配置文件需要在激活之前保存</string>
|
||||
<string name="mode_switch_tips">僅在本次會話中有效</string>
|
||||
<string name="import_">導入</string>
|
||||
<string name="sources">源代碼</string>
|
||||
<string name="clash_core">Clash 核心</string>
|
||||
<string name="name_server_policy">Name Server 策略</string>
|
||||
</resources>
|
||||
@@ -208,4 +208,7 @@
|
||||
<string name="active_unsaved_tips">配置文件需要在激活之前保存</string>
|
||||
<string name="mode_switch_tips">僅在本次會話中有效</string>
|
||||
<string name="import_">導入</string>
|
||||
<string name="sources">源代碼</string>
|
||||
<string name="clash_core">Clash 核心</string>
|
||||
<string name="name_server_policy">Name Server 策略</string>
|
||||
</resources>
|
||||
@@ -210,4 +210,5 @@
|
||||
<string name="import_">导入</string>
|
||||
<string name="sources">源代码</string>
|
||||
<string name="clash_core">Clash 核心</string>
|
||||
<string name="name_server_policy">Name Server 策略</string>
|
||||
</resources>
|
||||
@@ -159,6 +159,7 @@
|
||||
<string name="geoip_fallback">GeoIP Fallback</string>
|
||||
<string name="ipcidr_fallback">IPCIDR Fallback</string>
|
||||
<string name="domain_fallback">Domain Fallback</string>
|
||||
<string name="name_server_policy">Name Server Policy</string>
|
||||
|
||||
<string name="dont_modify">Do not modify</string>
|
||||
<string name="empty">Empty</string>
|
||||
|
||||
@@ -238,7 +238,7 @@ class OverrideSettingsDesign(
|
||||
summary = R.string.sideload_geoip_summary
|
||||
) {
|
||||
clicked {
|
||||
requests.offer(Request.EditSideloadGeoip)
|
||||
requests.trySend(Request.EditSideloadGeoip)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,6 +380,15 @@ class OverrideSettingsDesign(
|
||||
configure = dnsDependencies::add,
|
||||
)
|
||||
|
||||
editableTextMap(
|
||||
value = configuration.dns::nameserverPolicy,
|
||||
keyAdapter = TextAdapter.String,
|
||||
valueAdapter = TextAdapter.String,
|
||||
title = R.string.name_server_policy,
|
||||
placeholder = R.string.dont_modify,
|
||||
configure = dnsDependencies::add,
|
||||
)
|
||||
|
||||
dns.listener?.onChanged()
|
||||
}
|
||||
|
||||
@@ -387,6 +396,6 @@ class OverrideSettingsDesign(
|
||||
}
|
||||
|
||||
fun requestClear() {
|
||||
requests.offer(Request.ResetOverride)
|
||||
requests.trySend(Request.ResetOverride)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,51 +32,51 @@ class ProxyMenu(
|
||||
R.id.not_selectable -> {
|
||||
uiStore.proxyExcludeNotSelectable = item.isChecked
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReLaunch)
|
||||
requests.trySend(ProxyDesign.Request.ReLaunch)
|
||||
}
|
||||
R.id.single -> {
|
||||
uiStore.proxySingleLine = true
|
||||
|
||||
updateConfig()
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.multiple -> {
|
||||
uiStore.proxySingleLine = false
|
||||
|
||||
updateConfig()
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.default_ -> {
|
||||
uiStore.proxySort = ProxySort.Default
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.name -> {
|
||||
uiStore.proxySort = ProxySort.Title
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.delay -> {
|
||||
uiStore.proxySort = ProxySort.Delay
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.dont_modify -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(null))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(null))
|
||||
}
|
||||
R.id.direct_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Direct))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Direct))
|
||||
}
|
||||
R.id.global_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Global))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Global))
|
||||
}
|
||||
R.id.rule_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Rule))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Rule))
|
||||
}
|
||||
R.id.script_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Script))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Script))
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
|
||||
@@ -48,15 +48,6 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
named("debug") {
|
||||
java.srcDir(buildDir.resolve("generated/ksp/debug/kotlin"))
|
||||
}
|
||||
named("release") {
|
||||
java.srcDir(buildDir.resolve("generated/ksp/release/kotlin"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -71,9 +62,15 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
|
||||
implementation("androidx.room:room-runtime:$roomVersion")
|
||||
implementation("androidx.room:room-ktx:$roomVersion")
|
||||
implementation("androidx.core:core-ktx:$ktxVersion")
|
||||
implementation("com.microsoft.appcenter:appcenter-analytics:$appcenterVersion")
|
||||
implementation("com.microsoft.appcenter:appcenter-crashes:$appcenterVersion")
|
||||
implementation("androidx.core:core-ktx:$coreVersion")
|
||||
implementation("dev.rikka.rikkax.preference:multiprocess:$muiltprocessVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android {
|
||||
libraryVariants.forEach {
|
||||
sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,7 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".ClashManager"
|
||||
android:exported="false"
|
||||
android:process=":background" />
|
||||
<service
|
||||
android:name=".ProfileService"
|
||||
android:name=".RemoteService"
|
||||
android:exported="false"
|
||||
android:process=":background" />
|
||||
<service
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.content.Context
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.model.*
|
||||
@@ -9,22 +8,16 @@ import com.github.kr328.clash.service.data.Selection
|
||||
import com.github.kr328.clash.service.data.SelectionDao
|
||||
import com.github.kr328.clash.service.remote.IClashManager
|
||||
import com.github.kr328.clash.service.remote.ILogObserver
|
||||
import com.github.kr328.clash.service.remote.wrap
|
||||
import com.github.kr328.clash.service.store.ServiceStore
|
||||
import com.github.kr328.clash.service.util.sendOverrideChanged
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import java.util.*
|
||||
|
||||
class ClashManager : BaseService(), IClashManager {
|
||||
private val store by lazy { ServiceStore(this) }
|
||||
private val binder = this.wrap()
|
||||
class ClashManager(private val context: Context) : IClashManager,
|
||||
CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||
private val store = ServiceStore(context)
|
||||
private var logReceiver: ReceiveChannel<LogMessage>? = null
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun queryTunnelState(): TunnelState {
|
||||
return Clash.queryTunnelState()
|
||||
}
|
||||
@@ -68,7 +61,7 @@ class ClashManager : BaseService(), IClashManager {
|
||||
override fun patchOverride(slot: Clash.OverrideSlot, configuration: ConfigurationOverride) {
|
||||
Clash.patchOverride(slot, configuration)
|
||||
|
||||
sendOverrideChanged()
|
||||
context.sendOverrideChanged()
|
||||
}
|
||||
|
||||
override fun clearOverride(slot: Clash.OverrideSlot) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.content.Context
|
||||
import com.github.kr328.clash.service.data.Database
|
||||
import com.github.kr328.clash.service.data.ImportedDao
|
||||
import com.github.kr328.clash.service.data.Pending
|
||||
@@ -9,34 +8,27 @@ import com.github.kr328.clash.service.data.PendingDao
|
||||
import com.github.kr328.clash.service.model.Profile
|
||||
import com.github.kr328.clash.service.remote.IFetchObserver
|
||||
import com.github.kr328.clash.service.remote.IProfileManager
|
||||
import com.github.kr328.clash.service.remote.wrap
|
||||
import com.github.kr328.clash.service.store.ServiceStore
|
||||
import com.github.kr328.clash.service.util.directoryLastModified
|
||||
import com.github.kr328.clash.service.util.generateProfileUUID
|
||||
import com.github.kr328.clash.service.util.importedDir
|
||||
import com.github.kr328.clash.service.util.pendingDir
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.*
|
||||
|
||||
class ProfileService : BaseService(), IProfileManager {
|
||||
private val service = this
|
||||
private val store by lazy { ServiceStore(this) }
|
||||
private val binder = this.wrap()
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
Database.database //.init
|
||||
class ProfileManager(private val context: Context) : IProfileManager,
|
||||
CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||
private val store = ServiceStore(context)
|
||||
|
||||
init {
|
||||
launch {
|
||||
ProfileReceiver.rescheduleAll(service)
|
||||
Database.database //.init
|
||||
|
||||
ProfileReceiver.rescheduleAll(context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +44,7 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
|
||||
PendingDao().insert(pending)
|
||||
|
||||
pendingDir.resolve(uuid.toString()).apply {
|
||||
context.pendingDir.resolve(uuid.toString()).apply {
|
||||
deleteRecursively()
|
||||
mkdirs()
|
||||
|
||||
@@ -119,21 +111,21 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
}
|
||||
|
||||
override suspend fun commit(uuid: UUID, callback: IFetchObserver?) {
|
||||
ProfileProcessor.apply(service, uuid, callback)
|
||||
ProfileProcessor.apply(context, uuid, callback)
|
||||
|
||||
scheduleUpdate(uuid, false)
|
||||
}
|
||||
|
||||
override suspend fun release(uuid: UUID) {
|
||||
ProfileProcessor.release(this, uuid)
|
||||
ProfileProcessor.release(context, uuid)
|
||||
}
|
||||
|
||||
override suspend fun delete(uuid: UUID) {
|
||||
ImportedDao().queryByUUID(uuid)?.also {
|
||||
ProfileReceiver.cancelNext(service, it)
|
||||
ProfileReceiver.cancelNext(context, it)
|
||||
}
|
||||
|
||||
ProfileProcessor.delete(service, uuid)
|
||||
ProfileProcessor.delete(context, uuid)
|
||||
}
|
||||
|
||||
override suspend fun queryByUUID(uuid: UUID): Profile? {
|
||||
@@ -159,7 +151,7 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
}
|
||||
|
||||
override suspend fun setActive(profile: Profile) {
|
||||
ProfileProcessor.active(this, profile.uuid)
|
||||
ProfileProcessor.active(context, profile.uuid)
|
||||
}
|
||||
|
||||
private suspend fun resolveProfile(uuid: UUID): Profile? {
|
||||
@@ -186,14 +178,14 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
}
|
||||
|
||||
private fun resolveUpdatedAt(uuid: UUID): Long {
|
||||
return pendingDir.resolve(uuid.toString()).directoryLastModified
|
||||
?: importedDir.resolve(uuid.toString()).directoryLastModified
|
||||
return context.pendingDir.resolve(uuid.toString()).directoryLastModified
|
||||
?: context.importedDir.resolve(uuid.toString()).directoryLastModified
|
||||
?: -1
|
||||
}
|
||||
|
||||
private fun cloneImportedFiles(source: UUID, target: UUID = source) {
|
||||
val s = importedDir.resolve(source.toString())
|
||||
val t = pendingDir.resolve(target.toString())
|
||||
val s = context.importedDir.resolve(source.toString())
|
||||
val t = context.pendingDir.resolve(target.toString())
|
||||
|
||||
if (!s.exists())
|
||||
throw FileNotFoundException("profile $source not found")
|
||||
@@ -207,9 +199,9 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
val imported = ImportedDao().queryByUUID(uuid) ?: return
|
||||
|
||||
if (startImmediately) {
|
||||
ProfileReceiver.schedule(service, imported)
|
||||
ProfileReceiver.schedule(context, imported)
|
||||
} else {
|
||||
ProfileReceiver.scheduleNext(service, imported)
|
||||
ProfileReceiver.scheduleNext(context, imported)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.content.getSystemService
|
||||
import com.github.kr328.clash.common.Global
|
||||
import com.github.kr328.clash.common.compat.pendingIntentFlags
|
||||
import com.github.kr328.clash.common.compat.startForegroundServiceCompat
|
||||
import com.github.kr328.clash.common.constants.Intents
|
||||
@@ -16,7 +17,6 @@ import com.github.kr328.clash.service.data.Imported
|
||||
import com.github.kr328.clash.service.data.ImportedDao
|
||||
import com.github.kr328.clash.service.model.Profile
|
||||
import com.github.kr328.clash.service.util.importedDir
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
@@ -27,7 +27,7 @@ class ProfileReceiver : BroadcastReceiver() {
|
||||
when (intent.action) {
|
||||
Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED,
|
||||
Intent.ACTION_TIMEZONE_CHANGED, Intent.ACTION_TIME_CHANGED -> {
|
||||
GlobalScope.launch {
|
||||
Global.launch {
|
||||
reset()
|
||||
|
||||
val service = Intent(Intents.ACTION_PROFILE_SCHEDULE_UPDATES)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.github.kr328.clash.service.remote.IClashManager
|
||||
import com.github.kr328.clash.service.remote.IRemoteService
|
||||
import com.github.kr328.clash.service.remote.IProfileManager
|
||||
import com.github.kr328.clash.service.remote.wrap
|
||||
import com.github.kr328.clash.service.util.cancelAndJoinBlocking
|
||||
|
||||
class RemoteService : BaseService(), IRemoteService {
|
||||
private val binder = this.wrap()
|
||||
|
||||
private var clash: ClashManager? = null
|
||||
private var profile: ProfileManager? = null
|
||||
private var clashBinder: IClashManager? = null
|
||||
private var profileBinder: IProfileManager? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
clash = ClashManager(this)
|
||||
profile = ProfileManager(this)
|
||||
clashBinder = clash?.wrap() as IClashManager?
|
||||
profileBinder = profile?.wrap() as IProfileManager?
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
clash?.cancelAndJoinBlocking()
|
||||
profile?.cancelAndJoinBlocking()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun clash(): IClashManager {
|
||||
return clashBinder!!
|
||||
}
|
||||
|
||||
override fun profile(): IProfileManager {
|
||||
return profileBinder!!
|
||||
}
|
||||
}
|
||||
@@ -217,8 +217,6 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
fd = establish()?.detachFd()
|
||||
?: throw NullPointerException("Establish VPN rejected by system"),
|
||||
mtu = TUN_MTU,
|
||||
gateway = TUN_GATEWAY,
|
||||
mirror = TUN_MIRROR,
|
||||
dns = if (store.dnsHijacking) NET_ANY else TUN_DNS,
|
||||
)
|
||||
}
|
||||
@@ -230,7 +228,6 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
private const val TUN_MTU = 9000
|
||||
private const val TUN_SUBNET_PREFIX = 30
|
||||
private const val TUN_GATEWAY = "172.31.255.253"
|
||||
private const val TUN_MIRROR = "172.31.255.254"
|
||||
private const val TUN_DNS = "198.18.0.1"
|
||||
private const val NET_ANY = "0.0.0.0"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class ConfigurationModule(service: Service) : Module<ConfigurationModule.LoadExc
|
||||
|
||||
var loaded: UUID? = null
|
||||
|
||||
reload.offer(Unit)
|
||||
reload.trySend(Unit)
|
||||
|
||||
while (true) {
|
||||
val changed: UUID? = select {
|
||||
@@ -75,6 +75,6 @@ class ConfigurationModule(service: Service) : Module<ConfigurationModule.LoadExc
|
||||
}
|
||||
|
||||
fun reload() {
|
||||
reload.offer(Unit)
|
||||
reload.trySend(Unit)
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ abstract class Module<E>(val service: Service) {
|
||||
return
|
||||
}
|
||||
|
||||
channel.offer(intent)
|
||||
channel.trySend(intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class NetworkObserveModule(service: Service) :
|
||||
override fun onAvailable(network: Network) {
|
||||
this.network = network
|
||||
|
||||
networks.offer(network)
|
||||
networks.trySend(network)
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(
|
||||
@@ -49,19 +49,19 @@ class NetworkObserveModule(service: Service) :
|
||||
if (this.network == network && this.internet != internet) {
|
||||
this.internet = internet
|
||||
|
||||
networks.offer(network)
|
||||
networks.trySend(network)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
if (this.network == network) {
|
||||
networks.offer(null)
|
||||
networks.trySend(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
|
||||
if (this.network == network) {
|
||||
networks.offer(network)
|
||||
networks.trySend(network)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ class TunModule(private val vpn: VpnService) : Module<Unit>(vpn) {
|
||||
data class TunDevice(
|
||||
val fd: Int,
|
||||
val mtu: Int,
|
||||
val gateway: String,
|
||||
val mirror: String,
|
||||
val dns: String
|
||||
)
|
||||
|
||||
@@ -58,8 +56,6 @@ class TunModule(private val vpn: VpnService) : Module<Unit>(vpn) {
|
||||
Clash.startTun(
|
||||
fd = device.fd,
|
||||
mtu = device.mtu,
|
||||
gateway = device.gateway,
|
||||
mirror = device.mirror,
|
||||
dns = device.dns,
|
||||
markSocket = vpn::protect,
|
||||
querySocketUid = this::queryUid
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.github.kr328.clash.common.Global
|
||||
import com.github.kr328.clash.service.data.migrations.LEGACY_MIGRATION
|
||||
import com.github.kr328.clash.service.data.migrations.MIGRATIONS
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.ref.SoftReference
|
||||
import androidx.room.Database as DB
|
||||
@@ -41,7 +40,7 @@ abstract class Database : RoomDatabase() {
|
||||
}
|
||||
|
||||
init {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
Global.launch(Dispatchers.IO) {
|
||||
LEGACY_MIGRATION(Global.application)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import com.github.kr328.clash.service.model.Profile
|
||||
import com.github.kr328.clash.service.util.generateProfileUUID
|
||||
import com.github.kr328.clash.service.util.pendingDir
|
||||
import com.github.kr328.clash.service.util.sendProfileChanged
|
||||
import com.microsoft.appcenter.crashes.Crashes
|
||||
import java.io.File
|
||||
|
||||
internal suspend fun migrationFromLegacy(context: Context) {
|
||||
@@ -37,8 +36,6 @@ internal suspend fun migrationFromLegacy(context: Context) {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Crashes.trackError(e)
|
||||
|
||||
Log.w("Migration legacy database: $e", e)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.github.kr328.clash.service.remote
|
||||
|
||||
import com.github.kr328.kaidl.BinderInterface
|
||||
|
||||
@BinderInterface
|
||||
interface IRemoteService {
|
||||
fun clash(): IClashManager
|
||||
fun profile(): IProfileManager
|
||||
}
|
||||
@@ -76,6 +76,5 @@
|
||||
<item>255.255.255.248/30</item>
|
||||
<item>255.255.255.252/31</item>
|
||||
<item>255.255.255.254/32</item>
|
||||
<item>172.31.255.252/30</item> <!-- tun device address -->
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user