Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion app/src/main/java/com/github/kr328/clash/ProxyActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
design.requests.send(ProxyDesign.Request.Reload(it.index))
}
}
is ProxyDesign.Request.HealthCheck -> {
launch {
withClash {
healthCheckProxy(names[it.index], it.name)
}

design.requests.send(ProxyDesign.Request.Reload(it.index))
}
}
is ProxyDesign.Request.PatchMode -> {
design.showModeSwitchTips()

Expand All @@ -115,4 +124,4 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
}
}
}
}
}
18 changes: 17 additions & 1 deletion core/src/main/cpp/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,23 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheck(JNIEnv *env, jo
}

JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheckAll(JNIEnv *env, jobject thiz) {
Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheckProxy(JNIEnv *env, jobject thiz,
jobject completable,
jstring group_name,
jstring proxy_name)
{
TRACE_METHOD();

jobject _completable = new_global(completable);
scoped_string _group_name = get_string(group_name);
scoped_string _proxy_name = get_string(proxy_name);

healthCheckProxy(_completable, _group_name, _proxy_name);
}

JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheckAll(JNIEnv *env, jobject thiz)
{
TRACE_METHOD();

healthCheckAll();
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/golang/native/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ func healthCheck(completable unsafe.Pointer, name C.c_string) {
}(C.GoString(name))
}

//export healthCheckProxy
func healthCheckProxy(completable unsafe.Pointer, groupName C.c_string, proxyName C.c_string) {
go func(groupName string, proxyName string) {
tunnel.HealthCheckProxy(groupName, proxyName)

C.complete(completable, nil)
}(C.GoString(groupName), C.GoString(proxyName))
}

//export healthCheckAll
func healthCheckAll() {
tunnel.HealthCheckAll()
Expand Down
54 changes: 53 additions & 1 deletion core/src/main/golang/native/tunnel/connectivity.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package tunnel

import (
"context"
"sync"
"time"

"github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/constant/provider"
Expand All @@ -20,7 +22,20 @@ func HealthCheck(name string) {

g, ok := p.Adapter().(outboundgroup.ProxyGroup)
if !ok {
log.Warnln("Request health check for `%s`: invalid type %s", name, p.Type().String())
testURL := "https://www.gstatic.com/generate_204"
for k := range p.ExtraDelayHistories() {
if len(k) > 0 {
testURL = k
break
}
}

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if _, err := p.URLTest(ctx, testURL, nil); err != nil && ctx.Err() == nil {
log.Warnln("Request health check for `%s`: %s", name, err.Error())
}

return
}
Expand All @@ -47,3 +62,40 @@ func HealthCheckAll() {
}(g)
}
}

func HealthCheckProxy(groupName string, proxyName string) {
p := tunnel.Proxies()[groupName]

if p == nil {
log.Warnln("Request health check for proxy `%s` in group `%s`: group not found", proxyName, groupName)
return
}

g, ok := p.Adapter().(outboundgroup.ProxyGroup)
if !ok {
log.Warnln("Request health check for proxy `%s` in group `%s`: not a proxy group", proxyName, groupName)
return
}

for _, proxy := range g.Proxies() {
if proxy.Name() == proxyName {
testURL := "https://www.gstatic.com/generate_204"
for k := range proxy.ExtraDelayHistories() {
if len(k) > 0 {
testURL = k
break
}
}

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if _, err := proxy.URLTest(ctx, testURL, nil); err != nil && ctx.Err() == nil {
log.Warnln("Request health check for proxy `%s`: %s", proxyName, err.Error())
}
return
}
}

log.Warnln("Request health check for proxy `%s` in group `%s`: proxy not found", proxyName, groupName)
}
6 changes: 6 additions & 0 deletions core/src/main/java/com/github/kr328/clash/core/Clash.kt
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ object Clash {
}
}

fun healthCheckProxy(groupName: String, proxyName: String): CompletableDeferred<Unit> {
return CompletableDeferred<Unit>().apply {
Bridge.nativeHealthCheckProxy(this, groupName, proxyName)
}
}

fun healthCheckAll() {
Bridge.nativeHealthCheckAll()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ object Bridge {
external fun nativeQueryGroupNames(excludeNotSelectable: Boolean): String
external fun nativeQueryGroup(name: String, sort: String): String?
external fun nativeHealthCheck(completable: CompletableDeferred<Unit>, name: String)
external fun nativeHealthCheckProxy(completable: CompletableDeferred<Unit>, groupName: String, proxyName: String)
external fun nativeHealthCheckAll()
external fun nativePatchSelector(selector: String, name: String): Boolean
external fun nativeFetchAndValid(
Expand Down
15 changes: 11 additions & 4 deletions design/src/main/java/com/github/kr328/clash/design/ProxyDesign.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class ProxyDesign(
data class Reload(val index: Int) : Request()
data class Select(val index: Int, val name: String) : Request()
data class UrlTest(val index: Int) : Request()
data class HealthCheck(val index: Int, val name: String) : Request()
}

private val binding = DesignProxyBinding
Expand Down Expand Up @@ -116,9 +117,15 @@ class ProxyDesign(
surface,
config,
List(groupNames.size) { index ->
ProxyAdapter(config) { name ->
requests.trySend(Request.Select(index, name))
}
ProxyAdapter(
config,
{ name ->
requests.trySend(Request.Select(index, name))
},
{ name ->
requests.trySend(Request.HealthCheck(index, name))
}
)
}
) {
if (it == currentItem)
Expand Down Expand Up @@ -174,4 +181,4 @@ class ProxyDesign(
binding.urlTestProgressView.visibility = View.GONE
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.github.kr328.clash.design.component.ProxyViewState
class ProxyAdapter(
private val config: ProxyViewConfig,
private val clicked: (String) -> Unit,
private val delayClicked: (String) -> Unit,
) : RecyclerView.Adapter<ProxyAdapter.Holder>() {
class Holder(val view: ProxyView) : RecyclerView.ViewHolder(view)

Expand All @@ -24,6 +25,9 @@ class ProxyAdapter(

holder.view.apply {
state = current
onDelayClick = {
delayClicked(current.proxy.name)
}

setOnClickListener {
clicked(current.proxy.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.github.kr328.clash.design.view.VerticalScrollableHost

class ProxyPageFactory(private val config: ProxyViewConfig) {
Expand Down Expand Up @@ -48,6 +49,9 @@ class ProxyPageFactory(private val config: ProxyViewConfig) {
setRecycledViewPool(childrenPool)

clipToPadding = false

// Avoid default change-animation flicker when delay updates trigger notifyItemRangeChanged.
(itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
}

return Holder(recyclerView, root).apply {
Expand All @@ -58,4 +62,4 @@ class ProxyPageFactory(private val config: ProxyViewConfig) {
fun fromRoot(root: View): Holder {
return root.tag!! as Holder
}
}
}
Loading