Feat: Android client Phase 0-3 full implementation

This commit was merged in pull request #3.
This commit is contained in:
yuanyuanxiang
2026-06-17 23:19:12 +02:00
parent 837d89c8b5
commit 45553ec5b6
53 changed files with 3321 additions and 27 deletions

View File

@@ -1485,13 +1485,19 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
if (TryMigrateClientMetadata(id, strPCName, path)) {
modify = true;
}
CString loc = m_ClientMap->GetClientMapData(id, MAP_LOCATION);
if (loc.IsEmpty()) {
loc = v[RES_CLIENT_LOC].c_str();
if (loc.IsEmpty()) {
loc = m_IPConverter->GetGeoLocation(data[ONLINELIST_IP].GetString()).c_str();
needConvert = !m_HasLocDB;
}
// 优先级:① 客户端刚发来的有效地理位置(非空非"?"
// ② 服务端缓存(排除历史遗留的"?"无效值)
// ③ 服务端按连接 IP 自行查询
CString clientLoc = v[RES_CLIENT_LOC].c_str();
CString cachedLoc = m_ClientMap->GetClientMapData(id, MAP_LOCATION);
CString loc;
if (!clientLoc.IsEmpty() && clientLoc != "?") {
loc = clientLoc;
} else if (!cachedLoc.IsEmpty() && cachedLoc != "?") {
loc = cachedLoc;
} else {
loc = m_IPConverter->GetGeoLocation(data[ONLINELIST_IP].GetString()).c_str();
needConvert = !m_HasLocDB;
}
// TODO: Remove SafeUtf8ToAnsi after migrating to UTF-8
if (needConvert)
@@ -3478,7 +3484,7 @@ void CMy2015RemoteDlg::CheckHeartbeat()
{
CLock lock(m_cs);
auto now = time(0);
int HEARTBEAT_TIMEOUT = max(60, m_settings.ReportInterval * 3);
int HEARTBEAT_TIMEOUT = max(120, m_settings.ReportInterval * 3);
// 收集需要删除的 context避免遍历时修改 vector
std::vector<context*> toRemove;
@@ -10304,6 +10310,17 @@ context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip)
return NULL;
}
context* CMy2015RemoteDlg::FindHostByClientID(uint64_t clientID)
{
EnterCriticalSection(&m_cs);
auto it = m_ClientIndex.find(clientID);
context* ctx = nullptr;
if (it != m_ClientIndex.end() && it->second < m_HostList.size())
ctx = m_HostList[it->second];
LeaveCriticalSection(&m_cs);
return ctx;
}
uint64_t CMy2015RemoteDlg::FindClientIDByIP(const std::string& ip)
{
CString clientIP(ip.c_str());

View File

@@ -368,6 +368,7 @@ public:
CGridDialog * m_gridDlg = NULL;
std::vector<DllInfo*> m_DllList;
context* FindHostByIP(const std::string& ip);
context* FindHostByClientID(uint64_t clientID); // 线程安全:通过 clientID 精确查主连接
uint64_t FindClientIDByIP(const std::string& ip); // 线程安全在锁内获取ID
void InjectTinyRunDll(const std::string& ip, int pid);
NOTIFYICONDATA m_Nid;

View File

@@ -18,6 +18,8 @@
#include <md5.h>
#include <cstdint> // for uint16_t
extern CMy2015RemoteDlg* g_2015RemoteDlg;
extern "C" uint32_t licenseGetBuildTag() { volatile uint32_t tag = 0xC0DE2026u; return tag; }
#include <vector>
#include <mutex> // for std::mutex, std::lock_guard
@@ -225,7 +227,15 @@ CScreenSpyDlg::CScreenSpyDlg(CMy2015RemoteDlg* Parent, Server* IOCPServer, CONTE
if (WebService().IsRunning() && !WebService().IsMfcTriggered(m_ClientID)) {
int width = m_BitmapInfor_Full->bmiHeader.biWidth;
int height = abs(m_BitmapInfor_Full->bmiHeader.biHeight);
WebService().NotifyResolutionChange(m_ClientID, width, height);
bool topDown = (m_BitmapInfor_Full->bmiHeader.biClrImportant == 1);
// m_ContextObject is the screen sub-connection; client_type lives on the
// main login connection. Look it up via the peer IP (same pattern as GetClientEncoding).
std::string clientType;
if (g_2015RemoteDlg) {
context* main = g_2015RemoteDlg->FindHostByClientID(m_ClientID);
if (main) clientType = main->GetAdditionalData(RES_CLIENT_TYPE).GetString();
}
WebService().NotifyResolutionChange(m_ClientID, width, height, topDown, clientType);
// 透传客户端初始的音频开/关状态给 web让前端按钮显示正确
WebService().NotifyAudioState(m_ClientID, m_Settings.AudioEnabled != 0);
}
@@ -953,7 +963,6 @@ VOID CScreenSpyDlg::OnClose()
CWnd* parent = GetParent();
if (parent)
parent->SendMessage(WM_CHILD_CLOSED, (WPARAM)this, 0);
extern CMy2015RemoteDlg *g_2015RemoteDlg;
if(g_2015RemoteDlg)
g_2015RemoteDlg->RemoveRemoteWindow(GetSafeHwnd());
@@ -1126,7 +1135,13 @@ VOID CScreenSpyDlg::OnReceiveComplete()
if (m_bIsWebSession && WebService().IsRunning()) {
int width = m_BitmapInfor_Full->bmiHeader.biWidth;
int height = abs(m_BitmapInfor_Full->bmiHeader.biHeight);
WebService().NotifyResolutionChange(m_ClientID, width, height);
bool topDown = (m_BitmapInfor_Full->bmiHeader.biClrImportant == 1);
std::string clientType;
if (g_2015RemoteDlg) {
context* main = g_2015RemoteDlg->FindHostByClientID(m_ClientID);
if (main) clientType = main->GetAdditionalData(RES_CLIENT_TYPE).GetString();
}
WebService().NotifyResolutionChange(m_ClientID, width, height, topDown, clientType);
}
break;
}

View File

@@ -694,13 +694,17 @@ void CWebService::HandleConnect(void* ws_ptr, const std::string& token, uint64_t
// Get screen dimensions + audio state from device info cache (may not be ready)
int width = 0, height = 0;
int audio_enabled = -1; // -1 = unknown yet (前端走 audio_state 事件兜底)
bool top_down = false;
std::string client_type;
{
std::lock_guard<std::mutex> lock(m_DeviceCacheMutex);
auto it = m_DeviceCache.find(device_id);
if (it != m_DeviceCache.end()) {
width = it->second->screen_width;
height = it->second->screen_height;
width = it->second->screen_width;
height = it->second->screen_height;
audio_enabled = it->second->audio_enabled;
top_down = it->second->top_down;
client_type = it->second->client_type;
}
}
@@ -713,6 +717,8 @@ void CWebService::HandleConnect(void* ws_ptr, const std::string& token, uint64_t
if (width > 0 && height > 0) {
res["width"] = width;
res["height"] = height;
res["top_down"] = top_down;
if (!client_type.empty()) res["client_type"] = client_type;
}
if (audio_enabled >= 0) {
res["audio_enabled"] = (audio_enabled != 0);
@@ -1712,7 +1718,7 @@ void CWebService::BroadcastH264Frame(uint64_t device_id, const uint8_t* data, si
}
}
void CWebService::NotifyResolutionChange(uint64_t device_id, int width, int height) {
void CWebService::NotifyResolutionChange(uint64_t device_id, int width, int height, bool top_down, const std::string& client_type) {
if (m_bStopping) return;
// Update cache
@@ -1723,8 +1729,10 @@ void CWebService::NotifyResolutionChange(uint64_t device_id, int width, int heig
m_DeviceCache[device_id] = std::make_shared<WebDeviceInfo>();
it = m_DeviceCache.find(device_id);
}
it->second->screen_width = width;
it->second->screen_width = width;
it->second->screen_height = height;
it->second->top_down = top_down;
if (!client_type.empty()) it->second->client_type = client_type;
}
// Notify watching clients
@@ -1733,6 +1741,8 @@ void CWebService::NotifyResolutionChange(uint64_t device_id, int width, int heig
res["id"] = device_id;
res["width"] = width;
res["height"] = height;
res["top_down"] = top_down;
if (!client_type.empty()) res["client_type"] = client_type;
Json::StreamWriterBuilder builder;
builder["indentation"] = "";

View File

@@ -55,6 +55,8 @@ struct WebDeviceInfo {
int screen_width;
int screen_height;
bool online;
bool top_down = false; // true = Android/top-down H.264, no flip needed in browser
std::string client_type; // RES_CLIENT_TYPE: "APK"=Android, "EXE"=Windows, "LNX"=Linux, "MAC"=macOS
// 当前会话的音频开关。-1=未知(客户端 BITMAPINFO 还没回来0=关1=开
int audio_enabled = -1;
@@ -98,7 +100,7 @@ public:
void CacheKeyframe(uint64_t device_id, const uint8_t* data, size_t len);
// Resolution change notification
void NotifyResolutionChange(uint64_t device_id, int width, int height);
void NotifyResolutionChange(uint64_t device_id, int width, int height, bool top_down = false, const std::string& client_type = "");
// Audio enable/disable notification — pushes current state to all web
// clients watching this device and caches it for newcomers.