Skip to content
Merged
20 changes: 17 additions & 3 deletions core/src/main/cpp/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryGroup(JNIEnv *env, job

JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheck(JNIEnv *env, jobject thiz,
jobject completable,
jstring name) {
jobject completable,
jstring name) {
TRACE_METHOD();

jobject _completable = new_global(completable);
Expand All @@ -195,6 +195,20 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheck(JNIEnv *env, jo
healthCheck(_completable, _name);
}

JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheckProxy(JNIEnv *env, jobject thiz,
jobject completable,
jstring groupName,
jstring proxyName) {
TRACE_METHOD();

jobject _completable = new_global(completable);
scoped_string _groupName = get_string(groupName);
scoped_string _proxyName = get_string(proxyName);

healthCheckProxy(_completable, _groupName, _proxyName);
}

JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheckAll(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
Expand Down Expand Up @@ -526,4 +540,4 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeCoreVersion(JNIEnv *env, jo
char* Version = make_String(GIT_VERSION);

return new_string(Version);
}
}
11 changes: 11 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,17 @@ 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.release_object(completable)
}(C.GoString(groupName), C.GoString(proxyName))
}
Comment thread
Goooler marked this conversation as resolved.

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

import (
"context"
"sync"
"time"

"github.com/metacubex/mihomo/adapter/outboundgroup"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
)

const healthCheckTimeout = 5 * time.Second
const defaultHealthCheckURL = "https://www.gstatic.com/generate_204"

func probeURL(proxy C.Proxy, proxyName string) {
testURL := defaultHealthCheckURL
for k := range proxy.ExtraDelayHistories() {
if len(k) > 0 {
testURL = k
break
}
}

ctx, cancel := context.WithTimeout(context.Background(), healthCheckTimeout)
defer cancel()

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

func HealthCheck(name string) {
p := tunnel.Proxies()[name]

Expand All @@ -20,7 +48,7 @@ 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())
probeURL(p, name)

return
}
Expand All @@ -47,3 +75,39 @@ 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 {
probeURL(proxy, proxyName)
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/kotlin/com/github/kr328/clash/core/Clash.kt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ object Clash {
return CompletableDeferred<Unit>().apply { Bridge.nativeHealthCheck(this, name) }
}

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 @@ -49,6 +49,12 @@ object Bridge {

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ class ClashManager(private val context: Context) :
return Clash.healthCheck(group).await()
}

override suspend fun healthCheckProxy(group: String, name: String) {
return Clash.healthCheckProxy(group, name).await()
}

override suspend fun updateProvider(type: Provider.Type, name: String) {
return Clash.updateProvider(type, name).await()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ interface IClashManager {

suspend fun healthCheck(group: String)

suspend fun healthCheckProxy(group: String, name: String)

suspend fun updateProvider(type: Provider.Type, name: String)

fun queryOverride(slot: Clash.OverrideSlot): ConfigurationOverride
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator
Expand Down Expand Up @@ -114,6 +115,7 @@ internal fun ProxyScreen(
onProxySortChanged = viewModel::onProxySortChanged,
onOverrideModeSelected = viewModel::onOverrideModeSelected,
onProxySelected = viewModel::onProxySelected,
onProxyDelayTest = viewModel::onProxyDelayTest,
)
}

Expand All @@ -130,6 +132,7 @@ private fun ProxyContent(
onProxySortChanged: (ProxySort) -> Unit,
onOverrideModeSelected: (TunnelState.Mode?) -> Unit,
onProxySelected: (Int, String) -> Unit,
onProxyDelayTest: (Int, String) -> Unit,
) {
var menuVisible by remember { mutableStateOf(false) }
var scrollSelectedToTopRequestVersion by remember { mutableIntStateOf(0) }
Expand Down Expand Up @@ -276,6 +279,7 @@ private fun ProxyContent(
pagerState = pagerState ?: return@Box,
gridStates = gridStates,
onProxySelected = onProxySelected,
onProxyDelayTest = onProxyDelayTest,
)
}
}
Expand All @@ -291,6 +295,7 @@ private fun ProxyPagerContent(
pagerState: PagerState,
gridStates: List<LazyGridState>,
onProxySelected: (Int, String) -> Unit,
onProxyDelayTest: (Int, String) -> Unit,
) {
val groupNames = uiState.groupNames
if (groupNames.isEmpty()) return
Expand Down Expand Up @@ -322,6 +327,7 @@ private fun ProxyPagerContent(
gridState = gridStates[page],
selectedProxies = selectedProxies,
onProxySelected = onProxySelected,
onProxyDelayTest = onProxyDelayTest,
)
}
}
Expand All @@ -339,6 +345,7 @@ private fun ProxyGroupPage(
gridState: LazyGridState,
selectedProxies: List<SelectedProxy>,
onProxySelected: (Int, String) -> Unit,
onProxyDelayTest: (Int, String) -> Unit,
) {
val sources = group.sources
val refreshVersion = group.refreshVersion
Expand Down Expand Up @@ -384,6 +391,7 @@ private fun ProxyGroupPage(
selectedBackground = selectedBackground,
unselectedControl = unselectedControl,
unselectedBackground = unselectedBackground,
delayTesting = source.proxy.name in group.delayTestingKeys,
)
}

Expand All @@ -392,6 +400,7 @@ private fun ProxyGroupPage(
proxyLine = proxyLine,
selectable = group.selectable,
onClick = { onProxySelected(index, item.key) },
onDelayClick = { onProxyDelayTest(index, item.key) },
)
}
}
Expand All @@ -403,6 +412,7 @@ private fun ProxyItemCard(
proxyLine: Int,
selectable: Boolean,
onClick: () -> Unit,
onDelayClick: () -> Unit,
) {
val shape = RoundedCornerShape(if (proxyLine == 1) 0.dp else 5.dp)
val modifier =
Expand Down Expand Up @@ -438,14 +448,18 @@ private fun ProxyItemCard(
)
}

if (item.delayText.isNotEmpty()) {
Text(
text = item.delayText,
color = item.controls,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
)
}
val badgeText = if (item.delayTesting) delayTestingPlaceholder else item.delayText
Text(
modifier =
Modifier.clip(CircleShape)
.clickable(onClick = onDelayClick)
.background(item.controls.copy(alpha = if (item.delayTesting) 0.33f else 0.14f))
.padding(horizontal = 8.dp, vertical = 2.dp),
text = badgeText,
color = item.controls,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
)
}
}

Expand Down Expand Up @@ -575,6 +589,7 @@ private fun ProxyMenuRadioRow(title: String, selected: Boolean, onClick: () -> U
}

private val gridContentPadding = 12.dp
private const val delayTestingPlaceholder = "···"

private fun columnsForProxyLine(proxyLine: Int): Int =
when (proxyLine) {
Expand Down Expand Up @@ -659,5 +674,6 @@ private fun ProxyContentPreview() {
onProxySortChanged = {},
onOverrideModeSelected = {},
onProxySelected = { _, _ -> },
onProxyDelayTest = { _, _ -> },
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,29 @@ internal class ProxyViewModel(app: Application) : AndroidViewModel(app), Default
}
}

fun onProxyDelayTest(index: Int, name: String) {
val names = uiState.value.groupNames
if (index !in names.indices) return

updateGroupState(index) {
it.copy(delayTestingKeys = it.delayTestingKeys + name, refreshVersion = it.refreshVersion + 1)
}

viewModelScope.launch {
try {
withClash { healthCheckProxy(names[index], name) }
reload(index)
} finally {
updateGroupState(index) {
it.copy(
delayTestingKeys = it.delayTestingKeys - name,
refreshVersion = it.refreshVersion + 1,
)
}
}
}
}

fun reloadAll() {
val names = uiState.value.groupNames
names.indices.forEach { idx -> reload(idx) }
Expand Down Expand Up @@ -207,6 +230,8 @@ internal class ProxyViewModel(app: Application) : AndroidViewModel(app), Default
selectable = group.type == Proxy.Type.Selector,
urlTesting = false,
sources = sources,
delayTestingKeys =
it.delayTestingKeys.intersect(sources.mapTo(mutableSetOf()) { s -> s.proxy.name }),
refreshVersion = it.refreshVersion + 1,
)
}
Expand Down Expand Up @@ -239,6 +264,7 @@ internal class ProxyViewModel(app: Application) : AndroidViewModel(app), Default
val selectable: Boolean = false,
val urlTesting: Boolean = false,
val sources: List<ProxyItemSource> = emptyList(),
val delayTestingKeys: Set<String> = emptySet(),
val refreshVersion: Int = 0,
)

Expand All @@ -251,6 +277,7 @@ internal class ProxyViewModel(app: Application) : AndroidViewModel(app), Default
selectedBackground: Color,
unselectedControl: Color,
unselectedBackground: Color,
delayTesting: Boolean,
): ProxyItemUiState {
val selected = proxy.name == parentNow?.name
val background =
Expand Down Expand Up @@ -278,7 +305,8 @@ internal class ProxyViewModel(app: Application) : AndroidViewModel(app), Default
key = proxy.name,
title = title,
subtitle = subtitle,
delayText = if (proxy.delay in 0..Short.MAX_VALUE) proxy.delay.toString() else "",
delayText = if (proxy.delay in 0..Short.MAX_VALUE) proxy.delay.toString() else "--",
delayTesting = delayTesting,
selected = selected,
background = background,
controls = controls,
Expand All @@ -291,6 +319,7 @@ internal class ProxyViewModel(app: Application) : AndroidViewModel(app), Default
val title: String,
val subtitle: String,
val delayText: String,
val delayTesting: Boolean,
val selected: Boolean,
val background: Color,
val controls: Color,
Expand Down
Loading