feat: clash api selector callback

This commit is contained in:
arm64v8a 2023-04-21 09:59:21 +09:00
parent 46526560d8
commit dd648447eb
20 changed files with 177 additions and 46 deletions

View File

@ -9,4 +9,5 @@ oneway interface ISagerNetServiceCallback {
void routeAlert(int type, String routeName);
void cbSpeedUpdate(in SpeedDisplayData stats);
void cbTrafficUpdate(in TrafficData stats);
void cbSelectorUpdate(long id);
}

View File

@ -20,7 +20,9 @@ import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import go.Seq
import io.nekohasekai.sagernet.bg.SagerConnection
import io.nekohasekai.sagernet.bg.ServiceNotification
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.database.SagerDatabase
import io.nekohasekai.sagernet.ktx.Logs
import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
import io.nekohasekai.sagernet.ui.MainActivity
@ -266,4 +268,22 @@ class SagerNet : Application(),
return DataStore.rulesProvider == 0
}
override fun selector_OnProxySelected(tag: String) {
DataStore.baseService?.apply {
runOnDefaultDispatcher {
val id = data.proxy!!.config.profileTagMap
.filterValues { it == tag }.keys.firstOrNull() ?: -1
val ent = SagerDatabase.proxyDao.getById(id) ?: return@runOnDefaultDispatcher
// traffic & title
data.proxy!!.looper?.selectMain(id)
val title = ServiceNotification.genTitle(ent)
data.notification?.postNotificationTitle(title)
// post MainActivity animation
data.binder.broadcast { b ->
b.cbSelectorUpdate(id)
}
}
}
}
}

View File

@ -58,6 +58,7 @@ class BaseService {
.show()
}
}
else -> service.stopRunner()
}
}
@ -165,12 +166,10 @@ class BaseService {
val ent = SagerDatabase.proxyDao.getById(DataStore.selectedProxy)
val tag = data.proxy!!.config.profileTagMap[ent?.id] ?: ""
if (tag.isNotBlank() && ent != null) {
val success = data.proxy!!.box.selectOutbound(tag)
if (success) runOnDefaultDispatcher {
data.proxy!!.looper?.selectMain(ent.id)
val title = ServiceNotification.genTitle(ent)
data.notification?.postNotificationTitle(title)
}
// select from GUI
data.proxy!!.box.selectOutbound(tag)
// or select from webui
// => selector_OnProxySelected
}
return
}

View File

@ -39,6 +39,7 @@ class SagerConnection(
fun cbSpeedUpdate(stats: SpeedDisplayData) {}
fun cbTrafficUpdate(data: TrafficData) {}
fun cbSelectorUpdate(id: Long) {}
fun stateChanged(state: BaseService.State, profileName: String?, msg: String?)
@ -83,6 +84,13 @@ class SagerConnection(
}
}
override fun cbSelectorUpdate(id: Long) {
val callback = callback ?: return
runOnMainDispatcher {
callback.cbSelectorUpdate(id)
}
}
override fun missingPlugin(profileName: String, pluginName: String) {
val callback = callback ?: return
runOnMainDispatcher {

View File

@ -9,6 +9,7 @@ import io.nekohasekai.sagernet.database.ProfileManager
import io.nekohasekai.sagernet.fmt.TAG_BYPASS
import io.nekohasekai.sagernet.fmt.TAG_PROXY
import io.nekohasekai.sagernet.ktx.Logs
import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
import kotlinx.coroutines.*
class TrafficLooper
@ -55,14 +56,22 @@ class TrafficLooper
fun selectMain(id: Long) {
Logs.d("select traffic count $TAG_PROXY to $id, old id is $selectorNowId")
val oldData = items[selectorNowId]
val data = items[id] ?: return
val newData = items[id] ?: return
oldData?.apply {
tag = selectorNowFakeTag
ignore = true
// post traffic when switch
data.proxy?.config?.trafficMap?.get(tag)?.firstOrNull()?.let {
it.rx = rx
it.tx = tx
runOnDefaultDispatcher {
ProfileManager.updateProfile(it) // update DB
}
}
}
selectorNowFakeTag = data.tag
selectorNowFakeTag = newData.tag
selectorNowId = id
data.apply {
newData.apply {
tag = TAG_PROXY
ignore = false
}

View File

@ -17,7 +17,7 @@ object ProfileManager {
interface Listener {
suspend fun onAdd(profile: ProxyEntity)
suspend fun onUpdated(data: TrafficData)
suspend fun onUpdated(profile: ProxyEntity)
suspend fun onUpdated(profile: ProxyEntity, noTraffic: Boolean)
suspend fun onRemoved(groupId: Long, profileId: Long)
}
@ -87,13 +87,13 @@ object ProfileManager {
suspend fun updateProfile(profile: ProxyEntity) {
SagerDatabase.proxyDao.updateProxy(profile)
iterator { onUpdated(profile) }
iterator { onUpdated(profile, false) }
}
suspend fun updateProfile(profiles: List<ProxyEntity>) {
SagerDatabase.proxyDao.updateProxy(profiles)
profiles.forEach {
iterator { onUpdated(it) }
iterator { onUpdated(it, false) }
}
}
@ -141,12 +141,12 @@ object ProfileManager {
// postUpdate: post to listeners, don't change the DB
suspend fun postUpdate(profileId: Long) {
postUpdate(getProfile(profileId) ?: return)
suspend fun postUpdate(profileId: Long, noTraffic: Boolean = false) {
postUpdate(getProfile(profileId) ?: return, noTraffic)
}
suspend fun postUpdate(profile: ProxyEntity) {
iterator { onUpdated(profile) }
suspend fun postUpdate(profile: ProxyEntity, noTraffic: Boolean = false) {
iterator { onUpdated(profile, noTraffic) }
}
suspend fun postUpdate(data: TrafficData) {

View File

@ -293,6 +293,7 @@ class ConfigurationFragment @JvmOverloads constructor(
R.id.action_scan_qr_code -> {
startActivity(Intent(context, ScannerActivity::class.java))
}
R.id.action_import_clipboard -> {
val text = SagerNet.getClipboardText()
if (text.isBlank()) {
@ -314,56 +315,73 @@ class ConfigurationFragment @JvmOverloads constructor(
}
}
}
R.id.action_import_file -> {
startFilesForResult(importFile, "*/*")
}
R.id.action_new_socks -> {
startActivity(Intent(requireActivity(), SocksSettingsActivity::class.java))
}
R.id.action_new_http -> {
startActivity(Intent(requireActivity(), HttpSettingsActivity::class.java))
}
R.id.action_new_ss -> {
startActivity(Intent(requireActivity(), ShadowsocksSettingsActivity::class.java))
}
R.id.action_new_vmess -> {
startActivity(Intent(requireActivity(), VMessSettingsActivity::class.java))
}
R.id.action_new_vless -> {
startActivity(Intent(requireActivity(), VMessSettingsActivity::class.java).apply {
putExtra("vless", true)
})
}
R.id.action_new_trojan -> {
startActivity(Intent(requireActivity(), TrojanSettingsActivity::class.java))
}
R.id.action_new_trojan_go -> {
startActivity(Intent(requireActivity(), TrojanGoSettingsActivity::class.java))
}
R.id.action_new_naive -> {
startActivity(Intent(requireActivity(), NaiveSettingsActivity::class.java))
}
R.id.action_new_hysteria -> {
startActivity(Intent(requireActivity(), HysteriaSettingsActivity::class.java))
}
R.id.action_new_tuic -> {
startActivity(Intent(requireActivity(), TuicSettingsActivity::class.java))
}
R.id.action_new_ssh -> {
startActivity(Intent(requireActivity(), SSHSettingsActivity::class.java))
}
R.id.action_new_wg -> {
startActivity(Intent(requireActivity(), WireGuardSettingsActivity::class.java))
}
R.id.action_new_shadowtls -> {
startActivity(Intent(requireActivity(), ShadowTLSSettingsActivity::class.java))
}
R.id.action_new_config -> {
startActivity(Intent(requireActivity(), ConfigSettingActivity::class.java))
}
R.id.action_new_chain -> {
startActivity(Intent(requireActivity(), ChainSettingsActivity::class.java))
}
R.id.action_new_neko -> {
val context = requireContext()
lateinit var dialog: AlertDialog
@ -395,6 +413,7 @@ class ConfigurationFragment @JvmOverloads constructor(
.setView(linearLayout)
.show()
}
R.id.action_clear_traffic_statistics -> {
runOnDefaultDispatcher {
val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId())
@ -411,6 +430,7 @@ class ConfigurationFragment @JvmOverloads constructor(
}
}
}
R.id.action_connection_test_clear_results -> {
runOnDefaultDispatcher {
val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId())
@ -428,6 +448,7 @@ class ConfigurationFragment @JvmOverloads constructor(
}
}
}
R.id.action_connection_test_delete_unavailable -> {
runOnDefaultDispatcher {
val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId())
@ -466,6 +487,7 @@ class ConfigurationFragment @JvmOverloads constructor(
}
}
}
R.id.action_remove_duplicate -> {
runOnDefaultDispatcher {
val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId())
@ -517,9 +539,11 @@ class ConfigurationFragment @JvmOverloads constructor(
}
}
}
R.id.action_connection_tcp_ping -> {
pingTest(false)
}
R.id.action_connection_url_test -> {
urlTest()
}
@ -561,18 +585,22 @@ class ConfigurationFragment @JvmOverloads constructor(
profileStatusText = profile.error
profileStatusColor = context.getColorAttr(android.R.attr.textColorSecondary)
}
0 -> {
profileStatusText = getString(R.string.connection_test_testing)
profileStatusColor = context.getColorAttr(android.R.attr.textColorSecondary)
}
1 -> {
profileStatusText = getString(R.string.available, profile.ping)
profileStatusColor = context.getColour(R.color.material_green_500)
}
2 -> {
profileStatusText = profile.error
profileStatusColor = context.getColour(R.color.material_red_500)
}
3 -> {
val err = profile.error ?: ""
val msg = Protocols.genFriendlyMsg(err)
@ -705,15 +733,18 @@ class ConfigurationFragment @JvmOverloads constructor(
when {
!message.contains("failed:") -> profile.error =
getString(R.string.connection_test_timeout)
else -> when {
message.contains("ECONNREFUSED") -> {
profile.error =
getString(R.string.connection_test_refused)
}
message.contains("ENETUNREACH") -> {
profile.error =
getString(R.string.connection_test_unreachable)
}
else -> {
profile.status = 3
profile.error = message
@ -939,7 +970,7 @@ class ConfigurationFragment @JvmOverloads constructor(
override suspend fun onUpdated(data: TrafficData) = Unit
override suspend fun onUpdated(profile: ProxyEntity) = Unit
override suspend fun onUpdated(profile: ProxyEntity, noTraffic: Boolean) = Unit
override suspend fun onRemoved(groupId: Long, profileId: Long) {
val group = groupList.find { it.id == groupId } ?: return
@ -1034,9 +1065,11 @@ class ConfigurationFragment @JvmOverloads constructor(
GroupOrder.ORIGIN -> {
origin.isChecked = true
}
GroupOrder.BY_NAME -> {
byName.isChecked = true
}
GroupOrder.BY_DELAY -> {
byDelay.isChecked = true
}
@ -1267,7 +1300,7 @@ class ConfigurationFragment @JvmOverloads constructor(
}
}
override suspend fun onUpdated(profile: ProxyEntity) {
override suspend fun onUpdated(profile: ProxyEntity, noTraffic: Boolean) {
if (profile.groupId != proxyGroup.id) return
val index = configurationIdList.indexOf(profile.id)
if (index < 0) return
@ -1275,9 +1308,21 @@ class ConfigurationFragment @JvmOverloads constructor(
if (::undoManager.isInitialized) {
undoManager.flush()
}
val oldProfile = configurationList[profile.id]
configurationList[profile.id] = profile
notifyItemChanged(index)
//
val oldProfile = configurationList[profile.id]
if (noTraffic && oldProfile != null) {
runOnDefaultDispatcher {
onUpdated(
TrafficData(
id = profile.id,
rx = oldProfile.rx,
tx = oldProfile.tx
)
)
}
}
}
}
@ -1333,6 +1378,7 @@ class ConfigurationFragment @JvmOverloads constructor(
newProfiles = newProfiles.sortedBy { it.displayName() }
}
GroupOrder.BY_DELAY -> {
newProfiles =
newProfiles.sortedBy { if (it.status == 1) it.ping else 114514 }
@ -1538,6 +1584,7 @@ class ConfigurationFragment @JvmOverloads constructor(
R.id.action_standard_clipboard
)
}
!proxyEntity.haveLink() -> {
popup.menu.removeItem(R.id.action_group_qr)
popup.menu.removeItem(R.id.action_group_clipboard)
@ -1589,6 +1636,7 @@ class ConfigurationFragment @JvmOverloads constructor(
R.id.action_universal_clipboard -> export(
entity.requireBean().toUniversalLink()
)
R.id.action_config_export_clipboard -> export(entity.exportConfig().first)
R.id.action_config_export_file -> {
val cfg = entity.exportConfig()

View File

@ -300,6 +300,7 @@ class MainActivity : ThemedActivity(),
R.id.nav_configuration -> {
displayFragment(ConfigurationFragment())
}
R.id.nav_group -> displayFragment(GroupFragment())
R.id.nav_route -> displayFragment(RouteFragment())
R.id.nav_settings -> displayFragment(SettingsFragment())
@ -310,11 +311,13 @@ class MainActivity : ThemedActivity(),
launchCustomTab("https://matsuridayo.github.io/")
return false
}
R.id.nav_about -> displayFragment(AboutFragment())
R.id.nav_tuiguang -> {
launchCustomTab("https://matsuricom.github.io/")
return false
}
else -> return false
}
navigation.menu.findItem(id).isChecked = true
@ -352,6 +355,7 @@ class MainActivity : ThemedActivity(),
ProfileManager.postUpdate(DataStore.currentProfile)
}
}
else -> {}
}
}
@ -412,6 +416,16 @@ class MainActivity : ThemedActivity(),
}
}
override fun cbSelectorUpdate(id: Long) {
val old = DataStore.selectedProxy
DataStore.selectedProxy = id
DataStore.currentProfile = id
runOnDefaultDispatcher {
ProfileManager.postUpdate(old, true)
ProfileManager.postUpdate(id, true)
}
}
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
when (key) {
Key.SERVICE_MODE -> onBinderDied()
@ -449,6 +463,7 @@ class MainActivity : ThemedActivity(),
binding.drawerLayout.open()
navigation.requestFocus()
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()

View File

@ -16,7 +16,10 @@ class SwitchActivity : ThemedActivity(R.layout.layout_empty),
super.onCreate(savedInstanceState)
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_holder, ConfigurationFragment(true, null, R.string.action_switch))
.replace(
R.id.fragment_holder,
ConfigurationFragment(true, null, R.string.action_switch)
)
.commitAllowingStateLoss()
}
@ -24,8 +27,8 @@ class SwitchActivity : ThemedActivity(R.layout.layout_empty),
val old = DataStore.selectedProxy
DataStore.selectedProxy = profileId
runOnMainDispatcher {
ProfileManager.postUpdate(old)
ProfileManager.postUpdate(profileId)
ProfileManager.postUpdate(old, true)
ProfileManager.postUpdate(profileId, true)
}
SagerNet.reloadService()
finish()

View File

@ -9,9 +9,9 @@ export PATH=$golang/go/bin:$GOPATH/bin:$PATH
source buildScript/init/env_ndk.sh
if [[ "$OSTYPE" =~ ^darwin ]]; then
export PROJECT=$PWD
export SRC_ROOT=$PWD
else
export PROJECT=$(realpath .)
export SRC_ROOT=$(realpath .)
fi
DEPS=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin

View File

@ -0,0 +1,26 @@
#!/bin/bash
set -e
source "buildScript/init/env.sh"
ENV_NB4A=1
source "buildScript/lib/core/get_source_env.sh"
pushd ..
######
## From nekoray/libs/get_source.sh
######
####
if [ ! -d "sing-box-extra" ]; then
git clone --no-checkout https://github.com/MatsuriDayo/sing-box-extra.git
fi
pushd sing-box-extra
git checkout "$COMMIT_SING_BOX_EXTRA"
ENV_SING_BOX_EXTRA=1
source $SRC_ROOT/buildScript/lib/core/get_source_env.sh
NO_ENV=1 ./libs/get_source.sh
popd
popd

View File

@ -0,0 +1,8 @@
if [ ! -z $ENV_NB4A ]; then
export COMMIT_SING_BOX_EXTRA="a4eacbd0e54b6ec0a42096c42b6137a5be91a0bc"
fi
if [ ! -z $ENV_SING_BOX_EXTRA ]; then
source libs/get_source_env.sh
export COMMIT_SING_BOX="91495e813068294aae506fdd769437c41dd8d3a3"
fi

View File

@ -2,6 +2,9 @@
source "buildScript/init/env.sh"
# fetch soucre
bash buildScript/lib/core/get_source.sh
[ -f libcore/go.mod ] || exit 1
cd libcore

View File

@ -4,12 +4,12 @@ go 1.18
require (
github.com/codeclysm/extract v2.2.0+incompatible
github.com/matsuridayo/libneko v0.0.0-20230315005352-9d7e3f3a79d1
github.com/matsuridayo/sing-box-extra v0.0.0-20230417014110-39b3adb5f93f
github.com/matsuridayo/libneko v1.0.0 // replaced
github.com/matsuridayo/sing-box-extra v1.0.0 // replaced
github.com/miekg/dns v1.1.53
github.com/sagernet/sing v0.2.3
github.com/sagernet/sing-box v1.2.4
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc
github.com/sagernet/sing-box v1.0.0 // replaced
github.com/sagernet/sing-dns v1.0.0 // replaced
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab
github.com/ulikunitz/xz v0.5.10
golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105
@ -93,10 +93,10 @@ require (
lukechampine.com/blake3 v1.1.7 // indirect
)
// replace github.com/matsuridayo/libneko => ../../libneko
replace github.com/matsuridayo/libneko v1.0.0 => ../../libneko
// replace github.com/matsuridayo/sing-box-extra => ../../sing-box-extra
replace github.com/matsuridayo/sing-box-extra v1.0.0 => ../../sing-box-extra
replace github.com/sagernet/sing-dns => github.com/matsuridayo/sing-dns v0.0.0-20230420050318-63790a1843f8
replace github.com/sagernet/sing-box v1.0.0 => ../../sing-box
replace github.com/sagernet/sing-box => github.com/matsuridayo/sing-box v0.0.0-20230419123417-eaa058f8a077
replace github.com/sagernet/sing-dns v1.0.0 => ../../sing-dns

View File

@ -76,14 +76,6 @@ github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/matsuridayo/libneko v0.0.0-20230315005352-9d7e3f3a79d1 h1:+FflyEuq2hn++MENFuT1/qFHz0KITKK/F6ZHxs23mrg=
github.com/matsuridayo/libneko v0.0.0-20230315005352-9d7e3f3a79d1/go.mod h1:IRO07Queptz/rGFvEW+3Hmwpx7MCup6WiDs4p5jMt4g=
github.com/matsuridayo/sing-box v0.0.0-20230419123417-eaa058f8a077 h1:k2glJShoEN6l1aBVkxETzlspS9Q+/MEJR9B8z5kirhA=
github.com/matsuridayo/sing-box v0.0.0-20230419123417-eaa058f8a077/go.mod h1:LDW5ZOuWURxSWz+auElryalxCBlGbA0zvsC8XSy8Sp0=
github.com/matsuridayo/sing-box-extra v0.0.0-20230417014110-39b3adb5f93f h1:x0UjjMoYh5WSDGhIuRkmZUhGoxXnK++wyvuPnURhz6Q=
github.com/matsuridayo/sing-box-extra v0.0.0-20230417014110-39b3adb5f93f/go.mod h1:NfCwDELPcVFo8rp0d/P7M2OPmegGCGcwF4GSpgZu8Rs=
github.com/matsuridayo/sing-dns v0.0.0-20230420050318-63790a1843f8 h1:eEXScrFlYLiXp2n6G6nnRQVPH81TFF1DGu0xqCFcPHY=
github.com/matsuridayo/sing-dns v0.0.0-20230420050318-63790a1843f8/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=

View File

@ -14,6 +14,7 @@ import (
"github.com/matsuridayo/libneko/neko_common"
"github.com/matsuridayo/libneko/neko_log"
"github.com/matsuridayo/sing-box-extra/boxmain"
"github.com/sagernet/sing-box/nekoutils"
)
//go:linkname resourcePaths github.com/sagernet/sing-box/constant.resourcePaths
@ -62,6 +63,9 @@ func InitCore(process, cachePath, internalAssets, externalAssets string,
neko_log.SetupLog(int(maxLogSizeKb)*1024, filepath.Join(cachePath, "neko.log"))
boxmain.DisableColor()
// nekoutils
nekoutils.Selector_OnProxySelected = iif.Selector_OnProxySelected
// Set up some component
go func() {
defer device.DeferPanicToError("InitCore-go", func(err error) { log.Println(err) })

View File

@ -7,6 +7,7 @@ var useProcfs bool
type NB4AInterface interface {
UseOfficialAssets() bool
Selector_OnProxySelected(tag string)
}
type LocalResolver interface {

View File

@ -1,2 +0,0 @@
go get github.com/sagernet/sing-box@"$1"
go mod tidy

View File

@ -1,2 +0,0 @@
go get github.com/matsuridayo/sing-box-extra@"$1"
go mod tidy

View File

@ -1,2 +0,0 @@
go get github.com/matsuridayo/libneko@"$1"
go mod tidy