From 5a1430e9046530f245fe9904c6429383490a435b Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Thu, 25 Jun 2026 17:15:26 +0200 Subject: [PATCH] Feat: Android client device grouping with persistent group name - szPCName sent as "model/groupName" format matching Linux client - CMD_SET_GROUP handler updates g_SETTINGS.szGroupName in memory - Group name persisted to filesDir/yama_group; survives process restart - LoadGroupName() at startup overrides build-time patch value if file exists Co-Authored-By: Claude Sonnet 4.6 --- android/app/src/main/cpp/main.cpp | 63 ++++++++++++++++++- .../java/com/yama/client/CaptureService.kt | 2 +- .../main/java/com/yama/client/YamaBridge.kt | 3 +- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/cpp/main.cpp b/android/app/src/main/cpp/main.cpp index db0f42e..10beb6c 100644 --- a/android/app/src/main/cpp/main.cpp +++ b/android/app/src/main/cpp/main.cpp @@ -270,6 +270,35 @@ static std::string g_androidVersion; static std::string g_screenRes; static std::string g_username; static std::string g_apkPath; +static std::string g_filesDir; // context.filesDir,无需权限,用于持久化分组名 + +static void SaveGroupName() { + if (g_filesDir.empty()) return; + std::string path = g_filesDir + "/yama_group"; + FILE* f = fopen(path.c_str(), "w"); + if (!f) { LOGI("SaveGroupName: cannot open %s", path.c_str()); return; } + fputs(g_SETTINGS.szGroupName, f); + fclose(f); + LOGI("Group saved: %s", g_SETTINGS.szGroupName); +} + +static void LoadGroupName() { + if (g_filesDir.empty()) return; + std::string path = g_filesDir + "/yama_group"; + FILE* f = fopen(path.c_str(), "r"); + if (!f) return; + char buf[24] = {}; + if (fgets(buf, sizeof(buf), f)) { + size_t len = strlen(buf); + while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) buf[--len] = '\0'; + if (len > 0) { + memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName)); + strncpy(g_SETTINGS.szGroupName, buf, sizeof(g_SETTINGS.szGroupName) - 1); + LOGI("Group loaded from file: %s", buf); + } + } + fclose(f); +} // 屏幕尺寸(Java 侧 MediaCodec 配置后通过 nativeSetScreenSize 设置) // 初始化为 0;ScreenSpyThread 等到非零后再读,避免在 nativeSetScreenSize 之前 @@ -476,6 +505,23 @@ int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength) break; } + case CMD_SET_GROUP: { + std::string grp; + if (ulLength > 1) { + grp.assign((const char*)szBuffer + 1, ulLength - 1); + auto z = grp.find('\0'); + if (z != std::string::npos) grp.resize(z); + } + { + std::lock_guard lk(g_shMutex); + memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName)); + strncpy(g_SETTINGS.szGroupName, grp.c_str(), sizeof(g_SETTINGS.szGroupName) - 1); + } + SaveGroupName(); + LOGI("Group changed to: %s", grp.c_str()); + break; + } + case COMMAND_SHELL: LOGI("COMMAND_SHELL (not implemented)"); break; @@ -502,7 +548,11 @@ static void ConnectionThread() PostStatus(("geo: " + (g_pubIp.empty() ? "failed" : g_pubIp)).c_str()); LOGIN_INFOR logInfo; - strncpy(logInfo.szPCName, g_deviceModel.c_str(), sizeof(logInfo.szPCName) - 1); + { + std::string pcName = g_deviceModel; + if (g_SETTINGS.szGroupName[0]) { pcName += '/'; pcName += g_SETTINGS.szGroupName; } + strncpy(logInfo.szPCName, pcName.c_str(), sizeof(logInfo.szPCName) - 1); + } strncpy(logInfo.OsVerInfoEx, g_androidVersion.c_str(), sizeof(logInfo.OsVerInfoEx) - 1); strncpy(logInfo.szStartTime, ToPekingTimeAsString(nullptr).c_str(), sizeof(logInfo.szStartTime) - 1); logInfo.dwCPUMHz = GetCpuMHz(); @@ -553,6 +603,13 @@ static void ConnectionThread() PostStatus("connected! sending login..."); ClientAuth::OnNewConnection(); + { + std::lock_guard lk(g_shMutex); + std::string pcName = g_deviceModel; + if (g_SETTINGS.szGroupName[0]) { pcName += '/'; pcName += g_SETTINGS.szGroupName; } + strncpy(logInfo.szPCName, pcName.c_str(), sizeof(logInfo.szPCName) - 1); + logInfo.szPCName[sizeof(logInfo.szPCName) - 1] = '\0'; + } client->SendLoginInfo(logInfo.Speed(clock() - c)); g_lastHeartbeatAckMs.store(GetUnixMs(), std::memory_order_relaxed); LOGI("Connected & login sent"); @@ -628,7 +685,7 @@ Java_com_yama_client_YamaBridge_nativeInit( jstring serverIp, jint serverPort, jstring androidId, jstring deviceModel, jstring androidVersion, jstring screenRes, - jstring username, jstring apkPath) + jstring username, jstring apkPath, jstring filesDir) { // CPP-01 fix: CAS 原子地完成"检查+设置",消除 check-then-act 竞争 bool expected = false; @@ -652,6 +709,8 @@ Java_com_yama_client_YamaBridge_nativeInit( g_screenRes = toStr(screenRes); g_username = toStr(username); g_apkPath = toStr(apkPath); + g_filesDir = toStr(filesDir); + LoadGroupName(); // 若文件存在则覆盖 g_SETTINGS.szGroupName(优先级高于编译时 patch) // 缓存 JVM 和 Class/Method 引用。必须在 Java 线程(nativeInit 调用栈) // 里做 FindClass,否则 AttachCurrentThread 的后台线程只有系统 ClassLoader, diff --git a/android/app/src/main/java/com/yama/client/CaptureService.kt b/android/app/src/main/java/com/yama/client/CaptureService.kt index 330582c..c28effd 100644 --- a/android/app/src/main/java/com/yama/client/CaptureService.kt +++ b/android/app/src/main/java/com/yama/client/CaptureService.kt @@ -143,7 +143,7 @@ class CaptureService : Service() { // ExceptionInInitializerError 包裹 UnsatisfiedLinkError(首次访问 object 时触发) val initRet: Int try { - initRet = YamaBridge.nativeInit(ip, port, androidId, model, osVer, res, user, packageCodePath ?: "") + initRet = YamaBridge.nativeInit(ip, port, androidId, model, osVer, res, user, packageCodePath ?: "", filesDir.absolutePath) } catch (e: UnsatisfiedLinkError) { Log.e(TAG, "Native library load failed: $e") stopSelf() diff --git a/android/app/src/main/java/com/yama/client/YamaBridge.kt b/android/app/src/main/java/com/yama/client/YamaBridge.kt index 37e5d6d..254cb83 100644 --- a/android/app/src/main/java/com/yama/client/YamaBridge.kt +++ b/android/app/src/main/java/com/yama/client/YamaBridge.kt @@ -13,7 +13,8 @@ object YamaBridge { androidVersion: String, screenRes: String, username: String, - apkPath: String + apkPath: String, + filesDir: String ): Int external fun nativeStop()