refactor event service dispatch
This commit is contained in:
21 files changed
+536
-566
No files matched your search
@@ -6,7 +6,7 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.github.kr328.clash.core.event.*
|
||||
import com.github.kr328.clash.core.model.GeneralPacket
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.utils.ByteFormatter
|
||||
import com.github.kr328.clash.service.TunService
|
||||
import com.github.kr328.clash.utils.ServiceUtils
|
||||
@@ -113,13 +113,13 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
runOnUiThread {
|
||||
when ( general.mode ) {
|
||||
GeneralPacket.Mode.DIRECT ->
|
||||
General.Mode.DIRECT ->
|
||||
activity_main_clash_proxies_summary.text =
|
||||
getText(R.string.clash_proxy_manage_summary_direct)
|
||||
GeneralPacket.Mode.GLOBAL ->
|
||||
General.Mode.GLOBAL ->
|
||||
activity_main_clash_proxies_summary.text =
|
||||
getText(R.string.clash_proxy_manage_summary_global)
|
||||
GeneralPacket.Mode.RULE ->
|
||||
General.Mode.RULE ->
|
||||
activity_main_clash_proxies_summary.text =
|
||||
getText(R.string.clash_proxy_manage_summary_rule)
|
||||
}
|
||||
|
||||
@@ -173,11 +173,4 @@ open class GolangBindTask : DefaultTask() {
|
||||
if (process.waitFor() != 0)
|
||||
throw GradleException("Run command $this failure")
|
||||
}
|
||||
|
||||
private fun String.exe(): String {
|
||||
return if (Os.isFamily(Os.FAMILY_WINDOWS))
|
||||
"$this.exe"
|
||||
else
|
||||
this
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import java.io.File
|
||||
|
||||
data class GolangBuildOptions(val sourceDir: File, val outputDir: File, val platform: String)
|
||||
@@ -1,92 +0,0 @@
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.tasks.Exec
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
|
||||
import java.util.Properties
|
||||
|
||||
open class GolangBuildTask : Exec() {
|
||||
private lateinit var options: GolangBuildOptions
|
||||
private lateinit var abi: String
|
||||
|
||||
override fun exec() {
|
||||
val toolchainRoot = findAndroidNdkPath().resolve("toolchains/llvm/prebuilt/${detectOsType()}/bin")
|
||||
val linkerPrefix = toolChainPrefix()
|
||||
val compilerPrefix = linkerPrefix + options.platform
|
||||
|
||||
if ( !toolchainRoot.exists() )
|
||||
throw GradleException("Compiler not found")
|
||||
|
||||
workingDir = options.sourceDir
|
||||
|
||||
environment.put("GOARCH", golangArch())
|
||||
environment.put("GOOS", "android")
|
||||
environment.put("CGO_ENABLED", "1")
|
||||
environment.put("GOPATH", project.buildDir.resolve("intermediates/gopath").absolutePath)
|
||||
environment.put("CXX", toolchainRoot.resolve(compilerPrefix + "-clang++".cmd()).absolutePath)
|
||||
environment.put("CC", toolchainRoot.resolve(compilerPrefix + "-clang".cmd()).absolutePath)
|
||||
environment.put("LD", toolchainRoot.resolve(linkerPrefix + "ld".exe()).absolutePath)
|
||||
|
||||
commandLine = listOf("go".exe(), "build", "-o", options.outputDir.resolve("$abi/libclash.so").absolutePath)
|
||||
|
||||
super.exec()
|
||||
}
|
||||
|
||||
fun setOptions(options: GolangBuildOptions, abi: String) {
|
||||
this.options = options
|
||||
this.abi = abi
|
||||
}
|
||||
|
||||
private fun findAndroidNdkPath(): File {
|
||||
val properties = FileReader(project.rootProject.file("local.properties")).use {
|
||||
Properties().apply { load(it) }
|
||||
}
|
||||
|
||||
return properties.getProperty("ndk.dir")?.let { File(it) }?.takeIf { it.exists() }
|
||||
?: throw GradleException("Android NDK not found.")
|
||||
}
|
||||
|
||||
private fun detectOsType(): String {
|
||||
return when {
|
||||
Os.isFamily(Os.FAMILY_WINDOWS) -> "windows-x86_64"
|
||||
Os.isFamily(Os.FAMILY_MAC) -> "darwin-x86_64"
|
||||
Os.isFamily(Os.FAMILY_UNIX) -> "linux-x86_64"
|
||||
else -> throw GradleException("Unsupported Build OS ${System.getenv("os.name")}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun golangArch(): String {
|
||||
return when (abi) {
|
||||
"armeabi-v7a" -> "arm"
|
||||
"arm64-v8a" -> "arm64"
|
||||
"x86" -> "i686"
|
||||
"x86_64" -> "amd64"
|
||||
else -> throw GradleException("Unsupported arch $abi")
|
||||
}
|
||||
}
|
||||
|
||||
private fun toolChainPrefix(): String {
|
||||
return when (abi) {
|
||||
"armeabi-v7a" -> "arm-linux-androideabi"
|
||||
"arm64-v8a" -> "aarch64-linux-android"
|
||||
"x86" -> "i686-linux-android"
|
||||
"x86_64" -> "x86_64-linux-android"
|
||||
else -> throw GradleException("Unsupported arch $abi")
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.exe(): String {
|
||||
return if ( Os.isFamily(Os.FAMILY_WINDOWS) )
|
||||
"$this.exe"
|
||||
else
|
||||
this
|
||||
}
|
||||
|
||||
private fun String.cmd(): String {
|
||||
return if ( Os.isFamily(Os.FAMILY_WINDOWS) )
|
||||
"$this.cmd"
|
||||
else
|
||||
this
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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()
|
||||
t := tunnel.Instance()
|
||||
|
||||
result.Mode = t.Mode().String()
|
||||
result.HTTPPort = g.Port
|
||||
result.SocksPort = g.SocksPort
|
||||
result.RedirectPort = g.RedirPort
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type Proxy struct {
|
||||
Name string
|
||||
Type string `json:"type"`
|
||||
All []string `json:"all"`
|
||||
Now string `json:"now"`
|
||||
Delay int
|
||||
}
|
||||
|
||||
type ProxyList struct {
|
||||
proxies []*Proxy
|
||||
}
|
||||
|
||||
func (p Proxy) GetAllLength() int {
|
||||
return len(p.All)
|
||||
}
|
||||
|
||||
func (p Proxy) GetAllElement(index int) string {
|
||||
return p.All[index]
|
||||
}
|
||||
|
||||
func (pl *ProxyList) GetProxiesLength() int {
|
||||
return len(pl.proxies)
|
||||
}
|
||||
|
||||
func (pl *ProxyList) GetProxiesElement(index int) *Proxy {
|
||||
return pl.proxies[index]
|
||||
}
|
||||
|
||||
func QueryAllProxies() (*ProxyList, error) {
|
||||
ps := tunnel.Instance().Proxies()
|
||||
result := make([]*Proxy, len(ps))
|
||||
currentIndex := 0
|
||||
|
||||
for k, v := range ps {
|
||||
current := &Proxy{
|
||||
Name: k,
|
||||
Type: v.Type().String(),
|
||||
All: []string{},
|
||||
Delay: int(v.LastDelay()),
|
||||
}
|
||||
|
||||
data, err := v.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(data, current)
|
||||
|
||||
result[currentIndex] = current
|
||||
|
||||
currentIndex++
|
||||
}
|
||||
|
||||
return &ProxyList{proxies: result}, nil
|
||||
}
|
||||
@@ -1,194 +1,118 @@
|
||||
package com.github.kr328.clash.core
|
||||
|
||||
import android.content.Context
|
||||
import android.net.LocalSocket
|
||||
import com.github.kr328.clash.core.event.BandwidthEvent
|
||||
import com.github.kr328.clash.core.event.LogEvent
|
||||
import bridge.Bridge
|
||||
import com.github.kr328.clash.core.event.ProcessEvent
|
||||
import com.github.kr328.clash.core.event.SpeedEvent
|
||||
import com.github.kr328.clash.core.model.*
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.stringify
|
||||
import java.io.File
|
||||
import java.io.FileDescriptor
|
||||
import java.io.IOException
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.Proxy
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
class Clash(
|
||||
context: Context,
|
||||
clashDir: File,
|
||||
controllerPath: File,
|
||||
listener: (ProcessEvent) -> Unit
|
||||
) : BaseClash(controllerPath) {
|
||||
private val listener: (ProcessEvent) -> Unit
|
||||
) {
|
||||
companion object {
|
||||
const val COMMAND_PING = 0
|
||||
const val COMMAND_TUN_START = 1
|
||||
const val COMMAND_TUN_STOP = 2
|
||||
const val COMMAND_PROFILE_RELOAD = 4
|
||||
const val COMMAND_QUERY_PROXIES = 5
|
||||
const val COMMAND_PULL_SPEED = 6
|
||||
const val COMMAND_PULL_LOG = 7
|
||||
const val COMMAND_PULL_BANDWIDTH = 8
|
||||
const val COMMAND_SET_PROXY = 9
|
||||
const val COMMAND_QUERY_GENERAL = 10
|
||||
const val COMMAND_URL_TEST = 11
|
||||
|
||||
const val PING_REPLY = 233
|
||||
const val CLASH_DIR = "clash"
|
||||
|
||||
const val DEFAULT_URL_TEST_TIMEOUT = 5000
|
||||
const val DEFAULT_URL_TEST_URL = "https://www.gstatic.com/generate_204"
|
||||
}
|
||||
|
||||
val process = ClashProcess(context, clashDir, controllerPath, listener)
|
||||
private var currentProcess = ProcessEvent.STOPPED
|
||||
|
||||
fun ping(): Boolean {
|
||||
return runControlNoException(COMMAND_PING) { _, input, _ ->
|
||||
input.readInt() == PING_REPLY
|
||||
} ?: false
|
||||
init {
|
||||
val home = context.filesDir.resolve(CLASH_DIR)
|
||||
|
||||
home.mkdirs()
|
||||
|
||||
Bridge.init(home.absolutePath)
|
||||
}
|
||||
|
||||
fun loadProfile(file: File, selected: Map<String, String>): List<String> {
|
||||
return runControl(COMMAND_PROFILE_RELOAD) { _, input, output ->
|
||||
output.writeString(
|
||||
Json(JsonConfiguration.Stable)
|
||||
.stringify(
|
||||
LoadProfilePacket.Request.serializer(),
|
||||
LoadProfilePacket.Request(file.absolutePath, selected)
|
||||
)
|
||||
)
|
||||
|
||||
val result = Json(JsonConfiguration.Stable)
|
||||
.parse(LoadProfilePacket.Response.serializer(), input.readString())
|
||||
|
||||
if (result.error.isNotEmpty()) {
|
||||
throw IOException(result.error)
|
||||
}
|
||||
|
||||
return@runControl result.invalidSelected
|
||||
}
|
||||
fun getCurrentProcessStatus(): ProcessEvent {
|
||||
return currentProcess
|
||||
}
|
||||
|
||||
fun queryGeneral(): GeneralPacket {
|
||||
return runCatching {
|
||||
runControl(COMMAND_QUERY_GENERAL) { _, input, _ ->
|
||||
Json(JsonConfiguration.Stable).parse(GeneralPacket.serializer(), input.readString())
|
||||
}
|
||||
}.getOrDefault(
|
||||
GeneralPacket(
|
||||
GeneralPacket.Ports(0, 0, 0, 0),
|
||||
GeneralPacket.Mode.DIRECT
|
||||
)
|
||||
)
|
||||
fun start() {
|
||||
if ( currentProcess == ProcessEvent.STARTED )
|
||||
return
|
||||
|
||||
currentProcess = ProcessEvent.STARTED
|
||||
|
||||
listener(currentProcess)
|
||||
|
||||
loadDefault()
|
||||
}
|
||||
|
||||
fun queryProxies(): RawProxyPacket {
|
||||
return runControl(COMMAND_QUERY_PROXIES) { _, input, _ ->
|
||||
val data = input.readString()
|
||||
fun stop() {
|
||||
if ( currentProcess == ProcessEvent.STOPPED )
|
||||
return
|
||||
|
||||
Json(JsonConfiguration.Stable.copy(strictMode = false))
|
||||
.parse(RawProxyPacket.serializer(), data)
|
||||
}
|
||||
currentProcess = ProcessEvent.STOPPED
|
||||
|
||||
listener(currentProcess)
|
||||
|
||||
loadDefault()
|
||||
}
|
||||
|
||||
fun setSelectProxy(key: String, value: String) {
|
||||
runControl(COMMAND_SET_PROXY) { _, input, output ->
|
||||
output.writeString(
|
||||
Json(JsonConfiguration.Stable)
|
||||
.stringify(
|
||||
SetProxyPacket.Request.serializer(),
|
||||
SetProxyPacket.Request(key, value)
|
||||
)
|
||||
)
|
||||
fun startTunDevice(fd: Int, mtu: Int, gateway: String, dns: String, onSocket: (Int) -> Unit) {
|
||||
enforceStarted()
|
||||
|
||||
val response = Json(JsonConfiguration.Stable).parse(
|
||||
SetProxyPacket.Response.serializer(),
|
||||
input.readString()
|
||||
)
|
||||
|
||||
if (response.error.isNotEmpty())
|
||||
throw IOException(response.error)
|
||||
}
|
||||
}
|
||||
|
||||
fun pullSpeedEvent(initial: (LocalSocket) -> Unit, callback: (SpeedEvent) -> Unit) {
|
||||
runControlNoException(COMMAND_PULL_SPEED) { socket, input, _ ->
|
||||
initial(socket)
|
||||
|
||||
while (!Thread.currentThread().isInterrupted) {
|
||||
callback(
|
||||
Json(JsonConfiguration.Stable).parse(
|
||||
SpeedEvent.serializer(),
|
||||
input.readString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pullLogsEvent(initial: (LocalSocket) -> Unit, callback: (LogEvent) -> Unit) {
|
||||
runControlNoException(COMMAND_PULL_LOG) { socket, input, _ ->
|
||||
initial(socket)
|
||||
|
||||
while (!Thread.currentThread().isInterrupted) {
|
||||
callback(
|
||||
Json(JsonConfiguration.Stable).parse(
|
||||
LogEvent.serializer(),
|
||||
input.readString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pullBandwidthEvent(initial: (LocalSocket) -> Unit, callback: (BandwidthEvent) -> Unit) {
|
||||
runControlNoException(COMMAND_PULL_BANDWIDTH) { socket, input, _ ->
|
||||
initial(socket)
|
||||
|
||||
while (!Thread.currentThread().isInterrupted) {
|
||||
callback(
|
||||
Json(JsonConfiguration.Stable).parse(
|
||||
BandwidthEvent.serializer(),
|
||||
input.readString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startUrlTest(proxies: List<String>, callback: (String, Long) -> Unit) {
|
||||
runControl(COMMAND_URL_TEST) { _, input, output ->
|
||||
output.writeString(Json(JsonConfiguration.Stable)
|
||||
.stringify(UrlTestPacket.Request.serializer(),
|
||||
UrlTestPacket.Request(proxies, DEFAULT_URL_TEST_TIMEOUT, DEFAULT_URL_TEST_URL)))
|
||||
|
||||
while ( true ) {
|
||||
val data = input.readString()
|
||||
if ( data.isEmpty() )
|
||||
return@runControl
|
||||
|
||||
val response = Json(JsonConfiguration.Stable)
|
||||
.parse(UrlTestPacket.Response.serializer(), data)
|
||||
|
||||
callback(response.name, response.delay)
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("Url test exited")
|
||||
}
|
||||
|
||||
fun startTunDevice(fd: FileDescriptor, mtu: Int, dnsHijacking: Boolean) {
|
||||
runControl(COMMAND_TUN_START) { socket, _, output ->
|
||||
socket.setFileDescriptorsForSend(arrayOf(fd))
|
||||
socket.outputStream.write(0)
|
||||
socket.outputStream.flush()
|
||||
output.writeInt(mtu)
|
||||
output.writeInt(if ( dnsHijacking ) 1 else 0)
|
||||
output.writeInt(0x243)
|
||||
Bridge.startTunDevice(fd.toLong(), mtu.toLong(), gateway, dns) {
|
||||
onSocket(it.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
fun stopTunDevice() {
|
||||
runControlNoException(COMMAND_TUN_STOP)
|
||||
Bridge.stopTunDevice()
|
||||
}
|
||||
|
||||
fun loadProfile(path: String) {
|
||||
enforceStarted()
|
||||
|
||||
Bridge.loadProfileFile(path)
|
||||
}
|
||||
|
||||
fun queryProxies(): List<Proxy> {
|
||||
enforceStarted()
|
||||
|
||||
val list = Bridge.queryAllProxies()
|
||||
val result = mutableListOf<Proxy>()
|
||||
|
||||
for (i in 0..list.proxiesLength) {
|
||||
val p = list.getProxiesElement(i)
|
||||
val all = mutableListOf<String>()
|
||||
|
||||
for (index in 0..p.allLength) {
|
||||
all.add(p.getAllElement(index))
|
||||
}
|
||||
|
||||
result.add(
|
||||
Proxy(
|
||||
p.name,
|
||||
Proxy.Type.fromString(p.type),
|
||||
p.now,
|
||||
all,
|
||||
p.delay
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun queryGeneral(): General {
|
||||
val t = Bridge.queryGeneral()
|
||||
|
||||
return General(General.Mode.fromString(t.mode),
|
||||
t.httpPort.toInt(), t.socksPort.toInt(), t.redirectPort.toInt())
|
||||
}
|
||||
|
||||
private fun loadDefault() {
|
||||
Bridge.loadProfileDefault()
|
||||
}
|
||||
|
||||
private fun enforceStarted() {
|
||||
if ( currentProcess == ProcessEvent.STOPPED )
|
||||
throw IllegalStateException("Clash Stopped")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.github.kr328.clash.core.serialization.Parcels
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
fun List<Proxy>.compress(): CompressedProxyList {
|
||||
val nameMap = this.toList().mapIndexed { index, proxy ->
|
||||
proxy.name to index
|
||||
}.toMap()
|
||||
|
||||
val elements = this.map {
|
||||
CompressedProxyList.Element(
|
||||
name = nameMap[it.name] ?: throw IllegalStateException("Unknown proxy $it"),
|
||||
type = it.type,
|
||||
now = nameMap[it.now] ?: throw IllegalStateException("Unknown proxy $it"),
|
||||
all = it.all.mapNotNull { name -> nameMap[name] },
|
||||
delay = it.delay
|
||||
)
|
||||
}
|
||||
|
||||
return CompressedProxyList(nameMap.entries.associate { (k, v) -> v to k }, elements)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class CompressedProxyList(val proxyName: Map<Int, String>, val elements: List<Element>): Parcelable {
|
||||
@Serializable
|
||||
data class Element(val name: Int,
|
||||
val type: Proxy.Type,
|
||||
val now: Int,
|
||||
val all: List<Int>,
|
||||
val delay: Long)
|
||||
|
||||
fun uncompress(): List<Proxy> {
|
||||
return elements.map {
|
||||
Proxy(
|
||||
name = proxyName[it.name] ?: throw IllegalStateException("Unknown proxy $it"),
|
||||
type = it.type,
|
||||
now = proxyName[it.now] ?: throw IllegalStateException("Unknown proxy $it"),
|
||||
all = it.all.mapNotNull { index -> proxyName[index] },
|
||||
delay = it.delay
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
Parcels.dump(serializer(), this, parcel)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<CompressedProxyList> {
|
||||
override fun createFromParcel(parcel: Parcel): CompressedProxyList {
|
||||
return Parcels.load(serializer(), parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<CompressedProxyList?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
-18
@@ -5,25 +5,35 @@ import android.os.Parcelable
|
||||
import com.github.kr328.clash.core.serialization.Parcels
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.internal.StringDescriptor
|
||||
import java.lang.IllegalArgumentException
|
||||
import kotlin.IllegalArgumentException
|
||||
|
||||
@Serializable
|
||||
data class GeneralPacket(val ports: Ports, val mode: Mode) : Parcelable {
|
||||
data class General(val mode: Mode, val http: Int, val socks: Int, val redirect: Int) : Parcelable {
|
||||
@Serializable(with = ModeSerializer::class)
|
||||
enum class Mode {
|
||||
DIRECT, GLOBAL, RULE
|
||||
}
|
||||
DIRECT, GLOBAL, RULE;
|
||||
|
||||
@Serializable
|
||||
data class Ports(val http: Int, val socks: Int, val redirect: Int, val randomHttp: Int)
|
||||
|
||||
class ModeSerializer : KSerializer<Mode> {
|
||||
companion object {
|
||||
const val MODE_DIRECT = 1
|
||||
const val MODE_GLOBAL = 2
|
||||
const val MODE_RULE = 3
|
||||
override fun toString(): String {
|
||||
return when ( this ) {
|
||||
DIRECT -> "Direct"
|
||||
GLOBAL -> "Global"
|
||||
RULE -> "Rule"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromString(mode: String): Mode {
|
||||
return when ( mode ) {
|
||||
"Direct" -> DIRECT
|
||||
"Global" -> GLOBAL
|
||||
"Rule" -> RULE
|
||||
else -> throw IllegalArgumentException("Invalid mode $mode")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ModeSerializer : KSerializer<Mode> {
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = StringDescriptor
|
||||
|
||||
@@ -53,13 +63,20 @@ data class GeneralPacket(val ports: Ports, val mode: Mode) : Parcelable {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<GeneralPacket> {
|
||||
override fun createFromParcel(parcel: Parcel): GeneralPacket {
|
||||
return Parcels.load(serializer(), parcel)
|
||||
}
|
||||
companion object {
|
||||
const val MODE_DIRECT = 1
|
||||
const val MODE_GLOBAL = 2
|
||||
const val MODE_RULE = 3
|
||||
|
||||
override fun newArray(size: Int): Array<GeneralPacket?> {
|
||||
return arrayOfNulls(size)
|
||||
@JvmField
|
||||
val CREATOR = object: Parcelable.Creator<General> {
|
||||
override fun createFromParcel(parcel: Parcel): General {
|
||||
return Parcels.load(serializer(), parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<General?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
class LoadProfilePacket {
|
||||
@Serializable
|
||||
data class Request(val path: String, val selected: Map<String, String>)
|
||||
@Serializable
|
||||
data class Response(val error: String, val invalidSelected: List<String>)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Proxy(
|
||||
val name: String,
|
||||
val type: Type,
|
||||
val now: String,
|
||||
val all: List<String>,
|
||||
val delay: Long
|
||||
) {
|
||||
enum class Type {
|
||||
SELECT,
|
||||
URL_TEST,
|
||||
FALLBACK,
|
||||
DIRECT,
|
||||
REJECT,
|
||||
SHADOWSOCKS,
|
||||
SNELL,
|
||||
SOCKS5,
|
||||
HTTP,
|
||||
VMESS,
|
||||
LOAD_BALANCE,
|
||||
UNKNOWN;
|
||||
|
||||
override fun toString(): String {
|
||||
return when (this) {
|
||||
SELECT -> TYPE_SELECT
|
||||
URL_TEST -> TYPE_URL_TEST
|
||||
FALLBACK -> TYPE_FALLBACK
|
||||
DIRECT -> TYPE_DIRECT
|
||||
REJECT -> TYPE_REJECT
|
||||
SHADOWSOCKS -> TYPE_SHADOWSOCKS
|
||||
SNELL -> TYPE_SNELL
|
||||
SOCKS5 -> TYPE_SOCKS5
|
||||
HTTP -> TYPE_HTTP
|
||||
VMESS -> TYPE_VMESS
|
||||
LOAD_BALANCE -> TYPE_LOAD_BALANCE
|
||||
UNKNOWN -> TYPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromString(type: String): Type {
|
||||
return when (type) {
|
||||
TYPE_SELECT -> SELECT
|
||||
TYPE_URL_TEST -> URL_TEST
|
||||
TYPE_FALLBACK -> FALLBACK
|
||||
TYPE_DIRECT -> DIRECT
|
||||
TYPE_REJECT -> REJECT
|
||||
TYPE_SHADOWSOCKS -> SHADOWSOCKS
|
||||
TYPE_SNELL -> SNELL
|
||||
TYPE_SOCKS5 -> SOCKS5
|
||||
TYPE_HTTP -> HTTP
|
||||
TYPE_VMESS -> VMESS
|
||||
TYPE_LOAD_BALANCE -> LOAD_BALANCE
|
||||
TYPE_UNKNOWN -> UNKNOWN
|
||||
else -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TYPE_SELECT = "Selector"
|
||||
private const val TYPE_URL_TEST = "URLTest"
|
||||
private const val TYPE_FALLBACK = "Fallback"
|
||||
private const val TYPE_DIRECT = "Direct"
|
||||
private const val TYPE_REJECT = "Reject"
|
||||
private const val TYPE_SHADOWSOCKS = "Shadowsocks"
|
||||
private const val TYPE_SNELL = "Snell"
|
||||
private const val TYPE_SOCKS5 = "Socks5"
|
||||
private const val TYPE_HTTP = "Http"
|
||||
private const val TYPE_VMESS = "Vmess"
|
||||
private const val TYPE_LOAD_BALANCE = "LoadBalance"
|
||||
private const val TYPE_UNKNOWN = "Unknown"
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.github.kr328.clash.core.serialization.Parcels
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ProxyPacket(val mode: String, val proxies: Map<Int, Proxy>): Parcelable {
|
||||
@Serializable
|
||||
data class Proxy(val name: String, val type: Type, val now: Int, val all: List<Int>, val delay: Long)
|
||||
|
||||
enum class Type {
|
||||
SELECT,
|
||||
URL_TEST,
|
||||
FALLBACK,
|
||||
DIRECT,
|
||||
REJECT,
|
||||
SHADOWSOCKS,
|
||||
SNELL,
|
||||
SOCKS5,
|
||||
HTTP,
|
||||
VMESS,
|
||||
LOAD_BALANCE,
|
||||
UNKNOWN;
|
||||
|
||||
override fun toString(): String {
|
||||
return when ( this ) {
|
||||
SELECT -> "Select"
|
||||
URL_TEST -> "UrlTest"
|
||||
FALLBACK -> "Fallback"
|
||||
DIRECT -> "Direct"
|
||||
REJECT -> "Reject"
|
||||
SHADOWSOCKS -> "Shadowsocks"
|
||||
SNELL -> "Snell"
|
||||
SOCKS5 -> "Socks5"
|
||||
HTTP -> "HTTP"
|
||||
VMESS -> "Vmess"
|
||||
LOAD_BALANCE -> "LoadBalance"
|
||||
UNKNOWN -> "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TYPE_SELECT = "Selector"
|
||||
private const val TYPE_URL_TEST = "URLTest"
|
||||
private const val TYPE_FALLBACK = "Fallback"
|
||||
private const val TYPE_DIRECT = "Direct"
|
||||
private const val TYPE_REJECT = "Reject"
|
||||
private const val TYPE_SHADOWSOCKS = "Shadowsocks"
|
||||
private const val TYPE_SNELL = "Snell"
|
||||
private const val TYPE_SOCKS5 = "Socks5"
|
||||
private const val TYPE_HTTP = "Http"
|
||||
private const val TYPE_VMESS = "Vmess"
|
||||
private const val TYPE_LOAD_BALANCE = "LoadBalance"
|
||||
private const val TYPE_UNKNOWN = "Unknown"
|
||||
|
||||
fun fromRawProxy(rawProxy: RawProxyPacket): ProxyPacket {
|
||||
val hashed = rawProxy.proxies
|
||||
.map { it.key to (it.key.hashCode() to it.value) }.toMap()
|
||||
|
||||
val proxies = hashed.map { entry ->
|
||||
val type = when ( entry.value.second.type ) {
|
||||
TYPE_SELECT -> Type.SELECT
|
||||
TYPE_URL_TEST -> Type.URL_TEST
|
||||
TYPE_FALLBACK -> Type.FALLBACK
|
||||
TYPE_DIRECT -> Type.DIRECT
|
||||
TYPE_REJECT -> Type.REJECT
|
||||
TYPE_SHADOWSOCKS -> Type.SHADOWSOCKS
|
||||
TYPE_SNELL -> Type.SNELL
|
||||
TYPE_SOCKS5 -> Type.SOCKS5
|
||||
TYPE_HTTP -> Type.HTTP
|
||||
TYPE_VMESS -> Type.VMESS
|
||||
TYPE_LOAD_BALANCE -> Type.LOAD_BALANCE
|
||||
TYPE_UNKNOWN -> Type.UNKNOWN
|
||||
else -> Type.UNKNOWN
|
||||
}
|
||||
|
||||
val now = hashed[entry.value.second.now]?.first ?: 0
|
||||
val all = entry.value.second.all.mapNotNull { hashed[it]?.first }
|
||||
val delay = entry.value.second.history.firstOrNull()?.delay ?: 0
|
||||
|
||||
entry.value.first to Proxy(entry.key, type, now, all, delay)
|
||||
}
|
||||
|
||||
return ProxyPacket(rawProxy.mode, proxies.toMap())
|
||||
}
|
||||
|
||||
@JvmField
|
||||
val CREATOR = object: Parcelable.Creator<ProxyPacket> {
|
||||
override fun createFromParcel(parcel: Parcel): ProxyPacket {
|
||||
return Parcels.load(serializer(), parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<ProxyPacket?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
Parcels.dump(serializer(), this, parcel)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RawProxyPacket(val mode: String, val proxies: Map<String, RawProxy>) {
|
||||
@Serializable
|
||||
data class RawProxy(val type: String,
|
||||
val all: List<String> = emptyList(),
|
||||
val now: String = "",
|
||||
val history: List<History> = emptyList()) {
|
||||
@Serializable
|
||||
data class History(val delay: Long)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
class SetProxyPacket {
|
||||
@Serializable
|
||||
data class Request(val key: String, val value: String)
|
||||
@Serializable
|
||||
data class Response(val error: String)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.github.kr328.clash.core.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
class UrlTestPacket {
|
||||
@Serializable
|
||||
data class Request(val proxies: List<String>, val timeout: Int, val url: String)
|
||||
@Serializable
|
||||
data class Response(val name: String, val delay: Long)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.github.kr328.clash.core.model;
|
||||
|
||||
parcelable ProxyPacket;
|
||||
parcelable GeneralPacket;
|
||||
parcelable CompressedProxyList;
|
||||
parcelable General;
|
||||
@@ -23,7 +23,7 @@ interface IClashService {
|
||||
void stop();
|
||||
|
||||
// Query
|
||||
ProxyPacket queryAllProxies();
|
||||
GeneralPacket queryGeneral();
|
||||
CompressedProxyList queryAllProxies();
|
||||
General queryGeneral();
|
||||
void startUrlTest(in String[] proxies, IUrlTestCallback callback);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Context
|
||||
import com.github.kr328.clash.core.event.*
|
||||
|
||||
class ClashEventBridge(val service: ClashService): ClashProfileService.Master,
|
||||
ClashEventService.Master, ClashEventPuller.Master {
|
||||
val eventService: ClashEventService = ClashEventService(this)
|
||||
|
||||
override fun preformProfileChanged() {
|
||||
eventService.performProfileChangedEvent(ProfileChangedEvent())
|
||||
}
|
||||
|
||||
override fun acquireEvent(event: Int) {
|
||||
service.acquireEvent(event)
|
||||
}
|
||||
|
||||
override fun releaseEvent(event: Int) {
|
||||
service.releaseEvent(event)
|
||||
}
|
||||
|
||||
fun onProcessChanged(event: ProcessEvent) {
|
||||
eventService.performProcessEvent(event)
|
||||
}
|
||||
|
||||
override fun onLogPulled(event: LogEvent) {
|
||||
eventService.performLogEvent(event)
|
||||
}
|
||||
|
||||
override fun onSpeedPulled(event: SpeedEvent) {
|
||||
eventService.performSpeedEvent(event)
|
||||
}
|
||||
|
||||
override fun onBandwidthPulled(event: BandwidthEvent) {
|
||||
eventService.performBandwidthEvent(event)
|
||||
}
|
||||
}
|
||||
@@ -5,106 +5,40 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.*
|
||||
import bridge.Bridge
|
||||
import com.github.kr328.clash.callback.IUrlTestCallback
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.os.IInterface
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.event.*
|
||||
import com.github.kr328.clash.core.model.GeneralPacket
|
||||
import com.github.kr328.clash.core.model.ProxyPacket
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.Executors
|
||||
import javax.xml.transform.SourceLocator
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
|
||||
ClashProfileService.Master, ClashEventPuller.Master {
|
||||
class ClashService : Service(), IClashEventObserver {
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
|
||||
private val eventService = ClashEventService(this)
|
||||
private val profileService = ClashProfileService(this, this)
|
||||
private val settingService = ClashSettingService(this)
|
||||
private val instance: ClashServiceImpl by lazy {
|
||||
ClashServiceImpl(this)
|
||||
}
|
||||
|
||||
private var processStatus = ProcessEvent.STOPPED
|
||||
val events: ClashEventService
|
||||
get() = instance.eventService
|
||||
val clash: Clash
|
||||
get() = instance.clash
|
||||
|
||||
//private lateinit var puller: ClashEventPuller
|
||||
private lateinit var notification: ClashNotification
|
||||
|
||||
private val clashService = object : IClashService.Stub() {
|
||||
override fun setSelectProxy(proxy: String?, selected: String?) {
|
||||
require(proxy != null && selected != null)
|
||||
|
||||
try {
|
||||
this@ClashService.profileService.setCurrentProfileProxy(proxy, selected)
|
||||
} catch (e: IOException) {
|
||||
Log.w("Set proxy failure", e)
|
||||
|
||||
this@ClashService.eventService.performErrorEvent(
|
||||
ErrorEvent(ErrorEvent.Type.SET_PROXY_SELECTED, e.toString())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun queryGeneral(): GeneralPacket {
|
||||
return GeneralPacket(GeneralPacket.Ports(0, 0, 0, 0), GeneralPacket.Mode.DIRECT)
|
||||
}
|
||||
|
||||
override fun queryAllProxies(): ProxyPacket {
|
||||
return ProxyPacket("Direct", emptyMap())
|
||||
}
|
||||
|
||||
override fun startUrlTest(proxies: Array<out String>?, callback: IUrlTestCallback?) {
|
||||
require(proxies != null && callback != null)
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
if ( processStatus == ProcessEvent.STARTED )
|
||||
return
|
||||
|
||||
processStatus = ProcessEvent.STARTED
|
||||
|
||||
this@ClashService.eventService.performProcessEvent(processStatus)
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
if ( processStatus == ProcessEvent.STOPPED )
|
||||
return
|
||||
|
||||
processStatus = ProcessEvent.STOPPED
|
||||
|
||||
this@ClashService.eventService.performProcessEvent(processStatus)
|
||||
}
|
||||
|
||||
override fun getEventService(): IClashEventService {
|
||||
return this@ClashService.eventService
|
||||
}
|
||||
|
||||
override fun getProfileService(): IClashProfileService {
|
||||
return this@ClashService.profileService
|
||||
}
|
||||
|
||||
override fun getSettingService(): IClashSettingService {
|
||||
return this@ClashService.settingService
|
||||
}
|
||||
|
||||
override fun getCurrentProcessStatus(): ProcessEvent {
|
||||
return processStatus
|
||||
}
|
||||
}
|
||||
|
||||
private val screenReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SCREEN_ON ->
|
||||
eventService.registerEventObserver(
|
||||
instance.eventService.registerEventObserver(
|
||||
ClashService::class.java.name,
|
||||
this@ClashService,
|
||||
intArrayOf(Event.EVENT_SPEED)
|
||||
)
|
||||
Intent.ACTION_SCREEN_OFF ->
|
||||
eventService.registerEventObserver(
|
||||
instance.eventService.registerEventObserver(
|
||||
ClashService::class.java.name,
|
||||
this@ClashService,
|
||||
intArrayOf()
|
||||
@@ -113,27 +47,32 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
|
||||
}
|
||||
}
|
||||
|
||||
override fun acquireEvent(event: Int) {
|
||||
if (processStatus == ProcessEvent.STOPPED)
|
||||
private fun onClashProcessChanged(event: ProcessEvent) {
|
||||
instance.eventService.performProcessEvent(event)
|
||||
}
|
||||
|
||||
fun acquireEvent(event: Int) {
|
||||
if ( instance.clash.getCurrentProcessStatus() == ProcessEvent.STOPPED )
|
||||
return
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun releaseEvent(event: Int) {
|
||||
if (processStatus == ProcessEvent.STOPPED)
|
||||
fun releaseEvent(event: Int) {
|
||||
if ( instance.clash.getCurrentProcessStatus() == ProcessEvent.STOPPED )
|
||||
return
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
Bridge.init(filesDir.resolve("clash").absolutePath)
|
||||
|
||||
//puller = ClashEventPuller(clash, this)
|
||||
|
||||
notification = ClashNotification(this)
|
||||
|
||||
eventService.registerEventObserver(
|
||||
instance.eventService.registerEventObserver(
|
||||
ClashService::class.java.name,
|
||||
this@ClashService,
|
||||
intArrayOf(Event.EVENT_SPEED)
|
||||
@@ -148,20 +87,19 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
clashService.start()
|
||||
instance.clash.start()
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return clashService
|
||||
return instance
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
clashService.stop()
|
||||
instance.clash.stop()
|
||||
|
||||
executor.shutdown()
|
||||
eventService.shutdown()
|
||||
|
||||
unregisterReceiver(screenReceiver)
|
||||
|
||||
@@ -179,17 +117,15 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
|
||||
|
||||
notification.show()
|
||||
|
||||
eventService.recastEventRequirement()
|
||||
instance.eventService.recastEventRequirement()
|
||||
}
|
||||
ProcessEvent.STOPPED -> {
|
||||
eventService.performSpeedEvent(SpeedEvent(0, 0))
|
||||
eventService.performBandwidthEvent(BandwidthEvent(0))
|
||||
instance.eventService.performSpeedEvent(SpeedEvent(0, 0))
|
||||
instance.eventService.performBandwidthEvent(BandwidthEvent(0))
|
||||
|
||||
notification.cancel()
|
||||
|
||||
stopSelf()
|
||||
|
||||
Bridge.loadProfileDefault()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,28 +134,28 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
|
||||
|
||||
private fun reloadProfile() {
|
||||
executor.submit {
|
||||
if ( processStatus != ProcessEvent.STARTED)
|
||||
if ( clash.getCurrentProcessStatus() != ProcessEvent.STARTED)
|
||||
return@submit
|
||||
|
||||
val active = profileService.queryActiveProfile()
|
||||
val active = instance.profileService.queryActiveProfile()
|
||||
|
||||
if (active == null) {
|
||||
eventService.performErrorEvent(ErrorEvent(ErrorEvent.Type.PROFILE_LOAD, "No profile activated"))
|
||||
clashService.stop()
|
||||
sendError(ErrorEvent.Type.PROFILE_LOAD, "No profile activated")
|
||||
clash.stop()
|
||||
return@submit
|
||||
}
|
||||
|
||||
Log.i("Loading profile ${active.file}")
|
||||
|
||||
try {
|
||||
Bridge.loadProfileFile(active.file)
|
||||
clash.loadProfile(active.file)
|
||||
|
||||
notification.setProfile(active.name)
|
||||
|
||||
eventService.performProfileReloadEvent(ProfileReloadEvent())
|
||||
events.performProfileReloadEvent(ProfileReloadEvent())
|
||||
} catch (e: Exception) {
|
||||
clashService.stop()
|
||||
eventService.performErrorEvent(ErrorEvent(ErrorEvent.Type.PROFILE_LOAD, e.message ?: "Unknown"))
|
||||
clash.stop()
|
||||
sendError(ErrorEvent.Type.PROFILE_LOAD, e.message)
|
||||
Log.w("Load profile failure", e)
|
||||
}
|
||||
}
|
||||
@@ -233,22 +169,6 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
|
||||
notification.setSpeed(event?.up ?: 0, event?.down ?: 0)
|
||||
}
|
||||
|
||||
override fun preformProfileChanged() {
|
||||
eventService.performProfileChangedEvent(ProfileChangedEvent())
|
||||
}
|
||||
|
||||
override fun onLogPulled(event: LogEvent) {
|
||||
eventService.performLogEvent(event)
|
||||
}
|
||||
|
||||
override fun onSpeedPulled(event: SpeedEvent) {
|
||||
eventService.performSpeedEvent(event)
|
||||
}
|
||||
|
||||
override fun onBandwidthPulled(event: BandwidthEvent) {
|
||||
eventService.performBandwidthEvent(event)
|
||||
}
|
||||
|
||||
override fun onBandwidthEvent(event: BandwidthEvent?) {}
|
||||
override fun onLogEvent(event: LogEvent?) {}
|
||||
override fun onErrorEvent(event: ErrorEvent?) {}
|
||||
@@ -257,4 +177,8 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
|
||||
return this@ClashService
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendError(type: ErrorEvent.Type, message: String?) {
|
||||
events.performErrorEvent(ErrorEvent(type, message ?: "Unknown"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import com.github.kr328.clash.callback.IUrlTestCallback
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.event.ErrorEvent
|
||||
import com.github.kr328.clash.core.event.ProcessEvent
|
||||
import com.github.kr328.clash.core.model.CompressedProxyList
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.compress
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import java.io.IOException
|
||||
|
||||
class ClashServiceImpl(val clashService: ClashService) : IClashService.Stub() {
|
||||
private val bridge: ClashEventBridge = ClashEventBridge(clashService)
|
||||
|
||||
val clash: Clash = Clash(clashService, bridge::onProcessChanged)
|
||||
val profileService = ClashProfileService(clashService, bridge)
|
||||
val settingService = ClashSettingService(clashService)
|
||||
val eventService: ClashEventService
|
||||
get() = bridge.eventService
|
||||
|
||||
override fun setSelectProxy(proxy: String?, selected: String?) {
|
||||
require(proxy != null && selected != null)
|
||||
|
||||
try {
|
||||
profileService.setCurrentProfileProxy(proxy, selected)
|
||||
} catch (e: IOException) {
|
||||
Log.w("Set proxy failure", e)
|
||||
|
||||
eventService.performErrorEvent(
|
||||
ErrorEvent(ErrorEvent.Type.SET_PROXY_SELECTED, e.toString())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun queryGeneral(): General {
|
||||
return clash.queryGeneral()
|
||||
}
|
||||
|
||||
override fun queryAllProxies(): CompressedProxyList {
|
||||
return try {
|
||||
clash.queryProxies().compress()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
Log.w("Query proxies", e)
|
||||
|
||||
eventService.performErrorEvent(ErrorEvent(ErrorEvent.Type.QUERY_PROXY_FAILURE, e.message ?: "Unknown"))
|
||||
|
||||
CompressedProxyList(emptyMap(), emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
override fun startUrlTest(proxies: Array<out String>?, callback: IUrlTestCallback?) {
|
||||
require(proxies != null && callback != null)
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
clash.start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
clash.stop()
|
||||
}
|
||||
|
||||
override fun getEventService(): IClashEventService {
|
||||
return eventService
|
||||
}
|
||||
|
||||
override fun getProfileService(): IClashProfileService {
|
||||
return profileService
|
||||
}
|
||||
|
||||
override fun getSettingService(): IClashSettingService {
|
||||
return settingService
|
||||
}
|
||||
|
||||
override fun getCurrentProcessStatus(): ProcessEvent {
|
||||
return clash.getCurrentProcessStatus()
|
||||
}
|
||||
|
||||
fun getClashInstance(): Clash {
|
||||
return clash
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
eventService.shutdown()
|
||||
}
|
||||
}
|
||||
@@ -143,6 +143,8 @@ class TunService : VpnService(), IClashEventObserver {
|
||||
private fun Builder.addBypassPrivateRoute(): Builder {
|
||||
// IPv4
|
||||
if ( settings.isBypassPrivateNetwork ) {
|
||||
Log.i("Bypass Private Network")
|
||||
|
||||
resources.getStringArray(R.array.bypass_private_route).forEach {
|
||||
val address = it.split("/")
|
||||
addRoute(address[0], address[1].toInt())
|
||||
|
||||
Reference in new issue
Block a user