diff --git a/core/src/main/cpp/CMakeLists.txt b/core/src/main/cpp/CMakeLists.txt index dd75a94..c63d7c8 100644 --- a/core/src/main/cpp/CMakeLists.txt +++ b/core/src/main/cpp/CMakeLists.txt @@ -8,4 +8,4 @@ include_directories(${CLASH_OUTPUT}/${ANDROID_ABI} ${CLASH_SOURCE}) link_directories(${CLASH_OUTPUT}/${CMAKE_ANDROID_ARCH_ABI}) link_libraries(log clash) -add_library(bridge SHARED main.cpp main.h init.cpp query.cpp patch.cpp tun.cpp defer.cpp event_queue.cpp) \ No newline at end of file +add_library(bridge SHARED main.cpp main.h init.cpp query.cpp patch.cpp tun.cpp defer.cpp log.cpp event_queue.cpp) \ No newline at end of file diff --git a/core/src/main/cpp/log.cpp b/core/src/main/cpp/log.cpp new file mode 100644 index 0000000..72a41e9 --- /dev/null +++ b/core/src/main/cpp/log.cpp @@ -0,0 +1,51 @@ +#include "libclash.h" +#include "main.h" + +static jobject logCallback; + +extern "C" +JNIEXPORT void JNICALL +Java_com_github_kr328_clash_core_bridge_Bridge_setLogCallback(JNIEnv *env, jclass clazz, + jobject callback) { + UNUSED(clazz); + + Master::runWithContext(env, [&](Master::Context *context) { + if ( logCallback != nullptr ) + context->removeGlobalReference(logCallback); + + if ( callback == nullptr ) { + logCallback = nullptr; + return; + } + + logCallback = context->newGlobalReference(callback); + + EventQueue::getInstance()->registerHandler(LOG_RECEIVED, 0, [](const event_t *event) { + Master::runWithAttached([&](JNIEnv *env) { + Master::runWithContext(env, [&](Master::Context *context) { + context->logCallbackMessage(logCallback, event->payload); + }); + + return 0; + }); + }); + }); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_github_kr328_clash_core_bridge_Bridge_enableLogReport(JNIEnv *env, jclass clazz) { + UNUSED(env); + UNUSED(clazz); + + enableLogReport(); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_github_kr328_clash_core_bridge_Bridge_disableLogReport(JNIEnv *env, jclass clazz) { + UNUSED(env); + UNUSED(clazz); + + disableLogReport(); +} \ No newline at end of file diff --git a/core/src/main/cpp/main.cpp b/core/src/main/cpp/main.cpp index af164ed..b658ae4 100644 --- a/core/src/main/cpp/main.cpp +++ b/core/src/main/cpp/main.cpp @@ -16,7 +16,9 @@ Master::Master(JavaVM *vm, JNIEnv *env): vm(vm) { cCompletableFuture = g(env, env->FindClass("java/util/concurrent/CompletableFuture")); cProxyGroup = g(env, env->FindClass("com/github/kr328/clash/core/model/ProxyGroup")); cProxy = g(env, env->FindClass("com/github/kr328/clash/core/model/Proxy")); + cLogEvent = g(env, env->FindClass("com/github/kr328/clash/core/event/LogEvent")); iTunCallback = g(env, env->FindClass("com/github/kr328/clash/core/bridge/TunCallback")); + iLogCallback = g(env, env->FindClass("com/github/kr328/clash/core/bridge/LogCallback")); cClashExceptionConstructor = env->GetMethodID(cClashException, "", "(Ljava/lang/String;)V"); cTrafficConstructor = env->GetMethodID(cTraffic, "", "(JJ)V"); @@ -24,6 +26,7 @@ Master::Master(JavaVM *vm, JNIEnv *env): vm(vm) { cCompletableFutureConstructor = env->GetMethodID(cCompletableFuture, "", "()V"); cProxyGroupConstructor = env->GetMethodID(cProxyGroup, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Lcom/github/kr328/clash/core/model/Proxy;)V"); cProxyConstructor = env->GetMethodID(cProxy, "", "(Ljava/lang/String;Ljava/lang/String;J)V"); + cLogEventConstructor = env->GetMethodID(cLogEvent, "", "(Ljava/lang/String;)V"); mCompletableFutureComplete = env->GetMethodID(cCompletableFuture, "complete", "(Ljava/lang/Object;)Z"); mCompletableFutureCompleteExceptionally = env->GetMethodID(cCompletableFuture, @@ -31,6 +34,7 @@ Master::Master(JavaVM *vm, JNIEnv *env): vm(vm) { "(Ljava/lang/Throwable;)Z"); mTunCallbackOnNewSocket = env->GetMethodID(iTunCallback, "onNewSocket", "(I)V"); mTunCallbackOnStop = env->GetMethodID(iTunCallback, "onStop", "()V"); + mLogCallbackOnMessage = env->GetMethodID(iLogCallback, "onMessage", "(Lcom/github/kr328/clash/core/event/LogEvent;)V"); sDirect = g(env, env->NewStringUTF("Direct")); sReject = g(env, env->NewStringUTF("Reject")); @@ -224,6 +228,12 @@ jobject Master::Context::createProxyGroup(char const *name, proxy_type_t type, return env->NewObject(master->cProxyGroup, master->cProxyGroupConstructor, env->NewStringUTF(name), ts, env->NewStringUTF(current), proxies); } +void Master::Context::logCallbackMessage(jobject callback, const char *data) { + jobject event = env->NewObject(master->cLogEvent, master->cLogEventConstructor, env->NewStringUTF(data)); + + env->CallVoidMethod(callback, master->mLogCallbackOnMessage, event); +} + static void enqueue_event(const event_t *e) { EventQueue::getInstance()->enqueueEvent(e); } diff --git a/core/src/main/cpp/main.h b/core/src/main/cpp/main.h index c58e3ca..5bd5132 100644 --- a/core/src/main/cpp/main.h +++ b/core/src/main/cpp/main.h @@ -25,17 +25,21 @@ private: jclass cCompletableFuture; jclass cProxyGroup; jclass cProxy; + jclass cLogEvent; jclass iTunCallback; + jclass iLogCallback; jmethodID cClashExceptionConstructor; jmethodID cTrafficConstructor; jmethodID cGeneralConstructor; jmethodID cCompletableFutureConstructor; jmethodID cProxyGroupConstructor; jmethodID cProxyConstructor; + jmethodID cLogEventConstructor; jmethodID mCompletableFutureComplete; jmethodID mCompletableFutureCompleteExceptionally; jmethodID mTunCallbackOnNewSocket; jmethodID mTunCallbackOnStop; + jmethodID mLogCallbackOnMessage; private: jstring sDirect; @@ -86,6 +90,7 @@ public: bool completeExceptionallyCompletableFuture(jobject completable, jthrowable throwable); void tunCallbackNewSocket(jobject callback, int fd); void tunCallbackStop(jobject callback); + void logCallbackMessage(jobject callback, const char *data); public: jobject createProxy(char const *name, proxy_type_t type, long delay); diff --git a/core/src/main/cpp/query.cpp b/core/src/main/cpp/query.cpp index dc8c70a..68c36a4 100644 --- a/core/src/main/cpp/query.cpp +++ b/core/src/main/cpp/query.cpp @@ -1,7 +1,5 @@ #include "main.h" -#include - extern "C" JNIEXPORT jobject JNICALL Java_com_github_kr328_clash_core_bridge_Bridge_queryGeneral(JNIEnv *env, jclass clazz) { @@ -92,7 +90,7 @@ Java_com_github_kr328_clash_core_bridge_Bridge_queryProxyGroups(JNIEnv *env, jcl group_name, group->base.proxy_type, now, - context->createProxyArray(group->proxies_size, jproxies); + context->createProxyArray(group->proxies_size, jproxies)); delete[] jproxies; diff --git a/core/src/main/golang/event.h b/core/src/main/golang/event.h index cf526ea..c8fd7a2 100644 --- a/core/src/main/golang/event.h +++ b/core/src/main/golang/event.h @@ -8,7 +8,7 @@ extern "C" { #endif typedef enum { - NEW_SOCKET, TUN_STOP, COMPLETE + NEW_SOCKET, TUN_STOP, COMPLETE, LOG_RECEIVED } event_type_t; typedef struct event_t { diff --git a/core/src/main/golang/log.go b/core/src/main/golang/log.go new file mode 100644 index 0000000..5ef314d --- /dev/null +++ b/core/src/main/golang/log.go @@ -0,0 +1,58 @@ +package main + +//#include "event.h" +import "C" +import ( + "fmt" + "github.com/Dreamacro/clash/log" + "sync" +) + +var logLocker sync.Mutex +var closeChan chan struct{} + +//export enableLogReport +func enableLogReport() { + logLocker.Lock() + defer logLocker.Unlock() + + + if closeChan != nil { + close(closeChan) + } + + closeChan = make(chan struct{}) + + go func(closed chan struct{}) { + subscriber := log.Subscribe() + defer log.UnSubscribe(subscriber) + defer log.Infoln("Log broadcast disabled") + + for { + select { + case item := <-subscriber: + msg := item.(*log.Event) + + if msg.LogLevel < log.Level() { + continue + } + + <- sendEvent(C.LOG_RECEIVED, 0, fmt.Sprintf("%s:%s", msg.LogLevel.String(), msg.Payload)).result + case <-closeChan: + return + } + } + }(closeChan) +} + +//export disableLogReport +func disableLogReport() { + logLocker.Lock() + defer logLocker.Unlock() + + if closeChan != nil { + close(closeChan) + + closeChan = nil + } +} diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index 677fa2d..4e3a0ac 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -14,6 +14,8 @@ import java.io.InputStream import java.util.concurrent.CompletableFuture object Clash { + private val logReceivers = mutableMapOf Unit>() + init { val context = Global.application @@ -23,6 +25,12 @@ object Clash { Bridge.initialize(bytes, context.cacheDir.absolutePath, BuildConfig.VERSION_NAME) Bridge.reset() + Bridge.setLogCallback { + synchronized(logReceivers) { + logReceivers.forEach { (_, e) -> e(it) } + } + } + Log.i("Clash core initialized") } @@ -102,10 +110,18 @@ object Clash { } fun registerLogReceiver(key: String, receiver: (LogEvent) -> Unit) { - + synchronized(logReceivers) { + if ( logReceivers.isEmpty() ) + Bridge.enableLogReport() + logReceivers[key] = receiver + } } fun unregisterLogReceiver(key: String) { - + synchronized(logReceivers) { + logReceivers.remove(key) + if ( logReceivers.isEmpty() ) + Bridge.disableLogReport(); + } } } \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.java b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.java index 8e67d6d..3d37d8d 100644 --- a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.java +++ b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.java @@ -38,4 +38,8 @@ public final class Bridge { public static native CompletableFuture loadProfile(String path, String base); public static native CompletableFuture performHealthCheck(String group); + + public static native void setLogCallback(LogCallback callback); + public static native void enableLogReport(); + public static native void disableLogReport(); } diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/LogCallback.java b/core/src/main/java/com/github/kr328/clash/core/bridge/LogCallback.java index cdfa0a2..d612835 100644 --- a/core/src/main/java/com/github/kr328/clash/core/bridge/LogCallback.java +++ b/core/src/main/java/com/github/kr328/clash/core/bridge/LogCallback.java @@ -1,7 +1,11 @@ package com.github.kr328.clash.core.bridge; +import androidx.annotation.Keep; + import com.github.kr328.clash.core.event.LogEvent; -interface LogCallback { +@Keep +@SuppressWarnings("unused") +public interface LogCallback { void onMessage(LogEvent event); } diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/TunCallback.java b/core/src/main/java/com/github/kr328/clash/core/bridge/TunCallback.java index a43642b..09ee5ad 100644 --- a/core/src/main/java/com/github/kr328/clash/core/bridge/TunCallback.java +++ b/core/src/main/java/com/github/kr328/clash/core/bridge/TunCallback.java @@ -3,6 +3,7 @@ package com.github.kr328.clash.core.bridge; import androidx.annotation.Keep; @Keep +@SuppressWarnings("unused") public interface TunCallback { void onNewSocket(int socket); void onStop(); diff --git a/core/src/main/java/com/github/kr328/clash/core/event/LogEvent.kt b/core/src/main/java/com/github/kr328/clash/core/event/LogEvent.kt index 18cd607..5e78c76 100644 --- a/core/src/main/java/com/github/kr328/clash/core/event/LogEvent.kt +++ b/core/src/main/java/com/github/kr328/clash/core/event/LogEvent.kt @@ -13,8 +13,9 @@ data class LogEvent( val message: String, val time: Long = System.currentTimeMillis() ) : Parcelable { + private constructor(data: List): this(Level.fromString(data[0]), data[1]) @Keep - constructor(level: String, message: String): this(Level.fromString(level), message) + constructor(data: String) : this(data.split(":", limit = 2)) companion object { const val DEBUG_VALUE = "debug"