12 Commits
v1.3.7 ... main

Author SHA1 Message Date
yuanyuanxiang
b97f086c0c Feature: Add log search bar with prev/next/clear for m_CList_Message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 00:10:12 +02:00
yuanyuanxiang
f24e7acc48 Fix: Switching between 32bits and 64bits causes master crash 2026-07-14 22:32:29 +02:00
yuanyuanxiang
0cd7516bde Improve: Save user customized remote desktop cursor settings to registry 2026-07-14 21:38:21 +02:00
yuanyuanxiang
a646e0417d Feature: Support to forbidden the user in violation of rules 2026-07-14 15:17:31 +02:00
yuanyuanxiang
23c9a4f242 Feature: add client log dialog with auto-refresh, icon and menu bitmap
- CClientLogManager: push thread sends incremental logs every 3s; join() instead of detach() prevents use-after-free on shutdown
- CClientLog::OnReceiveComplete: handles TOKEN_REPORT_LOG on IOCP thread via PostMessage, fixing incremental packets being silently dropped
- Dialog icon (ClientLog.ico) and menu bitmap (ClientLog.bmp) added; OnInitDialog loads icon; IDR_MENU_LIST_ONLINE "运行日志" item gets the bitmap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-13 19:02:25 +02:00
yuanyuanxiang
85a5774a0f Feature: Add a menu to input YAMA_DBG which allows debugging 2026-07-11 22:59:51 +02:00
yuanyuanxiang
f2a95848ce Feature: Add a "View Windows" menu for remote desktop control 2026-07-11 18:05:25 +02:00
yuanyuanxiang
f146af121a Fix: prevent mobile web remote desktop freeze on Safari app-switch
by forcing clean WS reconnect on foreground return;

add 30-second grace period on server to avoid cold-start on quick reconnects
2026-07-10 16:08:02 +02:00
yuanyuanxiang
72dcdc5a6f Fix: RunFileReceiver must not register ReconnectProcess
to avoid killing the main screen session on file transfer completion
2026-07-07 16:30:27 +02:00
yuanyuanxiang
8e170cf971 Fix: toggle privacy wallpaper menu item to clear instead of re-picking when already set 2026-07-03 20:57:54 +02:00
yuanyuanxiang
6d0b1bb07b Feat: Add privilege column (SYSTEM/Admin/User) to online host list 2026-07-01 09:32:28 +02:00
yuanyuanxiang
bcccbefb77 Feature: Client running as SYSTEM and support remote control 2026-06-29 22:49:30 +02:00
51 changed files with 1554 additions and 164 deletions

View File

@@ -75,6 +75,50 @@ ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run, BOOL shared)
return a; return a;
} }
#if _CONSOLE
#define DLL_API
#else
#define DLL_API __declspec(dllexport)
#endif
extern "C" DLL_API int RunCommand(LPBYTE szBuffer, int ulLength) {
if (!ENABLE_SCREEN || ulLength == 0) {
return 1;
}
if (szBuffer[0] != COMMAND_SCREEN_SPY && szBuffer[0] != TOKEN_PRIVATESCREEN) {
return 2;
}
switch (szBuffer[0]) {
case COMMAND_SCREEN_SPY: {
BYTE bToken[32] = { COMMAND_SCREEN_SPY, USING_DXGI, ALGORITHM_H264, TRUE };
szBuffer = bToken; ulLength = 4;
CONNECT_ADDRESS* m_conn = g_MyApp.g_Connection;
UserParam* user = new UserParam{ ulLength > 1 ? new BYTE[ulLength - 1] : nullptr, int(ulLength - 1) };
if (ulLength > 1) {
memcpy(user->buffer, szBuffer + 1, ulLength - 1);
}
ThreadInfo m_hThread;
auto* sub = new IOCPClient(S_CLIENT_NORMAL, true, MaskTypeNone, m_conn, m_conn->GetRandomServerIP());
// sub->EnableSubConnAuth();
m_hThread.conn = m_conn;
m_hThread.p = sub;
m_hThread.user = user;
m_hThread.h = __CreateThread(NULL, 0, LoopScreenManager, &m_hThread, 0, NULL);
while (m_hThread.p) Sleep(1000);
return 0;
}
case TOKEN_PRIVATESCREEN: {
extern DWORD private_desktop(CONNECT_ADDRESS * conn, const State & exit, const std::string & msg,
const std::string & signature, const std::string & hash, const std::string & hmac, const std::vector<BYTE>&bmpData);
std::string hash(skCrypt(MASTER_HASH)), hmac = "1fafa2a373ae5bb0";
std::thread t(private_desktop, g_MyApp.g_Connection, S_CLIENT_NORMAL, "", "", hash, hmac, std::vector<BYTE>{});
t.join();
return 0;
}
}
return -1;
}
DWORD WINAPI StartClientApp(LPVOID param) DWORD WINAPI StartClientApp(LPVOID param)
{ {
ClientApp::AddCount(1); ClientApp::AddCount(1);
@@ -219,16 +263,24 @@ int main(int argc, const char *argv[])
licenseInit(); licenseInit();
Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc>1 ? argv[1] : "", argc); Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc>1 ? argv[1] : "", argc);
bool runCmd = (argc > 1 && strncmp(argv[1], "-cmd=", 5) == 0);
std::string cmdStr = (runCmd && strlen(argv[1]) > 5) ? std::string(argv[1] + 5) : "";
int nCmd = cmdStr.empty() ? 0 : std::atoi(cmdStr.c_str());
if (nCmd) {
BYTE buf[] = { nCmd };
return RunCommand(buf, 1);
}
InitWindowsService(NewService( InitWindowsService(NewService(
g_SETTINGS.installName[0] ? g_SETTINGS.installName : "RemoteControlService", g_SETTINGS.installName[0] ? g_SETTINGS.installName : "RemoteControlService",
g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Remote Control Service", g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Remote Control Service",
g_SETTINGS.installDesc[0] ? g_SETTINGS.installDesc : "Provides remote desktop control functionality."), Log); g_SETTINGS.installDesc[0] ? g_SETTINGS.installDesc : "Provides remote desktop control functionality."), Log);
bool isService = g_SETTINGS.iStartup == Startup_GhostMsc || IsSystemInSession0(); bool isService = g_SETTINGS.iStartup == Startup_GhostMsc || (IsSystemInSession0() && g_SETTINGS.iStartup != Startup_GhostSystem);
bool lockFile = g_SETTINGS.iStartup != Startup_GhostMsc && g_SETTINGS.iStartup != Startup_GhostSystem && !IsSystemInSession0();
// 注册启动项 // 注册启动项
int r = RegisterStartup( int r = RegisterStartup(
g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Windows Ghost", g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Windows Ghost",
g_SETTINGS.installName[0] ? g_SETTINGS.installName : "WinGhost", g_SETTINGS.installName[0] ? g_SETTINGS.installName : "WinGhost",
!isService, g_SETTINGS.runasAdmin, Logf); lockFile, g_SETTINGS.iStartup == Startup_GhostSystem ? 2 : g_SETTINGS.runasAdmin, Logf);
if (r <= 0) { if (r <= 0) {
BOOL s = self_del(); BOOL s = self_del();
if (!IsDebug) { if (!IsDebug) {

View File

@@ -181,6 +181,7 @@
<ClCompile Include="CaptureVideo.cpp" /> <ClCompile Include="CaptureVideo.cpp" />
<ClCompile Include="clang_rt_compat.c" /> <ClCompile Include="clang_rt_compat.c" />
<ClCompile Include="ClientDll.cpp" /> <ClCompile Include="ClientDll.cpp" />
<ClCompile Include="ClientLogManager.cpp" />
<ClCompile Include="Common.cpp" /> <ClCompile Include="Common.cpp" />
<ClCompile Include="ConPTYManager.cpp" /> <ClCompile Include="ConPTYManager.cpp" />
<ClCompile Include="FileManager.cpp" /> <ClCompile Include="FileManager.cpp" />
@@ -197,11 +198,15 @@
<ClCompile Include="proxy\ProxyManager.cpp" /> <ClCompile Include="proxy\ProxyManager.cpp" />
<ClCompile Include="RegisterManager.cpp" /> <ClCompile Include="RegisterManager.cpp" />
<ClCompile Include="RegisterOperation.cpp" /> <ClCompile Include="RegisterOperation.cpp" />
<ClCompile Include="reg_startup.c" />
<ClCompile Include="SafeThread.cpp" /> <ClCompile Include="SafeThread.cpp" />
<ClCompile Include="ScreenManager.cpp" /> <ClCompile Include="ScreenManager.cpp" />
<ClCompile Include="ScreenPreview.cpp" /> <ClCompile Include="ScreenPreview.cpp" />
<ClCompile Include="ScreenSpy.cpp" /> <ClCompile Include="ScreenSpy.cpp" />
<ClCompile Include="ServicesManager.cpp" /> <ClCompile Include="ServicesManager.cpp" />
<ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="session.cpp" />
<ClCompile Include="SessionMonitor.c" />
<ClCompile Include="ShellManager.cpp" /> <ClCompile Include="ShellManager.cpp" />
<ClCompile Include="StdAfx.cpp" /> <ClCompile Include="StdAfx.cpp" />
<ClCompile Include="SystemManager.cpp" /> <ClCompile Include="SystemManager.cpp" />
@@ -224,6 +229,7 @@
<ClInclude Include="Buffer.h" /> <ClInclude Include="Buffer.h" />
<ClInclude Include="CaptureVideo.h" /> <ClInclude Include="CaptureVideo.h" />
<ClInclude Include="clip.h" /> <ClInclude Include="clip.h" />
<ClInclude Include="ClientLogManager.h" />
<ClInclude Include="Common.h" /> <ClInclude Include="Common.h" />
<ClInclude Include="ConPTYManager.h" /> <ClInclude Include="ConPTYManager.h" />
<ClInclude Include="CursorInfo.h" /> <ClInclude Include="CursorInfo.h" />
@@ -234,6 +240,10 @@
<ClInclude Include="CFFmpegAV1Encoder.h" /> <ClInclude Include="CFFmpegAV1Encoder.h" />
<ClInclude Include="CFFmpegH264Encoder.h" /> <ClInclude Include="CFFmpegH264Encoder.h" />
<ClInclude Include="EncoderFactory.h" /> <ClInclude Include="EncoderFactory.h" />
<ClInclude Include="reg_startup.h" />
<ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="session.h" />
<ClInclude Include="SessionMonitor.h" />
<ClInclude Include="VideoEncoderBase.h" /> <ClInclude Include="VideoEncoderBase.h" />
<ClInclude Include="KernelManager.h" /> <ClInclude Include="KernelManager.h" />
<ClInclude Include="KeyboardManager.h" /> <ClInclude Include="KeyboardManager.h" />

View File

@@ -41,6 +41,10 @@
<ClCompile Include="EncoderFactory.cpp" /> <ClCompile Include="EncoderFactory.cpp" />
<ClCompile Include="..\common\file_upload.cpp" /> <ClCompile Include="..\common\file_upload.cpp" />
<ClCompile Include="ConPTYManager.cpp" /> <ClCompile Include="ConPTYManager.cpp" />
<ClCompile Include="session.cpp" />
<ClCompile Include="reg_startup.c" />
<ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="SessionMonitor.c" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\common\file_upload.h" /> <ClInclude Include="..\common\file_upload.h" />
@@ -89,6 +93,10 @@
<ClInclude Include="CFFmpegAV1Encoder.h" /> <ClInclude Include="CFFmpegAV1Encoder.h" />
<ClInclude Include="EncoderFactory.h" /> <ClInclude Include="EncoderFactory.h" />
<ClInclude Include="ConPTYManager.h" /> <ClInclude Include="ConPTYManager.h" />
<ClInclude Include="session.h" />
<ClInclude Include="reg_startup.h" />
<ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="SessionMonitor.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="Script.rc" /> <ResourceCompile Include="Script.rc" />

View File

@@ -0,0 +1,44 @@
#include "stdafx.h"
#include "ClientLogManager.h"
#include "../common/commands.h"
#include "../common/logger.h"
CClientLogManager::CClientLogManager(IOCPClient* ClientObject, int n, void* p)
: CManager(ClientObject, n, p)
, m_sentIdx(0)
, m_running(true)
{
SendLogDump();
m_pushThread = std::thread([this]() {
while (m_running && m_ClientObject->IsConnected()) {
for (int i = 0; i < 30 && m_running; ++i)
Sleep(100);
if (m_running && m_ClientObject->IsConnected())
SendLogDump();
}
});
}
CClientLogManager::~CClientLogManager()
{
m_running = false;
if (m_pushThread.joinable())
m_pushThread.join();
}
VOID CClientLogManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
{
if (ulLength >= 1 && szBuffer[0] == COMMAND_QUERY_LOG)
SendLogDump();
}
void CClientLogManager::SendLogDump()
{
std::lock_guard<std::mutex> lock(m_sendMutex);
std::string logs = Logger::getInstance().DumpMemoryLogFrom(m_sentIdx);
std::vector<BYTE> pkt(1 + logs.size());
pkt[0] = TOKEN_REPORT_LOG;
if (!logs.empty())
memcpy(pkt.data() + 1, logs.data(), logs.size());
Send(pkt.data(), (UINT)pkt.size());
}

21
client/ClientLogManager.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include "Manager.h"
#include <atomic>
#include <mutex>
#include <thread>
class CClientLogManager : public CManager
{
public:
CClientLogManager(IOCPClient* ClientObject, int n = 0, void* p = nullptr);
~CClientLogManager();
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
private:
void SendLogDump();
size_t m_sentIdx;
std::mutex m_sendMutex;
std::atomic<bool> m_running;
std::thread m_pushThread;
};

View File

@@ -14,6 +14,7 @@
#include "VideoManager.h" #include "VideoManager.h"
#include "KeyboardManager.h" #include "KeyboardManager.h"
#include "ProxyManager.h" #include "ProxyManager.h"
#include "ClientLogManager.h"
#include "KernelManager.h" #include "KernelManager.h"
#include <iniFile.h> #include <iniFile.h>
@@ -69,7 +70,10 @@ DWORD private_desktop(CONNECT_ADDRESS* conn, const State &exit, const std::strin
IOCPClient* ClientObject = new IOCPClient(exit, true, MaskTypeNone, conn); IOCPClient* ClientObject = new IOCPClient(exit, true, MaskTypeNone, conn);
if (ClientObject->ConnectServer(conn->ServerIP(), conn->ServerPort())) { if (ClientObject->ConnectServer(conn->ServerIP(), conn->ServerPort())) {
ClientObject->SetVerifyInfo(msg, signature); ClientObject->SetVerifyInfo(msg, signature);
CScreenManager m(ClientObject, 32, (void*)1, TRUE); BYTE bToken[32] = { COMMAND_SCREEN_SPY, USING_DXGI, ALGORITHM_H264, TRUE };
UserParam* user = new UserParam{ new BYTE[3], 3 };
memcpy(user->buffer, bToken + 1, 3);
CScreenManager m(ClientObject, 32, (void*)user, TRUE);
if (IsWindows8orHigher()) { if (IsWindows8orHigher()) {
ShowBlackWindow(ClientObject, conn, hash, hmac, bmpData); ShowBlackWindow(ClientObject, conn, hash, hmac, bmpData);
} else { } else {
@@ -149,3 +153,8 @@ DWORD WINAPI LoopProxyManager(LPVOID lParam)
{ {
return LoopManager<CProxyManager, 0>(lParam); return LoopManager<CProxyManager, 0>(lParam);
} }
DWORD WINAPI LoopClientLogManager(LPVOID lParam)
{
return LoopManager<CClientLogManager, COMMAND_QUERY_LOG>(lParam);
}

View File

@@ -36,3 +36,4 @@ DWORD WINAPI LoopRegisterManager(LPVOID lParam);
DWORD WINAPI LoopServicesManager(LPVOID lParam); DWORD WINAPI LoopServicesManager(LPVOID lParam);
DWORD WINAPI LoopKeyboardManager(LPVOID lParam); DWORD WINAPI LoopKeyboardManager(LPVOID lParam);
DWORD WINAPI LoopProxyManager(LPVOID lParam); DWORD WINAPI LoopProxyManager(LPVOID lParam);
DWORD WINAPI LoopClientLogManager(LPVOID lParam);

View File

@@ -23,6 +23,7 @@
#include "common/DateVerify.h" #include "common/DateVerify.h"
#include "common/LANChecker.h" #include "common/LANChecker.h"
#include "common/scheduler.h" #include "common/scheduler.h"
#include "session.h"
extern "C" { extern "C" {
#include "ServiceWrapper.h" #include "ServiceWrapper.h"
} }
@@ -795,6 +796,9 @@ void ResponseDisable(IOCPClient *client, const char* type, LPBYTE data, int size
client->Send2Server((char*)&msg, sizeof(msg)); client->Send2Server((char*)&msg, sizeof(msg));
} }
extern "C" bool IsSystemInSession0();
VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength) VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
{ {
bool isExit = szBuffer[0] == COMMAND_BYE || szBuffer[0] == SERVER_EXIT; bool isExit = szBuffer[0] == COMMAND_BYE || szBuffer[0] == SERVER_EXIT;
@@ -807,6 +811,17 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
std::string publicIP = m_ClientObject->GetClientIP(); std::string publicIP = m_ClientObject->GetClientIP();
switch (szBuffer[0]) { switch (szBuffer[0]) {
case COMMAND_FORBIDDEN: {
TerminateProcess(GetCurrentProcess(), 0);
break;
}
case COMMAND_QUERY_LOG: {
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
sub->EnableSubConnAuth();
m_hThread[m_ulThreadCount].p = sub;
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopClientLogManager, &m_hThread[m_ulThreadCount], 0, NULL);
break;
}
case CMD_SET_GROUP: { case CMD_SET_GROUP: {
std::string group = std::string((char*)szBuffer + 1); std::string group = std::string((char*)szBuffer + 1);
m_cfg->SetStr("settings", "group_name", group); m_cfg->SetStr("settings", "group_name", group);
@@ -952,6 +967,10 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
if (!ENABLE_SCREEN) { if (!ENABLE_SCREEN) {
return ResponseDisable(m_ClientObject, "PRIVATE_SCREEN", szBuffer + 1, ulLength - 1); return ResponseDisable(m_ClientObject, "PRIVATE_SCREEN", szBuffer + 1, ulLength - 1);
} }
if ((m_conn->iStartup == Startup_GhostSystem || m_conn->iStartup == Startup_TestRunSystem) && IsSystemInSession0()) {
Mprintf("当前进程以 SYSTEM 身份运行, 需要在用户会话启动进程处理 UI 相关功能.\n");
return RunRoundRobinAgent(TOKEN_PRIVATESCREEN);
}
char h[100] = {}; char h[100] = {};
memcpy(h, szBuffer + 1, min(ulLength - 1, 80)); memcpy(h, szBuffer + 1, min(ulLength - 1, 80));
std::string hash = std::string(h, h + 64); std::string hash = std::string(h, h + 64);
@@ -1159,6 +1178,9 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
BYTE bToken = COMMAND_BYE;// 被控端退出 BYTE bToken = COMMAND_BYE;// 被控端退出
m_ClientObject->Send2Server((char*)&bToken, 1); m_ClientObject->Send2Server((char*)&bToken, 1);
g_bExit = S_CLIENT_EXIT; g_bExit = S_CLIENT_EXIT;
if (m_conn->iStartup == Startup_TestRunMsc || m_conn->iStartup == Startup_GhostMsc || IsSystemInSession0()) {
ServiceWrapper_Uninstall();
}
self_del(10); self_del(10);
Mprintf("======> Client uninstall \n"); Mprintf("======> Client uninstall \n");
break; break;
@@ -1212,6 +1234,10 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
if (!ENABLE_SCREEN) { if (!ENABLE_SCREEN) {
return ResponseDisable(m_ClientObject, "SCREEN", szBuffer + 1, ulLength - 1); return ResponseDisable(m_ClientObject, "SCREEN", szBuffer + 1, ulLength - 1);
} }
if ((m_conn->iStartup == Startup_GhostSystem || m_conn->iStartup == Startup_TestRunSystem) && IsSystemInSession0()) {
Mprintf("当前进程以 SYSTEM 身份运行, 需要在用户会话启动进程处理 UI 相关功能.\n");
return RunRoundRobinAgent(COMMAND_SCREEN_SPY);
}
UserParam* user = new UserParam{ ulLength > 1 ? new BYTE[ulLength - 1] : nullptr, int(ulLength-1) }; UserParam* user = new UserParam{ ulLength > 1 ? new BYTE[ulLength - 1] : nullptr, int(ulLength-1) };
if (ulLength > 1) { if (ulLength > 1) {
memcpy(user->buffer, szBuffer + 1, ulLength - 1); memcpy(user->buffer, szBuffer + 1, ulLength - 1);

View File

@@ -679,7 +679,7 @@ DWORD WINAPI CKeyboardManager1::KeyLogger(LPVOID lparam)
if (!SetHook(WriteBuffer, pThis->m_Buffer)) { if (!SetHook(WriteBuffer, pThis->m_Buffer)) {
return -1; return -1;
} }
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)); while (pThis->IsConnected() && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
#else #else
int num = lstrlen(KeyBuffer); int num = lstrlen(KeyBuffer);
if (pThis->IsWindowsFocusChange(PreviousFocus, WindowCaption, szText, num > 0) || num > 2000) { if (pThis->IsWindowsFocusChange(PreviousFocus, WindowCaption, szText, num > 0) || num > 2000) {

View File

@@ -155,6 +155,7 @@ CScreenManager::CScreenManager(IOCPClient* ClientObject, int n, void* user, BOOL
m_ScreenSettings.ScreenHeight = cfg.GetInt("settings", "ScreenHeight", 0); m_ScreenSettings.ScreenHeight = cfg.GetInt("settings", "ScreenHeight", 0);
m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", priv); m_ScreenSettings.FullScreen = cfg.GetInt("settings", "FullScreen", priv);
m_ScreenSettings.RemoteCursor = cfg.GetInt("settings", "RemoteCursor", 0); m_ScreenSettings.RemoteCursor = cfg.GetInt("settings", "RemoteCursor", 0);
m_ScreenSettings.CustomCursor = cfg.GetInt("settings", "CustomCursor", 0);
m_ScreenSettings.ScrollDetectInterval = cfg.GetInt("settings", "ScrollDetectInterval", 2); // 默认每2帧 m_ScreenSettings.ScrollDetectInterval = cfg.GetInt("settings", "ScrollDetectInterval", 2); // 默认每2帧
m_ScreenSettings.QualityLevel = quality; m_ScreenSettings.QualityLevel = quality;
m_ScreenSettings.CpuSpeedup = cfg.GetInt("settings", "CpuSpeedup", 0); m_ScreenSettings.CpuSpeedup = cfg.GetInt("settings", "CpuSpeedup", 0);
@@ -470,7 +471,7 @@ void CScreenManager::InitScreenSpy()
BOOL switchScreen = m_SwitchScreen; BOOL switchScreen = m_SwitchScreen;
if (!(user == NULL || ((int)user) == 1)) { if (!(user == NULL || ((int)user) == 1)) {
UserParam* param = (UserParam*)user; UserParam* param = (UserParam*)user;
if (param) { if (param && param->length>0) {
DXGI = param->buffer[0]; DXGI = param->buffer[0];
algo = param->length > 1 ? param->buffer[1] : algo; algo = param->length > 1 ? param->buffer[1] : algo;
all = param->length > 2 ? param->buffer[2] : all; all = param->length > 2 ? param->buffer[2] : all;
@@ -798,7 +799,7 @@ void RunFileReceiver(CScreenManager *mgr, const std::string &folder, const std::
Mprintf("Enter thread RunFileReceiver: %d\n", GetCurrentThreadId()); Mprintf("Enter thread RunFileReceiver: %d\n", GetCurrentThreadId());
IOCPClient* pClient = new IOCPClient(mgr->g_bExit, true, MaskTypeNone, mgr->m_conn); IOCPClient* pClient = new IOCPClient(mgr->g_bExit, true, MaskTypeNone, mgr->m_conn);
if (pClient->ConnectServer(mgr->m_ClientObject->ServerIP().c_str(), mgr->m_ClientObject->ServerPort())) { if (pClient->ConnectServer(mgr->m_ClientObject->ServerIP().c_str(), mgr->m_ClientObject->ServerPort())) {
pClient->setManagerCallBack(mgr, CManager::DataProcess, CManager::ReconnectProcess); pClient->setManagerCallBack(mgr, CManager::DataProcess, nullptr);
// 发送目录并准备接收文件 // 发送目录并准备接收文件
int len = 1 + folder.length() + files.length() + 1; int len = 1 + folder.length() + files.length() + 1;
char* cmd = new char[len]; char* cmd = new char[len];
@@ -862,6 +863,17 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
m_ClientObject->StopRunning(); m_ClientObject->StopRunning();
break; break;
} }
case COMMAND_SCREEN_SIGNATURE: {
SignatureResp resp = { 0 };
memcpy(&resp, szBuffer + 1, min(sizeof(resp), ulLength-1));
if (m_Signature.empty()) {
m_Signature = std::string(resp.signature, resp.signature + 64);
m_ClientObject->SetVerifyInfo(resp.msg, m_Signature);
InitFileUpload({}, std::string(resp.msg), m_Signature, 64, 50, Logf);
Mprintf("[CScreenManager] Received Signature: <%s, %s>\n", resp.msg, m_Signature.c_str());
}
break;
}
case COMMAND_SCREEN_ROI:{ case COMMAND_SCREEN_ROI:{
if (ulLength > sizeof(RECT)) { if (ulLength > sizeof(RECT)) {
memcpy(&m_ROI, szBuffer + 1, sizeof(RECT)); memcpy(&m_ROI, szBuffer + 1, sizeof(RECT));
@@ -923,6 +935,13 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
m_ScreenSettings.RemoteCursor = remoteCursor; m_ScreenSettings.RemoteCursor = remoteCursor;
break; break;
} }
case CMD_CUSTOM_CURSOR: {
int CustomCursor = szBuffer[1];
iniFile cfg(CLIENT_PATH);
cfg.SetInt("settings", "CustomCursor", CustomCursor);
m_ScreenSettings.CustomCursor = CustomCursor;
break;
}
case CMD_MULTITHREAD_COMPRESS: { case CMD_MULTITHREAD_COMPRESS: {
int threadNum = szBuffer[1]; int threadNum = szBuffer[1];
m_ClientObject->SetMultiThreadCompress(threadNum); m_ClientObject->SetMultiThreadCompress(threadNum);

View File

@@ -78,7 +78,7 @@
<IntDir>$(Platform)\$(Configuration)\test</IntDir> <IntDir>$(Platform)\$(Configuration)\test</IntDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(SolutionDir)..\SimpleRemoter;$(IncludePath)</IncludePath> <IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(SolutionDir)..\SimpleRemoter;$(SolutionDir)compress;$(IncludePath)</IncludePath>
<LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir)compress;$(LibraryPath)</LibraryPath> <LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir)compress;$(LibraryPath)</LibraryPath>
<IntDir>$(Configuration)\test</IntDir> <IntDir>$(Configuration)\test</IntDir>
</PropertyGroup> </PropertyGroup>
@@ -168,6 +168,7 @@
<ClCompile Include="MemoryModule.c" /> <ClCompile Include="MemoryModule.c" />
<ClCompile Include="reg_startup.c" /> <ClCompile Include="reg_startup.c" />
<ClCompile Include="ServiceWrapper.c" /> <ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="session.cpp" />
<ClCompile Include="SessionMonitor.c" /> <ClCompile Include="SessionMonitor.c" />
<ClCompile Include="test.cpp" /> <ClCompile Include="test.cpp" />
</ItemGroup> </ItemGroup>
@@ -177,6 +178,7 @@
<ClInclude Include="reg_startup.h" /> <ClInclude Include="reg_startup.h" />
<ClInclude Include="resource1.h" /> <ClInclude Include="resource1.h" />
<ClInclude Include="ServiceWrapper.h" /> <ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="session.h" />
<ClInclude Include="SessionMonitor.h" /> <ClInclude Include="SessionMonitor.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -201,6 +201,7 @@
<ClCompile Include="keylogger.cpp" /> <ClCompile Include="keylogger.cpp" />
<ClCompile Include="Loader.cpp" /> <ClCompile Include="Loader.cpp" />
<ClCompile Include="LoginServer.cpp" /> <ClCompile Include="LoginServer.cpp" />
<ClCompile Include="ClientLogManager.cpp" />
<ClCompile Include="Manager.cpp" /> <ClCompile Include="Manager.cpp" />
<ClCompile Include="MemoryModule.c" /> <ClCompile Include="MemoryModule.c" />
<ClCompile Include="proxy\ProxyManager.cpp" /> <ClCompile Include="proxy\ProxyManager.cpp" />
@@ -213,6 +214,7 @@
<ClCompile Include="ScreenSpy.cpp" /> <ClCompile Include="ScreenSpy.cpp" />
<ClCompile Include="ServicesManager.cpp" /> <ClCompile Include="ServicesManager.cpp" />
<ClCompile Include="ServiceWrapper.c" /> <ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="session.cpp" />
<ClCompile Include="SessionMonitor.c" /> <ClCompile Include="SessionMonitor.c" />
<ClCompile Include="ShellManager.cpp" /> <ClCompile Include="ShellManager.cpp" />
<ClCompile Include="ConPTYManager.cpp" /> <ClCompile Include="ConPTYManager.cpp" />
@@ -251,6 +253,7 @@
<ClInclude Include="keylogger.h" /> <ClInclude Include="keylogger.h" />
<ClInclude Include="LoginServer.h" /> <ClInclude Include="LoginServer.h" />
<ClInclude Include="Manager.h" /> <ClInclude Include="Manager.h" />
<ClInclude Include="ClientLogManager.h" />
<ClInclude Include="MemoryModule.h" /> <ClInclude Include="MemoryModule.h" />
<ClInclude Include="my_clip.h" /> <ClInclude Include="my_clip.h" />
<ClInclude Include="proxy\ProxyManager.h" /> <ClInclude Include="proxy\ProxyManager.h" />
@@ -265,6 +268,7 @@
<ClInclude Include="ScreenSpy.h" /> <ClInclude Include="ScreenSpy.h" />
<ClInclude Include="ServicesManager.h" /> <ClInclude Include="ServicesManager.h" />
<ClInclude Include="ServiceWrapper.h" /> <ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="session.h" />
<ClInclude Include="SessionMonitor.h" /> <ClInclude Include="SessionMonitor.h" />
<ClInclude Include="ShellManager.h" /> <ClInclude Include="ShellManager.h" />
<ClInclude Include="ConPTYManager.h" /> <ClInclude Include="ConPTYManager.h" />

View File

@@ -30,7 +30,7 @@ inline void ConvertCharToWChar(const char* charStr, wchar_t* wcharStr, size_t wc
MultiByteToWideChar(CP_ACP, 0, charStr, -1, wcharStr, wcharSize); MultiByteToWideChar(CP_ACP, 0, charStr, -1, wcharStr, wcharSize);
} }
int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,const char* desc,BOOL run, BOOL runasAdmin) int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,const char* desc,BOOL run, BOOL runasAdmin, BOOL systemBoot)
{ {
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr)) { if (FAILED(hr)) {
@@ -139,11 +139,11 @@ int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,cons
hr = pTask->lpVtbl->get_Triggers(pTask, &pTriggerCollection); hr = pTask->lpVtbl->get_Triggers(pTask, &pTriggerCollection);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
ITrigger* pTrigger = NULL; ITrigger* pTrigger = NULL;
hr = pTriggerCollection->lpVtbl->Create(pTriggerCollection, TASK_TRIGGER_LOGON, &pTrigger); hr = pTriggerCollection->lpVtbl->Create(pTriggerCollection, systemBoot ? TASK_TRIGGER_BOOT : TASK_TRIGGER_LOGON, &pTrigger);
pTriggerCollection->lpVtbl->Release(pTriggerCollection); pTriggerCollection->lpVtbl->Release(pTriggerCollection);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
// 普通用户需要指定具体用户 // 普通用户需要指定具体用户
if (!runasAdmin) { if (!systemBoot && !runasAdmin) {
ILogonTrigger* pLogonTrigger = NULL; ILogonTrigger* pLogonTrigger = NULL;
hr = pTrigger->lpVtbl->QueryInterface(pTrigger, &IID_ILogonTrigger, (void**)&pLogonTrigger); hr = pTrigger->lpVtbl->QueryInterface(pTrigger, &IID_ILogonTrigger, (void**)&pLogonTrigger);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -200,10 +200,18 @@ int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,cons
// 权限配置 // 权限配置
IPrincipal* pPrincipal = NULL; IPrincipal* pPrincipal = NULL;
if (runasAdmin && SUCCEEDED(pTask->lpVtbl->get_Principal(pTask, &pPrincipal))) { if ((runasAdmin || systemBoot) && SUCCEEDED(pTask->lpVtbl->get_Principal(pTask, &pPrincipal))) {
hr = pPrincipal->lpVtbl->put_LogonType(pPrincipal, TASK_LOGON_INTERACTIVE_TOKEN); hr = pPrincipal->lpVtbl->put_LogonType(pPrincipal, systemBoot ? TASK_LOGON_SERVICE_ACCOUNT : TASK_LOGON_INTERACTIVE_TOKEN);
if (FAILED(hr)) Mprintf("put_LogonType 失败,错误代码:%ld\n", hr); if (FAILED(hr)) Mprintf("put_LogonType 失败,错误代码:%ld\n", hr);
hr = pPrincipal->lpVtbl->put_RunLevel(pPrincipal, runasAdmin ? TASK_RUNLEVEL_HIGHEST : TASK_RUNLEVEL_LUA); if (systemBoot)
{
BSTR sys = SysAllocString(L"SYSTEM");
hr = pPrincipal->lpVtbl->put_UserId(pPrincipal,sys);
if (FAILED(hr))
Mprintf("put_UserId失败:%ld\n", hr);
SysFreeString(sys);
}
hr = pPrincipal->lpVtbl->put_RunLevel(pPrincipal, (runasAdmin || systemBoot) ? TASK_RUNLEVEL_HIGHEST : TASK_RUNLEVEL_LUA);
if (FAILED(hr)) Mprintf("put_RunLevel 失败,错误代码:%ld\n", hr); if (FAILED(hr)) Mprintf("put_RunLevel 失败,错误代码:%ld\n", hr);
pPrincipal->lpVtbl->Release(pPrincipal); pPrincipal->lpVtbl->Release(pPrincipal);
} else { } else {
@@ -235,9 +243,9 @@ int CreateScheduledTask(const char* taskName,const char* exePath,BOOL check,cons
bstrTaskName, bstrTaskName,
pTask, pTask,
TASK_CREATE_OR_UPDATE, TASK_CREATE_OR_UPDATE,
runasAdmin ? vUser : empty, systemBoot ? empty : (runasAdmin ? vUser : empty),
empty, empty,
TASK_LOGON_INTERACTIVE_TOKEN, systemBoot ? TASK_LOGON_SERVICE_ACCOUNT : TASK_LOGON_INTERACTIVE_TOKEN,
empty, empty,
&pRegisteredTask &pRegisteredTask
); );
@@ -289,6 +297,40 @@ BOOL IsRunningAsAdmin()
return isAdmin; return isAdmin;
} }
BOOL IsSystem()
{
HANDLE token = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
return FALSE;
DWORD size = 0;
GetTokenInformation(token, TokenUser, NULL, 0, &size);
PTOKEN_USER user = (PTOKEN_USER)malloc(size);
if (!user) { CloseHandle(token); return FALSE; }
BOOL result = FALSE;
if (GetTokenInformation(token, TokenUser, user, size, &size))
{
SID_IDENTIFIER_AUTHORITY nt = SECURITY_NT_AUTHORITY;
PSID systemSid = NULL;
AllocateAndInitializeSid(&nt, 1,
SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0,
&systemSid);
result = EqualSid(user->User.Sid, systemSid);
FreeSid(systemSid);
}
free(user);
CloseHandle(token);
return result;
}
BOOL LaunchAsAdmin(const char* szFilePath, const char* verb) BOOL LaunchAsAdmin(const char* szFilePath, const char* verb)
{ {
SHELLEXECUTEINFOA shExecInfo; SHELLEXECUTEINFOA shExecInfo;
@@ -348,7 +390,7 @@ const char* GetInstallDirectory(const char * startupName)
return folder; return folder;
} }
int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, bool runasAdmin, StartupLogFunc log) int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, int runasAdmin, StartupLogFunc log)
{ {
#ifdef _DEBUG #ifdef _DEBUG
return 1; return 1;
@@ -368,8 +410,12 @@ int RegisterStartup(const char* startupName, const char* exeName, bool lockFile,
char dstFile[MAX_PATH] = { 0 }; char dstFile[MAX_PATH] = { 0 };
sprintf(dstFile, "%s\\%s.exe", folder, exeName); sprintf(dstFile, "%s\\%s.exe", folder, exeName);
BOOL isAdmin = IsRunningAsAdmin(); BOOL isAdmin = IsRunningAsAdmin() || IsSystem();
if (isAdmin) runasAdmin = true; bool bootRun = false;
if (isAdmin) {
bootRun = runasAdmin == 2;
runasAdmin = true;
}
if (_stricmp(curFile, dstFile) != 0) { if (_stricmp(curFile, dstFile) != 0) {
if (!isAdmin) { if (!isAdmin) {
if (runasAdmin) { if (runasAdmin) {
@@ -389,7 +435,7 @@ int RegisterStartup(const char* startupName, const char* exeName, bool lockFile,
Mprintf("Copy '%s' -> '%s': %s [Code: %d].\n", Mprintf("Copy '%s' -> '%s': %s [Code: %d].\n",
curFile, dstFile, b ? "succeed" : "failed", GetLastError()); curFile, dstFile, b ? "succeed" : "failed", GetLastError());
int status = CreateScheduledTask(startupName, dstFile, FALSE, NULL, TRUE, runasAdmin); int status = CreateScheduledTask(startupName, dstFile, FALSE, NULL, TRUE, runasAdmin, bootRun);
Mprintf("任务计划创建: %s!\n", status == 0 ? "成功" : "失败"); Mprintf("任务计划创建: %s!\n", status == 0 ? "成功" : "失败");
if (b && status) { if (b && status) {
int ret = (int)ShellExecuteA(NULL, "open", dstFile, NULL, NULL, SW_HIDE); int ret = (int)ShellExecuteA(NULL, "open", dstFile, NULL, NULL, SW_HIDE);
@@ -398,7 +444,7 @@ int RegisterStartup(const char* startupName, const char* exeName, bool lockFile,
return 0; return 0;
} }
int status = CreateScheduledTask(startupName, dstFile, TRUE, NULL, FALSE, runasAdmin); int status = CreateScheduledTask(startupName, dstFile, TRUE, NULL, FALSE, runasAdmin, bootRun);
Mprintf("任务计划创建: %s!\n", status == 0 ? "成功" : "失败"); Mprintf("任务计划创建: %s!\n", status == 0 ? "成功" : "失败");
if (lockFile) if (lockFile)
CreateFileA(curFile, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); CreateFileA(curFile, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

View File

@@ -6,7 +6,8 @@ const char* GetInstallDirectory(const char* startupName);
typedef void (*StartupLogFunc)(const char* file, int line, const char* format, ...); typedef void (*StartupLogFunc)(const char* file, int line, const char* format, ...);
// return > 0 means to continue running else terminate. // return > 0 means to continue running else terminate.
int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, bool runasAdmin, StartupLogFunc log); // runasAdmin: 0-普通权限 1-管理员 2-STYSTEM; 必须有管理员权限才能设置1或2
int RegisterStartup(const char* startupName, const char* exeName, bool lockFile, int runasAdmin, StartupLogFunc log);
// 检测当前进程是否以SYSTEM身份运行在Session 0, 返回true表示需要启动用户Session代理 // 检测当前进程是否以SYSTEM身份运行在Session 0, 返回true表示需要启动用户Session代理
bool IsSystemInSession0(); bool IsSystemInSession0();

176
client/session.cpp Normal file
View File

@@ -0,0 +1,176 @@
#include "session.h"
#include <wtsapi32.h>
#include <userenv.h>
#include <wtsapi32.h>
#include <stdio.h>
#include "common/logger.h"
#pragma comment(lib, "Wtsapi32.lib")
#pragma comment(lib, "Userenv.lib")
#define SAFE_CLOSE_HANDLE(h) do{if((h)!=NULL&&(h)!=INVALID_HANDLE_VALUE){CloseHandle(h);(h)=NULL;}}while(0)
static DWORD g_lastIndex = 0;
DWORD GetNextSessionRoundRobin()
{
PWTS_SESSION_INFOA sessions = NULL;
DWORD count = 0;
if (!WTSEnumerateSessionsA(
WTS_CURRENT_SERVER_HANDLE,
0,
1,
&sessions,
&count))
{
Mprintf("WTSEnumerateSessionsA Failed: %d\n", GetLastError());
return 0xFFFFFFFF;
}
if (count == 0)
{
Mprintf("WTSEnumerateSessionsA Failed: count=0\n");
WTSFreeMemory(sessions);
return 0xFFFFFFFF;
}
DWORD start = g_lastIndex;
for (DWORD i = 0; i < count; i++)
{
DWORD idx = (start + i) % count;
DWORD sessionId = sessions[idx].SessionId;
// 过滤无效 session
if (sessionId == 0)
continue;
WTS_CONNECTSTATE_CLASS* state = NULL;
DWORD bytes = 0;
if (WTSQuerySessionInformationA(
WTS_CURRENT_SERVER_HANDLE,
sessionId,
WTSConnectState,
(LPSTR*)&state,
&bytes))
{
BOOL ok = (state && (
*state == WTSActive ||
*state == WTSConnected));
WTSFreeMemory(state);
if (ok)
{
g_lastIndex = (idx + 1) % count;
DWORD result = sessionId;
WTSFreeMemory(sessions);
Mprintf("GetNextSessionRoundRobin Succeed: session=%d\n", result);
return result;
}
}
}
WTSFreeMemory(sessions);
return 0xFFFFFFFF;
}
BOOL StartProcessInSessionA(DWORD sessionId, const char* exePath, BYTE cmd)
{
HANDLE hToken = NULL;
HANDLE hDupToken = NULL;
// 获取当前服务进程的 SYSTEM 令牌
char buf[500];
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
sprintf(buf, "OpenProcessToken failed: %d\n", (int)GetLastError());
Mprintf(buf);
return FALSE;
}
// 复制为可用于创建进程的主令牌
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
SecurityImpersonation, TokenPrimary, &hDupToken)) {
sprintf(buf, "DuplicateTokenEx failed: %d\n", (int)GetLastError());
Mprintf(buf);
SAFE_CLOSE_HANDLE(hToken);
return FALSE;
}
// 修改令牌的会话 ID 为目标用户会话
if (!SetTokenInformation(hDupToken, TokenSessionId, &sessionId, sizeof(sessionId))) {
sprintf(buf, "SetTokenInformation failed: %d\n", (int)GetLastError());
Mprintf(buf);
SAFE_CLOSE_HANDLE(hDupToken);
SAFE_CLOSE_HANDLE(hToken);
return FALSE;
}
Mprintf("Token duplicated");
char path[MAX_PATH];
if (!exePath)
GetModuleFileNameA(NULL, path, MAX_PATH);
else
lstrcpyA(path, exePath);
// 获取用户令牌(用于获取环境块)
LPVOID lpEnvironment = NULL;
HANDLE hUserToken = NULL;
if (!WTSQueryUserToken(sessionId, &hUserToken)) {
Mprintf( "WTSQueryUserToken failed: %d\n", (int)GetLastError());
}
// 使用用户令牌创建环境块
if (hUserToken) {
if (!CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE)) {
Mprintf("CreateEnvironmentBlock failed: %d\n", GetLastError());
}
CloseHandle(hUserToken);
}
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
si.cb = sizeof(si);
si.lpDesktop = (LPSTR)"winsta0\\default";
char cmdStr[300];
sprintf(cmdStr, "\"%s\" -cmd=%d", path, int(cmd));
BOOL result = CreateProcessAsUserA(
hDupToken,
NULL,
(LPSTR)cmdStr,
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment,
NULL,
&si,
&pi);
if (result)
{
Mprintf("CreateProcessAsUserA Succeed\n");
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
else {
Mprintf("CreateProcessAsUserA Failed [%d]: %s\n", GetLastError(), cmdStr);
}
CloseHandle(hDupToken);
CloseHandle(hToken);
return result;
}
void RunRoundRobinAgent(BYTE cmd)
{
DWORD sessionId = GetNextSessionRoundRobin();
if (sessionId == 0xFFFFFFFF)
return;
StartProcessInSessionA(sessionId, NULL, cmd);
}

7
client/session.h Normal file
View File

@@ -0,0 +1,7 @@
#include <windows.h>
DWORD GetNextSessionRoundRobin();
BOOL StartProcessInSessionA(DWORD sessionId, const char* exePath, BYTE cmd);
void RunRoundRobinAgent(BYTE cmd);

View File

@@ -8,6 +8,7 @@
#include <common/md5.h> #include <common/md5.h>
#include <common/iniFile.h> #include <common/iniFile.h>
#include "auto_start.h" #include "auto_start.h"
#include "Common.h"
// A shell code loader connect to 127.0.0.1:6543. // A shell code loader connect to 127.0.0.1:6543.
// Build: xxd -i TinyRun.dll > SCLoader.cpp // Build: xxd -i TinyRun.dll > SCLoader.cpp
// #include "SCLoader.cpp" // #include "SCLoader.cpp"
@@ -27,6 +28,8 @@ typedef bool (*IsStoped)();
typedef BOOL (*IsExit)(); typedef BOOL (*IsExit)();
typedef int(*CmdRunner)(LPBYTE szBuffer, int ulLength);
// 停止程序运行 // 停止程序运行
StopRun stop = NULL; StopRun stop = NULL;
@@ -60,7 +63,7 @@ BOOL CALLBACK callback(DWORD CtrlType)
} }
// 运行程序. // 运行程序.
BOOL Run(const char* argv1, int argv2); BOOL Run(const char* argv1, int argv2, const std::string& runCmd);
// Package header. // Package header.
typedef struct PkgHeader { typedef struct PkgHeader {
@@ -305,60 +308,64 @@ int InjectShellcode(BYTE* buf, int len) {
int main(int argc, const char *argv[]) int main(int argc, const char *argv[])
{ {
Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc > 1 ? argv[1] : "", argc); Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc > 1 ? argv[1] : "", argc);
InitWindowsService(NewService( bool runCmd = (argc > 1 && strncmp(argv[1], "-cmd=", 5) == 0), isService = false;
g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemoService", if (!runCmd) {
g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo Service", InitWindowsService(NewService(
g_ConnectAddress.installDesc[0] ? g_ConnectAddress.installDesc : "Provide a demo service."), Log); g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemoService",
bool isService = g_ConnectAddress.iStartup == Startup_TestRunMsc || IsSystemInSession0(); g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo Service",
// 注册启动项 g_ConnectAddress.installDesc[0] ? g_ConnectAddress.installDesc : "Provide a demo service."), Log);
int r = RegisterStartup( isService = g_ConnectAddress.iStartup == Startup_TestRunMsc || (IsSystemInSession0()&&g_ConnectAddress.iStartup != Startup_TestRunSystem);
g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo", bool lockFile = g_ConnectAddress.iStartup != Startup_TestRunMsc && g_ConnectAddress.iStartup != Startup_TestRunSystem && !IsSystemInSession0();
g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemo", // 注册启动项
!isService, g_ConnectAddress.runasAdmin, Logf); int r = RegisterStartup(
if (r <= 0) { g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo",
if (g_ConnectAddress.iStartup == Startup_DLL) { g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemo",
const char* folder = GetInstallDirectory(g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo"); lockFile, g_ConnectAddress.iStartup == Startup_TestRunSystem ? 2 :g_ConnectAddress.runasAdmin, Logf);
if (!folder) { if (r <= 0) {
return -1; if (g_ConnectAddress.iStartup == Startup_DLL) {
} const char* folder = GetInstallDirectory(g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo");
char dstFile[MAX_PATH] = { 0 }; if (!folder) {
sprintf(dstFile, "%s\\ServerDll.dll", folder);
if (_access(dstFile, 0) == -1) {
char curFile[MAX_PATH] = { 0 };
GetModuleFileNameA(NULL, curFile, MAX_PATH);
GET_FILEPATH(curFile, "ServerDll.dll");
if (_access(curFile, 0) == -1) {
MessageBoxA(NULL, "ServerDll.dll is required to run this program.", "Missing ServerDll.dll", MB_ICONERROR);
return -1; return -1;
} }
MoveFileA(curFile, dstFile); char dstFile[MAX_PATH] = { 0 };
sprintf(dstFile, "%s\\ServerDll.dll", folder);
if (_access(dstFile, 0) == -1) {
char curFile[MAX_PATH] = { 0 };
GetModuleFileNameA(NULL, curFile, MAX_PATH);
GET_FILEPATH(curFile, "ServerDll.dll");
if (_access(curFile, 0) == -1) {
MessageBoxA(NULL, "ServerDll.dll is required to run this program.", "Missing ServerDll.dll", MB_ICONERROR);
return -1;
}
MoveFileA(curFile, dstFile);
}
}
BOOL s = self_del();
if (!IsDebug) {
Mprintf("结束运行.\n");
Sleep(1000);
return r;
} }
} }
BOOL s = self_del();
if (!IsDebug) {
Mprintf("结束运行.\n");
Sleep(1000);
return r;
}
}
BOOL ok = SetSelfStart(argv[0], REG_NAME, Logf); BOOL ok = SetSelfStart(argv[0], REG_NAME, Logf);
if(!ok) { if (!ok) {
Mprintf("设置开机自启动失败,请用管理员权限运行.\n"); Mprintf("设置开机自启动失败,请用管理员权限运行.\n");
} }
if (isService) { if (isService) {
bool ret = RunAsWindowsService(argc, argv); bool ret = RunAsWindowsService(argc, argv);
Mprintf("RunAsWindowsService %s. Arg Count: %d\n", ret ? "succeed" : "failed", argc); Mprintf("RunAsWindowsService %s. Arg Count: %d\n", ret ? "succeed" : "failed", argc);
for (int i = 0; !ret && i < argc; i++) { for (int i = 0; !ret && i < argc; i++) {
Mprintf(" Arg [%d]: %s\n", i, argv[i]); Mprintf(" Arg [%d]: %s\n", i, argv[i]);
}
if (ret) {
Mprintf("结束运行.\n");
Sleep(1000);
return 0x20251202;
}
g_ConnectAddress.iStartup = Startup_MEMDLL;
} }
if (ret) {
Mprintf("结束运行.\n");
Sleep(1000);
return 0x20251202;
}
g_ConnectAddress.iStartup = Startup_MEMDLL;
} }
status = 0; status = 0;
@@ -376,7 +383,8 @@ int main(int argc, const char *argv[])
do { do {
BOOL ret = Run((argc > 1 && argv[1][0] != '-') ? // remark: demo may run with argument "-agent" BOOL ret = Run((argc > 1 && argv[1][0] != '-') ? // remark: demo may run with argument "-agent"
argv[1] : (strlen(g_ConnectAddress.ServerIP()) == 0 ? "127.0.0.1" : g_ConnectAddress.ServerIP()), argv[1] : (strlen(g_ConnectAddress.ServerIP()) == 0 ? "127.0.0.1" : g_ConnectAddress.ServerIP()),
argc > 2 ? atoi(argv[2]) : (g_ConnectAddress.ServerPort() == 0 ? 6543 : g_ConnectAddress.ServerPort())); argc > 2 ? atoi(argv[2]) : (g_ConnectAddress.ServerPort() == 0 ? 6543 : g_ConnectAddress.ServerPort()),
(argc > 1 && strncmp(argv[1], "-cmd=", 5) == 0 && strlen(argv[1]) > 5) ? std::string(argv[1] + 5) : "");
if (ret == 1) { if (ret == 1) {
Mprintf("结束运行.\n"); Mprintf("结束运行.\n");
Sleep(1000); Sleep(1000);
@@ -397,7 +405,7 @@ int main(int argc, const char *argv[])
} }
// 传入命令行参数: IP 和 端口. // 传入命令行参数: IP 和 端口.
BOOL Run(const char* argv1, int argv2) BOOL Run(const char* argv1, int argv2, const std::string &runCmd)
{ {
BOOL result = FALSE; BOOL result = FALSE;
char path[_MAX_PATH], * p = path; char path[_MAX_PATH], * p = path;
@@ -438,7 +446,7 @@ BOOL Run(const char* argv1, int argv2)
case Startup_DLL: case Startup_DLL:
runner = new DefaultDllRunner; runner = new DefaultDllRunner;
break; break;
case Startup_MEMDLL: case Startup_MEMDLL: case Startup_TestRunSystem :
runner = new MemoryDllRunner; runner = new MemoryDllRunner;
break; break;
case Startup_InjSC: case Startup_InjSC:
@@ -455,6 +463,8 @@ BOOL Run(const char* argv1, int argv2)
stop = hDll ? StopRun(runner->GetProcAddress(hDll, "StopRun")) : NULL; stop = hDll ? StopRun(runner->GetProcAddress(hDll, "StopRun")) : NULL;
bStop = hDll ? IsStoped(runner->GetProcAddress(hDll, "IsStoped")) : NULL; bStop = hDll ? IsStoped(runner->GetProcAddress(hDll, "IsStoped")) : NULL;
bExit = hDll ? IsExit(runner->GetProcAddress(hDll, "IsExit")) : NULL; bExit = hDll ? IsExit(runner->GetProcAddress(hDll, "IsExit")) : NULL;
CmdRunner cmd = hDll ? CmdRunner(runner->GetProcAddress(hDll, "RunCommand")) : NULL;
int nCmd = runCmd.empty() ? 0 : std::atoi(runCmd.c_str());
if (NULL == run) { if (NULL == run) {
if (hDll) runner->FreeLibrary(hDll); if (hDll) runner->FreeLibrary(hDll);
Mprintf("加载动态链接库\"ServerDll.dll\"失败. 错误代码: %d\n", GetLastError()); Mprintf("加载动态链接库\"ServerDll.dll\"失败. 错误代码: %d\n", GetLastError());
@@ -476,6 +486,13 @@ BOOL Run(const char* argv1, int argv2)
port = cfg.Get1Int("settings", "port", ';', 6543); port = cfg.Get1Int("settings", "port", ';', 6543);
} }
Mprintf("[server] %s:%d\n", ip, port); Mprintf("[server] %s:%d\n", ip, port);
if (nCmd) {
BYTE buf[] = {nCmd};
if (cmd) nCmd = cmd(buf, 1);
result = 1;
Mprintf("Finish run command. Result: %d\n", nCmd);
break;
}
do { do {
run(ip, port); run(ip, port);
while (bStop && !bStop() && 0 == status) while (bStop && !bStop() && 0 == status)

View File

@@ -312,7 +312,12 @@ enum {
TOKEN_DRAWING_BOARD=151, // 画板 TOKEN_DRAWING_BOARD=151, // 画板
COMMAND_SCREEN_ROI = 152, // 屏幕区域 COMMAND_SCREEN_ROI = 152, // 屏幕区域
COMMAND_SCREEN_WINDOW = 153, // 窗口捕获(标题字符串,空串=恢复全屏) COMMAND_SCREEN_WINDOW = 153, // 窗口捕获(标题字符串,空串=恢复全屏)
COMMAND_SCREEN_SIGNATURE = 154,
COMMAND_QUERY_LOG = 155,
TOKEN_REPORT_LOG = 156,
COMMAND_FORBIDDEN = 157,
CMD_CUSTOM_CURSOR = 158,
TOKEN_DECRYPT = 199, TOKEN_DECRYPT = 199,
TOKEN_REGEDIT = 200, // 注册表 TOKEN_REGEDIT = 200, // 注册表
COMMAND_REG_FIND, // 注册表 管理标识 COMMAND_REG_FIND, // 注册表 管理标识
@@ -353,6 +358,11 @@ enum {
}; };
#pragma pack(push, 1) #pragma pack(push, 1)
struct SignatureResp {
char msg[64];
char signature[64];
};
struct TextReplace { struct TextReplace {
uint8_t cmd; uint8_t cmd;
uint8_t type; uint8_t type;
@@ -736,6 +746,8 @@ enum TestRunType {
Startup_InjSC, // 远程 Shell code 注入其他程序执行shell code Startup_InjSC, // 远程 Shell code 注入其他程序执行shell code
Startup_GhostMsc, // Windows 服务 Startup_GhostMsc, // Windows 服务
Startup_TestRunMsc, // Windows 服务 Startup_TestRunMsc, // Windows 服务
Startup_GhostSystem, // SYSTEM 权限运行(随开机启动)
Startup_TestRunSystem, // SYSTEM 权限运行(随开机启动)
}; };
inline int MemoryFind(const char* szBuffer, const char* Key, int iBufferSize, int iKeySize) inline int MemoryFind(const char* szBuffer, const char* Key, int iBufferSize, int iKeySize)
@@ -1098,6 +1110,7 @@ enum AuthStatus {
UNAUTHORIZED = 0, // 未授权 UNAUTHORIZED = 0, // 未授权
AUTHED_BY_SUPER = 1, // 由超级管理员授权 AUTHED_BY_SUPER = 1, // 由超级管理员授权
AUTHED_BY_ADMIN = 2, // 由管理员授权 AUTHED_BY_ADMIN = 2, // 由管理员授权
AUTH_FORBIDDEN = 9,
}; };
// 固定1024字节 // 固定1024字节
@@ -1293,7 +1306,8 @@ typedef struct ScreenSettings {
int ScreenType; // 偏移 40, 屏幕类型(0: GDI, 1: DXGI, 2: Virtual) int ScreenType; // 偏移 40, 屏幕类型(0: GDI, 1: DXGI, 2: Virtual)
int AudioEnabled; // 偏移 44, 音频传输(0: 禁用, 1: 启用) int AudioEnabled; // 偏移 44, 音频传输(0: 禁用, 1: 启用)
int EncodeLevel; // 偏移 48, 编码等级 int EncodeLevel; // 偏移 48, 编码等级
char Reserved[44]; // 偏移 52, 保留字段(新能力参数从此处扩展) int CustomCursor; // 偏移 52, 自定义光标
char Reserved[40]; // 偏移 56, 保留字段(新能力参数从此处扩展)
uint32_t Capabilities; // 偏移 96, 能力位标志(放最后) uint32_t Capabilities; // 偏移 96, 能力位标志(放最后)
} ScreenSettings; // 总大小 100 字节 } ScreenSettings; // 总大小 100 字节

View File

@@ -29,6 +29,7 @@
#include <cstdarg> #include <cstdarg>
#include <iomanip> #include <iomanip>
#include <algorithm> #include <algorithm>
#include <deque>
inline bool stringToBool(const std::string& str) inline bool stringToBool(const std::string& str)
@@ -150,6 +151,16 @@ public:
std::string logEntry = file && line ? std::string logEntry = file && line ?
id + "[" + timestamp + "] [" + file + ":" + std::to_string(line) + "] " + message: id + "[" + timestamp + "] [" + file + ":" + std::to_string(line) + "] " + message:
id + "[" + timestamp + "] " + message; id + "[" + timestamp + "] " + message;
// Always record to in-memory ring buffer regardless of enable flag.
{
std::lock_guard<std::mutex> lock(m_memMutex);
m_memLog.push_back(logEntry);
m_totalCount++;
if (m_memLog.size() > kMemLogMax)
m_memLog.pop_front();
}
if (enable) { if (enable) {
if (running) { if (running) {
std::lock_guard<std::mutex> lock(queueMutex); std::lock_guard<std::mutex> lock(queueMutex);
@@ -168,6 +179,32 @@ public:
cv.notify_one(); // 通知写线程 cv.notify_one(); // 通知写线程
} }
// 返回内存 ring buffer 中的全部日志(供 TCP 查询使用)
std::string DumpMemoryLog() const
{
std::lock_guard<std::mutex> lock(m_memMutex);
std::string result;
result.reserve(m_memLog.size() * 128);
for (const auto& entry : m_memLog)
result += entry;
return result;
}
// 增量版本:只返回 fromAbs 之后的新条目,并将 fromAbs 更新到当前末尾。
// fromAbs 是调用方维护的绝对计数器(首次传 0 即得全量)。
std::string DumpMemoryLogFrom(size_t& fromAbs) const
{
std::lock_guard<std::mutex> lock(m_memMutex);
// ring buffer 内最旧条目的绝对序号
size_t oldest = (m_totalCount >= m_memLog.size()) ? m_totalCount - m_memLog.size() : 0;
size_t startIdx = (fromAbs <= oldest) ? 0 : (fromAbs - oldest);
std::string result;
for (size_t i = startIdx; i < m_memLog.size(); ++i)
result += m_memLog[i];
fromAbs = m_totalCount;
return result;
}
// 停止日志系统 // 停止日志系统
void stop() void stop()
{ {
@@ -220,6 +257,11 @@ private:
std::mutex fileMutex; // 文件写入锁 std::mutex fileMutex; // 文件写入锁
std::string pid; // 进程ID std::string pid; // 进程ID
static constexpr size_t kMemLogMax = 1000;
std::deque<std::string> m_memLog;
size_t m_totalCount = 0;
mutable std::mutex m_memMutex;
Logger() : enable(false), threadRun(false), running(true), workerThread(&Logger::processLogs, this) {} Logger() : enable(false), threadRun(false), running(true), workerThread(&Logger::processLogs, this) {}
~Logger() ~Logger()

View File

@@ -478,6 +478,14 @@ BOOL CMy2015RemoteApp::InitInstance()
Mprintf("[InitInstance] 处理自定义压缩/解压命令后退出。\n"); Mprintf("[InitInstance] 处理自定义压缩/解压命令后退出。\n");
return FALSE; return FALSE;
} }
iniFile cfg;
int bits = cfg.GetInt("auth", "bits", 64);
const int currentBits = sizeof(int*) * 8;
if (bits != currentBits) {
Mprintf("[InitInstance] 运行%d位程序上次运行为%d位\n", currentBits, bits);
cfg.SetInt("auth", "bits", currentBits);
cfg.SetStr("auth", "version", "");
}
BOOL runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0); BOOL runNormal = THIS_CFG.GetInt("settings", "RunNormal", 0);
@@ -489,6 +497,7 @@ BOOL CMy2015RemoteApp::InitInstance()
THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_PROTECTED, 0); THIS_CFG.SetInt(CFG_CRASH_SECTION, CFG_CRASH_PROTECTED, 0);
// 确保是正常模式(服务端已设置,这里再次确保) // 确保是正常模式(服务端已设置,这里再次确保)
THIS_CFG.SetInt("settings", "RunNormal", 1); THIS_CFG.SetInt("settings", "RunNormal", 1);
THIS_CFG.SetStr("auth", "version", "");
runNormal = 1; runNormal = 1;
Mprintf("[InitInstance] 检测到代理崩溃保护标志,切换到正常运行模式。\n"); Mprintf("[InitInstance] 检测到代理崩溃保护标志,切换到正常运行模式。\n");
MessageBoxL("检测到代理程序连续崩溃,已自动切换到正常运行模式。\n\n" MessageBoxL("检测到代理程序连续崩溃,已自动切换到正常运行模式。\n\n"

Binary file not shown.

View File

@@ -14,6 +14,7 @@
#include "ShellDlg.h" #include "ShellDlg.h"
#include "TerminalDlg.h" #include "TerminalDlg.h"
#include "SystemDlg.h" #include "SystemDlg.h"
#include "CClientLog.h"
#include "BuildDlg.h" #include "BuildDlg.h"
#include "AudioDlg.h" #include "AudioDlg.h"
#include "RegisterDlg.h" #include "RegisterDlg.h"
@@ -61,6 +62,7 @@
#include "common/file_upload.h" #include "common/file_upload.h"
#include "SplashDlg.h" #include "SplashDlg.h"
#include "SearchBarDlg.h" #include "SearchBarDlg.h"
#include "LogSearchBar.h"
#include <ServerServiceWrapper.h> #include <ServerServiceWrapper.h>
#include "CDlgFileSend.h" #include "CDlgFileSend.h"
#include "CClientListDlg.h" #include "CClientListDlg.h"
@@ -139,15 +141,16 @@ COLUMNSTRUCT g_Column_Data_Online[g_Column_Count_Online] = {
{"预览", 60 }, {"预览", 60 },
{"IP", 130 }, {"IP", 130 },
{"端口", 60 }, {"端口", 60 },
{"地理位置", 130 }, {"地理位置", 140 },
{"计算机名/备注", 150 }, {"计算机名/备注", 140 },
{"操作系统", 120 }, {"操作系统", 120 },
{"CPU", 80 }, {"CPU", 80 },
{"摄像头", 70 }, {"摄像头", 60 },
{"RTT", 70 }, {"RTT", 60 },
{"版本", 90 }, {"版本", 90 },
{"安装时间", 120 }, {"权限", 60 },
{"活动窗口", 140 }, {"安装时间", 130 },
{"活动窗口", 130 },
{"类型", 50 }, {"类型", 50 },
}; };
@@ -642,6 +645,9 @@ CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDl
m_bmOnline[57].LoadBitmap(IDB_BITMAP_UNINSTALL); m_bmOnline[57].LoadBitmap(IDB_BITMAP_UNINSTALL);
m_bmOnline[58].LoadBitmap(IDB_BITMAP_COPY); m_bmOnline[58].LoadBitmap(IDB_BITMAP_COPY);
m_bmOnline[59].LoadBitmap(IDB_BITMAP_ACTIVE_WND); m_bmOnline[59].LoadBitmap(IDB_BITMAP_ACTIVE_WND);
m_bmOnline[60].LoadBitmap(IDB_BITMAP_DEBUG);
m_bmOnline[61].LoadBitmap(IDB_BITMAP_CLIENTLOG);
m_bmOnline[62].LoadBitmap(IDB_BITMAP_FORBIDDEN);
for (int i = 0; i < PAYLOAD_MAXTYPE; i++) { for (int i = 0; i < PAYLOAD_MAXTYPE; i++) {
m_ServerDLL[i] = nullptr; m_ServerDLL[i] = nullptr;
m_ServerBin[i] = nullptr; m_ServerBin[i] = nullptr;
@@ -684,6 +690,10 @@ CMy2015RemoteDlg::~CMy2015RemoteDlg()
m_pSearchBar->DestroyWindow(); m_pSearchBar->DestroyWindow();
SAFE_DELETE(m_pSearchBar); SAFE_DELETE(m_pSearchBar);
} }
if (m_pLogSearchBar) {
m_pLogSearchBar->DestroyWindow();
SAFE_DELETE(m_pLogSearchBar);
}
} }
// DLL 请求限流成员函数实现 (根据配置限制请求频率) // DLL 请求限流成员函数实现 (根据配置限制请求频率)
@@ -892,6 +902,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_MESSAGE(WM_INJECT_SHELLCODE, InjectShellcode) ON_MESSAGE(WM_INJECT_SHELLCODE, InjectShellcode)
ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen) ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen)
ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen) ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen)
ON_MESSAGE(WM_OPENCLIENTLOGDIALOG, OnOpenClientLogDialog)
ON_MESSAGE(WM_SHARE_CLIENT, ShareClient) ON_MESSAGE(WM_SHARE_CLIENT, ShareClient)
ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient) ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient)
ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient) ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient)
@@ -926,6 +937,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete) ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete)
ON_COMMAND(ID_MSGLOG_COPY, &CMy2015RemoteDlg::OnMsglogCopy) ON_COMMAND(ID_MSGLOG_COPY, &CMy2015RemoteDlg::OnMsglogCopy)
ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear) ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear)
ON_COMMAND(ID_MSGLOG_SEARCH, &CMy2015RemoteDlg::OnMsglogSearch)
ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch) ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch)
ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify) ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify)
ON_NOTIFY(NM_CUSTOMDRAW, IDC_ONLINE, &CMy2015RemoteDlg::OnNMCustomdrawOnline) ON_NOTIFY(NM_CUSTOMDRAW, IDC_ONLINE, &CMy2015RemoteDlg::OnNMCustomdrawOnline)
@@ -995,6 +1007,10 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_MESSAGE(WM_SPLITTER_RELEASED, &CMy2015RemoteDlg::OnSplitterReleased) ON_MESSAGE(WM_SPLITTER_RELEASED, &CMy2015RemoteDlg::OnSplitterReleased)
ON_COMMAND(ID_COPY_CLIENT_INFO, &CMy2015RemoteDlg::OnCopyClientInfo) ON_COMMAND(ID_COPY_CLIENT_INFO, &CMy2015RemoteDlg::OnCopyClientInfo)
ON_COMMAND(ID_ONLINE_ACTIVE_WND, &CMy2015RemoteDlg::OnOnlineActiveWnd) ON_COMMAND(ID_ONLINE_ACTIVE_WND, &CMy2015RemoteDlg::OnOnlineActiveWnd)
ON_COMMAND(ID_ONLINE_VIEW_WND, &CMy2015RemoteDlg::OnOnlineWindowManager)
ON_COMMAND(ID_ENABLE_DEV_DEBUG, &CMy2015RemoteDlg::OnEnableDevDebug)
ON_COMMAND(ID_ONLINE_CLIENT_LOG, &CMy2015RemoteDlg::OnOnlineClientLog)
ON_COMMAND(ID_ONLINE_FORBIDDEN, &CMy2015RemoteDlg::OnOnlineForbidden)
END_MESSAGE_MAP() END_MESSAGE_MAP()
@@ -1096,6 +1112,7 @@ VOID CMy2015RemoteDlg::CreateSolidMenu()
m_MainMenu.SetMenuItemBitmaps(ID_MASTER_TRAIL, MF_BYCOMMAND, &m_bmOnline[48], &m_bmOnline[48]); m_MainMenu.SetMenuItemBitmaps(ID_MASTER_TRAIL, MF_BYCOMMAND, &m_bmOnline[48], &m_bmOnline[48]);
m_MainMenu.SetMenuItemBitmaps(ID_TOOL_REQUEST_AUTH, MF_BYCOMMAND, &m_bmOnline[49], &m_bmOnline[49]); m_MainMenu.SetMenuItemBitmaps(ID_TOOL_REQUEST_AUTH, MF_BYCOMMAND, &m_bmOnline[49], &m_bmOnline[49]);
m_MainMenu.SetMenuItemBitmaps(ID_UNINSTALL_SOFTWARE, MF_BYCOMMAND, &m_bmOnline[57], &m_bmOnline[57]); m_MainMenu.SetMenuItemBitmaps(ID_UNINSTALL_SOFTWARE, MF_BYCOMMAND, &m_bmOnline[57], &m_bmOnline[57]);
m_MainMenu.SetMenuItemBitmaps(ID_ENABLE_DEV_DEBUG, MF_BYCOMMAND, &m_bmOnline[60], &m_bmOnline[60]);
// ============================================================ // ============================================================
// UIBranding: 根据编译时配置隐藏菜单项 // UIBranding: 根据编译时配置隐藏菜单项
@@ -1418,6 +1435,9 @@ VOID CMy2015RemoteDlg::InitControl()
m_nSplitPos = max(60, min(m_nSplitPos, 600)); m_nSplitPos = max(60, min(m_nSplitPos, 600));
m_SplitterBar.Create(this); m_SplitterBar.Create(this);
m_pLogSearchBar = new CLogSearchBar();
m_pLogSearchBar->Create(this, &m_CList_Message);
// 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg // 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg
// 之后统一 Apply避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。 // 之后统一 Apply避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。
} }
@@ -1462,8 +1482,12 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
capStr = ver.Mid(dashPos + 1); capStr = ver.Mid(dashPos + 1);
} }
const std::string& privUser = v[RES_USERNAME];
const std::string& privAdmin = v[RES_ISADMIN];
CString privilegeStr = privUser == "SYSTEM" ? "SYSTEM" : privAdmin == "1" ? "Admin" : "User";
CString data[ONLINELIST_MAX] = { strIP, strAddr, "", strPCName, strOS, strCPU, strVideo, strPing, CString data[ONLINELIST_MAX] = { strIP, strAddr, "", strPCName, strOS, strCPU, strVideo, strPing,
verDisplay, install, startTime, v[RES_CLIENT_TYPE].empty() ? "?" : v[RES_CLIENT_TYPE].c_str(), path, verDisplay, privilegeStr, install, startTime, v[RES_CLIENT_TYPE].empty() ? "?" : v[RES_CLIENT_TYPE].c_str(), path,
v[RES_CLIENT_PUBIP].empty() ? strIP : v[RES_CLIENT_PUBIP].c_str(), startTime, capStr, v[RES_CLIENT_PUBIP].empty() ? strIP : v[RES_CLIENT_PUBIP].c_str(), startTime, capStr,
}; };
// 优先采用客户端自报的 ID新客户端用 V2 算法 = MachineGuid + 归一化路径, // 优先采用客户端自报的 ID新客户端用 V2 算法 = MachineGuid + 归一化路径,
@@ -1509,6 +1533,11 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
ContextObject->SetID(id); ContextObject->SetID(id);
ContextObject->SetGroup(groupName); ContextObject->SetGroup(groupName);
m_ClientMap->SaveClientMapData(ContextObject); m_ClientMap->SaveClientMapData(ContextObject);
int status = m_ClientMap->GetClientMapInteger(id, MAP_AUTH);
if (status == AUTH_FORBIDDEN) {
BYTE buf[32] = { COMMAND_FORBIDDEN };
ContextObject->Send2Client(buf, sizeof(buf));
}
EnterCriticalSection(&m_cs); EnterCriticalSection(&m_cs);
@@ -1650,6 +1679,23 @@ LRESULT CMy2015RemoteDlg::OnShowMessage(WPARAM wParam, LPARAM lParam)
// 消息日志最大条数,达到上限时删除最旧的记录 // 消息日志最大条数,达到上限时删除最旧的记录
#define MAX_MESSAGE_COUNT 1000 #define MAX_MESSAGE_COUNT 1000
namespace {
bool MsgHasKW(const CString& s, const CString* kw, int n) {
for (int i = 0; i < n; i++)
if (s.Find(kw[i]) >= 0) return true;
return false;
}
int MsgColorLevel(const CString& type, const CString& msg) {
const CString redKW[] = { _L("严禁"), _L("禁止") };
const CString orangeKW[] = { _L("失败"), _L("警告") };
const CString greenKW[] = { _L("成功"), _L("") };
if (MsgHasKW(type, redKW, _countof(redKW)) || MsgHasKW(msg, redKW, _countof(redKW))) return 1;
if (MsgHasKW(type, orangeKW, _countof(orangeKW)) || MsgHasKW(msg, orangeKW, _countof(orangeKW))) return 2;
if (MsgHasKW(msg, greenKW, _countof(greenKW))) return 3;
return 0;
}
}
VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg) VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
{ {
AUTO_TICK(200, ""); AUTO_TICK(200, "");
@@ -1662,9 +1708,10 @@ VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
m_CList_Message.DeleteItem(count - 1); m_CList_Message.DeleteItem(count - 1);
} }
m_CList_Message.InsertItem(0, strType); //向控件中设置数据 m_CList_Message.InsertItem(0, strType);
m_CList_Message.SetItemText(0,1,strTime); m_CList_Message.SetItemText(0, 1, strTime);
m_CList_Message.SetItemText(0,2,strMsg); m_CList_Message.SetItemText(0, 2, strMsg);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strType, strMsg));
CString strStatusMsg; CString strStatusMsg;
@@ -1739,9 +1786,12 @@ LRESULT CMy2015RemoteDlg::OnShowErrMessage(WPARAM wParam, LPARAM lParam)
m_CList_Message.DeleteItem(count - 1); m_CList_Message.DeleteItem(count - 1);
} }
m_CList_Message.InsertItem(0, title ? _L(*title) : _TR("操作错误")); CString strTitle = title ? _L(*title) : _TR("操作错误");
CString strText = text ? _L(*text) : _TR("内部错误");
m_CList_Message.InsertItem(0, strTitle);
m_CList_Message.SetItemText(0, 1, strTime); m_CList_Message.SetItemText(0, 1, strTime);
m_CList_Message.SetItemText(0, 2, text ? _L(*text) : _TR("内部错误")); m_CList_Message.SetItemText(0, 2, strText);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strTitle, strText));
if(title)delete title; if(title)delete title;
if(text)delete text; if(text)delete text;
@@ -3205,7 +3255,8 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
lastType = nType; lastType = nType;
BOOL hideLog = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1; BOOL hideLog = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1;
const int SPLITTER_H = 6; const int SPLITTER_H = 6;
const int LOG_SEARCH_H = 26; // 日志搜索条高度
// 日志区有效高度 = m_nSplitPos不含分割条分割条紧贴日志区上方 // 日志区有效高度 = m_nSplitPos不含分割条分割条紧贴日志区上方
int splitPos = hideLog ? 0 : m_nSplitPos; int splitPos = hideLog ? 0 : m_nSplitPos;
@@ -3232,6 +3283,22 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
} }
LeaveCriticalSection(&m_cs); LeaveCriticalSection(&m_cs);
if (m_pLogSearchBar && m_pLogSearchBar->GetSafeHwnd()) {
if (hideLog || !m_bLogSearchVisible) {
m_pLogSearchBar->ShowWindow(SW_HIDE);
} else {
m_pLogSearchBar->ShowWindow(SW_SHOW);
// 右对齐,最多 380px左侧留出 180px 给分组信息
int barW = min(260, max(60, cx - 182));
int barX = cx - 2 - barW;
// 叠在在线列表底部右角:紧贴分割条上方,向上偏移一个条高
int barY = cy - 20 - splitPos - SPLITTER_H - LOG_SEARCH_H + 5;
m_pLogSearchBar->MoveWindow(barX, barY, barW, LOG_SEARCH_H);
// 保证搜索条在列表控件上层BringWindowToTop 仅影响 Z-order不影响焦点
m_pLogSearchBar->BringWindowToTop();
}
}
if (m_SplitterBar.m_hWnd != NULL) { if (m_SplitterBar.m_hWnd != NULL) {
if (hideLog) { if (hideLog) {
m_SplitterBar.ShowWindow(SW_HIDE); m_SplitterBar.ShowWindow(SW_HIDE);
@@ -4040,6 +4107,8 @@ void CMy2015RemoteDlg::OnNMRClickOnline(NMHDR *pNMHDR, LRESULT *pResult)
Menu.SetMenuItemBitmaps(ID_CANCEL_SHARE, MF_BYCOMMAND, &m_bmOnline[50], &m_bmOnline[50]); Menu.SetMenuItemBitmaps(ID_CANCEL_SHARE, MF_BYCOMMAND, &m_bmOnline[50], &m_bmOnline[50]);
Menu.SetMenuItemBitmaps(ID_COPY_CLIENT_INFO, MF_BYCOMMAND, &m_bmOnline[58], &m_bmOnline[58]); Menu.SetMenuItemBitmaps(ID_COPY_CLIENT_INFO, MF_BYCOMMAND, &m_bmOnline[58], &m_bmOnline[58]);
Menu.SetMenuItemBitmaps(ID_ONLINE_ACTIVE_WND, MF_BYCOMMAND, &m_bmOnline[59], &m_bmOnline[59]); Menu.SetMenuItemBitmaps(ID_ONLINE_ACTIVE_WND, MF_BYCOMMAND, &m_bmOnline[59], &m_bmOnline[59]);
Menu.SetMenuItemBitmaps(ID_ONLINE_CLIENT_LOG, MF_BYCOMMAND, &m_bmOnline[61], &m_bmOnline[61]);
Menu.SetMenuItemBitmaps(ID_ONLINE_FORBIDDEN, MF_BYCOMMAND, &m_bmOnline[62], &m_bmOnline[62]);
Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权")); Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权"));
@@ -5225,6 +5294,20 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
// 【L】主机上下线和授权 // 【L】主机上下线和授权
// 【x】对话框相关功能 // 【x】对话框相关功能
switch (cmd) { switch (cmd) {
case TOKEN_REPORT_LOG: {
std::string logText((char*)(szBuffer + 1), len > 1 ? len - 1 : 0);
if (!ContextObject->hDlg) {
// 对话框尚未打开:用 SendMessage 同步打开并显示初始全量日志
SendMessage(WM_OPENCLIENTLOGDIALOG,
(WPARAM)new std::string(logText),
(LPARAM)ContextObject);
} else {
// 对话框已在PostMessage 追加增量日志
static_cast<CClientLog*>(ContextObject->hDlg)->PostMessage(
WM_REPORT_CLIENT_LOG, 0, (LPARAM)new std::string(logText));
}
break;
}
case TOKEN_CLIENT_MSG: { case TOKEN_CLIENT_MSG: {
ClientMsg *msg =(ClientMsg*)ContextObject->InDeCompressedBuffer.GetBuffer(0); ClientMsg *msg =(ClientMsg*)ContextObject->InDeCompressedBuffer.GetBuffer(0);
PostMessageA(WM_SHOWERRORMSG, (WPARAM)new CString(_L(msg->text)), (LPARAM)new CString(_L(msg->title))); PostMessageA(WM_SHOWERRORMSG, (WPARAM)new CString(_L(msg->text)), (LPARAM)new CString(_L(msg->title)));
@@ -9160,10 +9243,40 @@ void CMy2015RemoteDlg::OnNMCustomdrawMessage(NMHDR* pNMHDR, LRESULT* pResult)
case CDDS_ITEMPREPAINT: { case CDDS_ITEMPREPAINT: {
int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec); int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec);
int nLastRow = m_CList_Message.GetItemCount() - 1;
if (nRow == nLastRow && nLastRow >= 0) { // 搜索命中行深蓝高亮(不依赖 CDIS_SELECTED直接读搜索栏当前命中行号
pLVCD->clrText = RGB(255, 0, 0); if (m_pLogSearchBar) {
int nHit = m_pLogSearchBar->GetHighlightedRow();
if (nHit >= 0 && nRow == nHit) {
pLVCD->clrTextBk = RGB(0, 80, 180);
pLVCD->clrText = RGB(255, 255, 255);
*pResult = CDRF_NEWFONT;
return;
}
} }
// 关键字着色:必须同时设 clrText + clrTextBk否则系统预填的蓝色会泄漏
int colorLevel = (int)m_CList_Message.GetItemData(nRow);
COLORREF bkDefault = ::GetSysColor(COLOR_WINDOW);
if (colorLevel == 1) {
pLVCD->clrText = RGB(220, 0, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
if (colorLevel == 2) {
pLVCD->clrText = RGB(200, 100, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
if (colorLevel == 3) {
pLVCD->clrText = RGB(0, 140, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
break;
} }
} }
} }
@@ -9177,6 +9290,9 @@ void CMy2015RemoteDlg::OnRClickMessage(NMHDR* pNMHDR, LRESULT* pResult)
menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中")); menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_COPY, _TR("复制选中")); menu.AppendMenu(MF_STRING, ID_MSGLOG_COPY, _TR("复制选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志")); menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志"));
menu.AppendMenu(MF_SEPARATOR);
menu.AppendMenu(MF_STRING, ID_MSGLOG_SEARCH,
m_bLogSearchVisible ? _TR("隐藏搜索") : _TR("搜索日志"));
// 没有选中项时禁用"删除选中" // 没有选中项时禁用"删除选中"
if (m_CList_Message.GetSelectedCount() == 0) { if (m_CList_Message.GetSelectedCount() == 0) {
@@ -9201,7 +9317,7 @@ void CMy2015RemoteDlg::OnMsglogDelete()
while (pos) { while (pos) {
selected.push_back(m_CList_Message.GetNextSelectedItem(pos)); selected.push_back(m_CList_Message.GetNextSelectedItem(pos));
} }
// 倒序删除 // 倒序删除,避免索引变化
for (auto it = selected.rbegin(); it != selected.rend(); ++it) { for (auto it = selected.rbegin(); it != selected.rend(); ++it) {
m_CList_Message.DeleteItem(*it); m_CList_Message.DeleteItem(*it);
} }
@@ -9244,6 +9360,14 @@ void CMy2015RemoteDlg::OnMsglogClear()
m_CList_Message.DeleteAllItems(); m_CList_Message.DeleteAllItems();
} }
void CMy2015RemoteDlg::OnMsglogSearch()
{
m_bLogSearchVisible = !m_bLogSearchVisible;
// 复用 OnSize 里的布局逻辑
CRect rc;
GetClientRect(&rc);
OnSize(SIZE_RESTORED, rc.Width(), rc.Height());
}
void CMy2015RemoteDlg::OnOnlineAddWatch() void CMy2015RemoteDlg::OnOnlineAddWatch()
{ {
@@ -10483,41 +10607,50 @@ void CMy2015RemoteDlg::OnParamEnableLog()
void CMy2015RemoteDlg::OnParamPrivacyWallpaper() void CMy2015RemoteDlg::OnParamPrivacyWallpaper()
{ {
auto SubMenu = m_MainMenu.GetSubMenu(2);
// 已设置壁纸:再次点击则清空
if (!m_PrivateScreenWallpaper.IsEmpty()) {
m_PrivateScreenWallpaper.Empty();
THIS_CFG.SetStr("settings", "Wallpaper", "");
if (SubMenu)
SubMenu->CheckMenuItem(ID_PARAM_PRIVACY_WALLPAPER, MF_UNCHECKED);
return;
}
// 未设置壁纸:弹出文件对话框
CFileDialog dlg(TRUE, _T("bmp"), NULL, CFileDialog dlg(TRUE, _T("bmp"), NULL,
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
_T("BMP Files (*.bmp)|*.bmp|All Files (*.*)|*.*||"), this); _T("BMP Files (*.bmp)|*.bmp|All Files (*.*)|*.*||"), this);
if (dlg.DoModal() == IDOK) { if (dlg.DoModal() != IDOK)
CString path = dlg.GetPathName(); return;
// 验证文件是否可以读取 CString path = dlg.GetPathName();
CFile file;
if (!file.Open(path, CFile::modeRead | CFile::typeBinary)) {
MessageBox(_TR("无法读取指定的BMP文件请检查文件是否存在或权限是否正确。"),
_TR("错误"), MB_ICONERROR);
return;
}
// 验证是否是有效的BMP文件 CFile file;
BITMAPFILEHEADER bmfh; if (!file.Open(path, CFile::modeRead | CFile::typeBinary)) {
if (file.Read(&bmfh, sizeof(bmfh)) != sizeof(bmfh) || bmfh.bfType != 0x4D42) { MessageBox(_TR("无法读取指定的BMP文件请检查文件是否存在或权限是否正确。"),
file.Close(); _TR("错误"), MB_ICONERROR);
MessageBox(_TR("指定的文件不是有效的BMP格式。"), return;
_TR("错误"), MB_ICONERROR);
return;
}
file.Close();
m_PrivateScreenWallpaper = path;
THIS_CFG.SetStr("settings", "Wallpaper", path.GetString());
auto SubMenu = m_MainMenu.GetSubMenu(2); // 工具菜单
if (SubMenu) {
SubMenu->CheckMenuItem(ID_PARAM_PRIVACY_WALLPAPER, MF_CHECKED);
}
CString msg;
msg.Format(_TR("隐私屏幕壁纸已设置为:\n%s"), path);
MessageBox(msg + CString("\n") + _L("如需恢复默认值,请从磁盘移除该文件。"), _TR("设置成功"), MB_ICONINFORMATION);
} }
BITMAPFILEHEADER bmfh;
if (file.Read(&bmfh, sizeof(bmfh)) != sizeof(bmfh) || bmfh.bfType != 0x4D42) {
file.Close();
MessageBox(_TR("指定的文件不是有效的BMP格式。"),
_TR("错误"), MB_ICONERROR);
return;
}
file.Close();
m_PrivateScreenWallpaper = path;
THIS_CFG.SetStr("settings", "Wallpaper", path.GetString());
if (SubMenu)
SubMenu->CheckMenuItem(ID_PARAM_PRIVACY_WALLPAPER, MF_CHECKED);
CString msg;
msg.Format(_TR("隐私屏幕壁纸已设置为:\n%s"), path);
MessageBox(msg, _TR("设置成功"), MB_ICONINFORMATION);
} }
void CMy2015RemoteDlg::OnParamFileV2() void CMy2015RemoteDlg::OnParamFileV2()
@@ -11452,3 +11585,101 @@ void CMy2015RemoteDlg::OnOnlineActiveWnd()
if (!ctx) return; if (!ctx) return;
PostCaptureForegroundWindow(ctx); PostCaptureForegroundWindow(ctx);
} }
bool SetUserEnvironmentVariable(const char* name, const char* value)
{
HKEY hKey = NULL;
LONG ret = RegOpenKeyExA(
HKEY_CURRENT_USER,
"Environment",
0,
KEY_SET_VALUE,
&hKey);
if (ret != ERROR_SUCCESS)
{
return false;
}
ret = RegSetValueExA(
hKey,
name,
0,
REG_SZ, // 或 REG_EXPAND_SZ
(const BYTE*)value,
(DWORD)(strlen(value) + 1));
RegCloseKey(hKey);
if (ret != ERROR_SUCCESS)
{
return false;
}
// 通知系统环境变量已更新
SendMessageTimeoutA(
HWND_BROADCAST,
WM_SETTINGCHANGE,
0,
(LPARAM)"Environment",
SMTO_ABORTIFHUNG,
5000,
NULL);
return true;
}
void CMy2015RemoteDlg::OnEnableDevDebug()
{
const char* debug = getenv("YAMA_DBG");
if (!debug || strlen(debug) == 0) {
MessageBoxL("您必须正确设置 YAMA_DBG 才能调试此程序!\r\n请联系管理员请求授权。",
"提示", MB_ICONINFORMATION);
CInputDialog dlg(this);
dlg.Init(_L("设置环境变量"), "YAMA_DBG:");
if (dlg.DoModal() != IDOK || dlg.m_str.IsEmpty()) return;
SetUserEnvironmentVariable("YAMA_DBG", dlg.m_str) ?
MessageBoxL("设置成功! 请重启程序和调试器。", "提示", MB_ICONINFORMATION) :
MessageBoxL("设置失败! 通常是权限不足, 请手动在系统环境变量设置。", "提示", MB_ICONINFORMATION);
}
else {
MessageBoxA(_TR("您已设置 YAMA_DBG, 请确保环境变量正确无误!\r\n")+
"YAMA_DBG=" + CString(debug), _L("提示"), MB_ICONINFORMATION);
}
}
void CMy2015RemoteDlg::OnOnlineClientLog()
{
BYTE cmd = COMMAND_QUERY_LOG;
SendSelectedCommand(&cmd, sizeof(BYTE));
}
LRESULT CMy2015RemoteDlg::OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam)
{
std::string* pInitLog = reinterpret_cast<std::string*>(wParam);
std::string initLog = pInitLog ? *pInitLog : "";
delete pInitLog;
OpenDialog<CClientLog, IDD_DIALOG_CLIENT_LOG>(0, lParam);
CONTEXT_OBJECT* ctx = reinterpret_cast<CONTEXT_OBJECT*>(lParam);
if (ctx && ctx->hDlg && !initLog.empty())
static_cast<CClientLog*>(ctx->hDlg)->AppendLog(initLog);
return 0;
}
void forbiddenClient(context* ctx, void* user) {
_ClientList* m_ClientMap = (_ClientList*)user;
m_ClientMap->SetClientMapInteger(ctx->GetClientID(), MAP_AUTH, AUTH_FORBIDDEN);
}
void CMy2015RemoteDlg::OnOnlineForbidden()
{
if (IDYES != MessageBoxL(_T("确定封禁选定的被控程序吗?"), _T("提示"), MB_ICONQUESTION | MB_YESNO))
return;
BYTE buf[32] = { COMMAND_FORBIDDEN };
SendSelectedCommand(buf, sizeof(buf), forbiddenClient, m_ClientMap);
}

View File

@@ -75,6 +75,7 @@ class CSplashDlg; // 前向声明
class CClientListDlg; class CClientListDlg;
class CLicenseDlg; class CLicenseDlg;
class CSearchBarDlg; class CSearchBarDlg;
class CLogSearchBar;
#include "pwd_gen.h" #include "pwd_gen.h"
@@ -140,7 +141,9 @@ public:
_ClientList *m_ClientMap = nullptr; _ClientList *m_ClientMap = nullptr;
CClientListDlg* m_pClientListDlg = nullptr; CClientListDlg* m_pClientListDlg = nullptr;
CLicenseDlg* m_pLicenseDlg = nullptr; CLicenseDlg* m_pLicenseDlg = nullptr;
CSearchBarDlg* m_pSearchBar = nullptr; // 搜索工具栏 CSearchBarDlg* m_pSearchBar = nullptr;
CLogSearchBar* m_pLogSearchBar = nullptr;
bool m_bLogSearchVisible = false; // 日志搜索条是否显示(右键菜单切换)
BOOL m_bEnableFileV2 = FALSE; // V2 文件传输开关 BOOL m_bEnableFileV2 = FALSE; // V2 文件传输开关
// 构造 // 构造
@@ -382,7 +385,7 @@ public:
bool IsDllRequestLimited(const std::string& ip); bool IsDllRequestLimited(const std::string& ip);
void RecordDllRequest(const std::string& ip); void RecordDllRequest(const std::string& ip);
CMenu m_MainMenu; CMenu m_MainMenu;
CBitmap m_bmOnline[60]; CBitmap m_bmOnline[63];
uint64_t m_superID; uint64_t m_superID;
std::map<HWND, CDialogBase *> m_RemoteWnds; std::map<HWND, CDialogBase *> m_RemoteWnds;
FileTransformCmd m_CmdList; FileTransformCmd m_CmdList;
@@ -560,6 +563,7 @@ public:
afx_msg void OnMsglogDelete(); afx_msg void OnMsglogDelete();
afx_msg void OnMsglogCopy(); afx_msg void OnMsglogCopy();
afx_msg void OnMsglogClear(); afx_msg void OnMsglogClear();
afx_msg void OnMsglogSearch();
afx_msg void OnOnlineAddWatch(); afx_msg void OnOnlineAddWatch();
afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnOnlineRunAsAdmin(); afx_msg void OnOnlineRunAsAdmin();
@@ -636,4 +640,8 @@ public:
afx_msg LRESULT OnSplitterReleased(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnSplitterReleased(WPARAM wParam, LPARAM lParam);
afx_msg void OnCopyClientInfo(); afx_msg void OnCopyClientInfo();
afx_msg void OnOnlineActiveWnd(); afx_msg void OnOnlineActiveWnd();
afx_msg void OnEnableDevDebug();
afx_msg void OnOnlineClientLog();
afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam);
afx_msg void OnOnlineForbidden();
}; };

View File

@@ -290,6 +290,8 @@
<ClInclude Include="Buffer.h" /> <ClInclude Include="Buffer.h" />
<ClInclude Include="BuildDlg.h" /> <ClInclude Include="BuildDlg.h" />
<ClInclude Include="CClientListDlg.h" /> <ClInclude Include="CClientListDlg.h" />
<ClInclude Include="CClientLog.h" />
<ClInclude Include="LogSearchBar.h" />
<ClInclude Include="CDlgFileSend.h" /> <ClInclude Include="CDlgFileSend.h" />
<ClInclude Include="CDrawingBoard.h" /> <ClInclude Include="CDrawingBoard.h" />
<ClInclude Include="CGridDialog.h" /> <ClInclude Include="CGridDialog.h" />
@@ -371,6 +373,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\..\client\Audio.cpp" /> <ClCompile Include="..\..\client\Audio.cpp" />
<ClCompile Include="CClientLog.cpp" />
<ClCompile Include="LogSearchBar.cpp" />
<ClCompile Include="msvc_compat.c"> <ClCompile Include="msvc_compat.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
@@ -507,8 +511,11 @@
<Image Include="res\Bitmap\authorize.bmp" /> <Image Include="res\Bitmap\authorize.bmp" />
<Image Include="res\Bitmap\Backup.bmp" /> <Image Include="res\Bitmap\Backup.bmp" />
<Image Include="res\Bitmap\CancelShare.bmp" /> <Image Include="res\Bitmap\CancelShare.bmp" />
<Image Include="res\Bitmap\ClientLog.bmp" />
<Image Include="res\Bitmap\Forbidden.bmp" />
<Image Include="res\bitmap\compress.bmp" /> <Image Include="res\bitmap\compress.bmp" />
<Image Include="res\Bitmap\Copy.bmp" /> <Image Include="res\Bitmap\Copy.bmp" />
<Image Include="res\Bitmap\Debug.bmp" />
<Image Include="res\Bitmap\delete.bmp" /> <Image Include="res\Bitmap\delete.bmp" />
<Image Include="res\Bitmap\DxgiDesktop.bmp" /> <Image Include="res\Bitmap\DxgiDesktop.bmp" />
<Image Include="res\Bitmap\EditGroup.bmp" /> <Image Include="res\Bitmap\EditGroup.bmp" />
@@ -560,6 +567,7 @@
<Image Include="res\Bitmap\Wallet.bmp" /> <Image Include="res\Bitmap\Wallet.bmp" />
<Image Include="res\Bitmap\WebDesktop.bmp" /> <Image Include="res\Bitmap\WebDesktop.bmp" />
<Image Include="res\chat.ico" /> <Image Include="res\chat.ico" />
<Image Include="res\ClientLog.ico" />
<Image Include="res\cmdshell.ico" /> <Image Include="res\cmdshell.ico" />
<Image Include="res\decrypt.ico" /> <Image Include="res\decrypt.ico" />
<Image Include="res\DrawingBoard.ico" /> <Image Include="res\DrawingBoard.ico" />

View File

@@ -84,6 +84,7 @@
<ClCompile Include="PluginSettingsDlg.cpp" /> <ClCompile Include="PluginSettingsDlg.cpp" />
<ClCompile Include="PreviewTipWnd.cpp" /> <ClCompile Include="PreviewTipWnd.cpp" />
<ClCompile Include="TriggerSettingsDlg.cpp" /> <ClCompile Include="TriggerSettingsDlg.cpp" />
<ClCompile Include="CClientLog.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\..\client\Audio.h" /> <ClInclude Include="..\..\client\Audio.h" />
@@ -188,6 +189,7 @@
<ClInclude Include="PluginSettingsDlg.h" /> <ClInclude Include="PluginSettingsDlg.h" />
<ClInclude Include="PreviewTipWnd.h" /> <ClInclude Include="PreviewTipWnd.h" />
<ClInclude Include="TriggerSettingsDlg.h" /> <ClInclude Include="TriggerSettingsDlg.h" />
<ClInclude Include="CClientLog.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="2015Remote.rc" /> <ResourceCompile Include="2015Remote.rc" />
@@ -327,5 +329,8 @@
<Image Include="res\bitmap\uninstall.bmp" /> <Image Include="res\bitmap\uninstall.bmp" />
<Image Include="res\Bitmap\Copy.bmp" /> <Image Include="res\Bitmap\Copy.bmp" />
<Image Include="res\bitmap\ActiveWnd.bmp" /> <Image Include="res\bitmap\ActiveWnd.bmp" />
<Image Include="res\Bitmap\Debug.bmp" />
<Image Include="res\ClientLog.ico" />
<Image Include="res\Bitmap\ClientLog.bmp" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -30,6 +30,8 @@ enum Index {
IndexLinuxGhost, IndexLinuxGhost,
IndexMacGhost, IndexMacGhost,
IndexAndroidGhost, IndexAndroidGhost,
IndexGhostSystem,
IndexTestRunSystem,
OTHER_ITEM OTHER_ITEM
}; };
@@ -493,6 +495,22 @@ void CBuildDlg::OnBnClickedOk()
szBuffer = ReadResource(IDR_ANDROID_GHOST, dwFileSize, ResFileName::GHOST_ANDROID); szBuffer = ReadResource(IDR_ANDROID_GHOST, dwFileSize, ResFileName::GHOST_ANDROID);
break; break;
} }
case IndexGhostSystem:
file = "ghost.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Windows Ghost" : m_sInstallDir);
typ = CLIENT_TYPE_ONE;
startup = Startup_GhostSystem;
szBuffer = ReadResource(is64bit ? IDR_GHOST_X64 : IDR_GHOST_X86, dwFileSize,
is64bit ? ResFileName::GHOST_X64 : ResFileName::GHOST_X86);
break;
case IndexTestRunSystem:
file = "TestRun.exe";
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "Client Demo" : m_sInstallDir);
typ = CLIENT_TYPE_MEMDLL;
startup = Startup_TestRunSystem;
szBuffer = ReadResource(is64bit ? IDR_TESTRUN_X64 : IDR_TESTRUN_X86, dwFileSize,
is64bit ? ResFileName::TESTRUN_X64 : ResFileName::TESTRUN_X86);
break;
case OTHER_ITEM: { case OTHER_ITEM: {
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "YamaDll" : m_sInstallDir); targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "YamaDll" : m_sInstallDir);
m_OtherItem.GetWindowTextA(file); m_OtherItem.GetWindowTextA(file);
@@ -801,6 +819,8 @@ BOOL CBuildDlg::OnInitDialog()
m_ComboExe.InsertStringL(IndexLinuxGhost, "ghost - Linux x64"); m_ComboExe.InsertStringL(IndexLinuxGhost, "ghost - Linux x64");
m_ComboExe.InsertStringL(IndexMacGhost, "ghost - Apple MacOS"); m_ComboExe.InsertStringL(IndexMacGhost, "ghost - Apple MacOS");
m_ComboExe.InsertStringL(IndexAndroidGhost, "ghost - Google Android"); m_ComboExe.InsertStringL(IndexAndroidGhost, "ghost - Google Android");
m_ComboExe.InsertStringL(IndexGhostSystem, "ghost - SYSTEM");
m_ComboExe.InsertStringL(IndexTestRunSystem, "TestRun - SYSTEM");
m_ComboExe.InsertStringL(OTHER_ITEM, CString("选择文件")); m_ComboExe.InsertStringL(OTHER_ITEM, CString("选择文件"));
m_ComboExe.SetCurSel(IndexTestRun_MemDLL); m_ComboExe.SetCurSel(IndexTestRun_MemDLL);

View File

@@ -288,7 +288,7 @@ void CClientListDlg::DisplayClients()
CString strLevel; CString strLevel;
strLevel.FormatL(_T("%d"), val.Level); strLevel.FormatL(_T("%d"), val.Level);
CString strAuth = val.Authorized ? _T("Y") : _T("N"); CString strAuth = (val.Authorized == AUTHED_BY_SUPER || val.Authorized == AUTHED_BY_ADMIN)? _T("Y") : _T("N");
nItem = m_ClientList.InsertItem(nRow, strNo); nItem = m_ClientList.InsertItem(nRow, strNo);
m_ClientList.SetItemText(nItem, COL_ID, strID); m_ClientList.SetItemText(nItem, COL_ID, strID);
@@ -340,7 +340,7 @@ void CClientListDlg::DisplayClients()
CString strLevel; CString strLevel;
strLevel.FormatL(_T("%d"), val.Level); strLevel.FormatL(_T("%d"), val.Level);
CString strAuth = val.Authorized ? _T("Y") : _T("N"); CString strAuth = (val.Authorized == AUTHED_BY_SUPER || val.Authorized == AUTHED_BY_ADMIN) ? _T("Y") : _T("N");
nItem = m_ClientList.InsertItem(nRow, strSubNo); nItem = m_ClientList.InsertItem(nRow, strSubNo);
m_ClientList.SetItemText(nItem, COL_ID, strID); m_ClientList.SetItemText(nItem, COL_ID, strID);

View File

@@ -0,0 +1,106 @@
// CClientLog.cpp: 实现文件
#include "stdafx.h"
#include "afxdialogex.h"
#include "CClientLog.h"
#include "resource.h"
#include "../../common/commands.h"
IMPLEMENT_DYNAMIC(CClientLog, CDialogEx)
CClientLog::CClientLog(CWnd* pParent, Server* pServer, CONTEXT_OBJECT* pContext)
: CDialogBase(IDD_DIALOG_CLIENT_LOG, pParent, pServer, pContext, 0)
{
}
CClientLog::~CClientLog()
{
}
void CClientLog::DoDataExchange(CDataExchange* pDX)
{
CDialogBase::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_LOG, m_editLog);
}
BEGIN_MESSAGE_MAP(CClientLog, CDialogBase)
ON_WM_DESTROY()
ON_WM_SIZE()
ON_MESSAGE(WM_REPORT_CLIENT_LOG, OnReportLog)
END_MESSAGE_MAP()
BOOL CClientLog::OnInitDialog()
{
CDialogBase::OnInitDialog();
SetWindowText(_TR("客户端日志") + " - " + m_IPAddress);
HICON hIcon = AfxGetApp()->LoadIcon(IDI_CLIENTLOG);
SetIcon(hIcon, TRUE);
SetIcon(hIcon, FALSE);
CFont* pFont = new CFont();
pFont->CreatePointFont(85, _T("Consolas"));
m_editLog.SetFont(pFont);
return TRUE;
}
void CClientLog::OnSize(UINT nType, int cx, int cy)
{
CDialogBase::OnSize(nType, cx, cy);
if (m_editLog.GetSafeHwnd() && cx > 0 && cy > 0)
m_editLog.MoveWindow(7, 7, cx - 14, cy - 14);
}
void CClientLog::OnDestroy()
{
if (m_ContextObject && m_bConnected) {
BYTE cmd = COMMAND_BYE;
m_ContextObject->Send2Client(&cmd, 1);
}
CDialogBase::OnDestroy();
}
void CClientLog::AppendLog(const std::string& text)
{
if (text.empty()) return;
// \n → \r\nWindows 编辑框换行需要 \r\n
std::string norm;
norm.reserve(text.size() + 64);
for (size_t i = 0; i < text.size(); ++i) {
if (text[i] == '\n' && (i == 0 || text[i - 1] != '\r'))
norm += '\r';
norm += text[i];
}
// 超过 512KB 时截断旧内容
int existing = m_editLog.GetWindowTextLength();
if (existing > 512 * 1024) {
CString cur;
m_editLog.GetWindowText(cur);
m_editLog.SetWindowText(cur.Mid(cur.GetLength() - 256 * 1024));
}
// 追加到末尾并滚动
CString add = CA2T(norm.c_str(), CP_UTF8);
m_editLog.SetSel(-1, -1);
m_editLog.ReplaceSel(add);
m_editLog.SendMessage(EM_SCROLLCARET);
}
void CClientLog::OnReceiveComplete()
{
auto& buf = m_ContextObject->InDeCompressedBuffer;
if (buf.GetBYTE(0) != TOKEN_REPORT_LOG)
return;
unsigned len = buf.GetBufferLen();
auto* pText = new std::string((char*)(buf.GetBuffer() + 1), len > 1 ? len - 1 : 0);
if (!PostMessage(WM_REPORT_CLIENT_LOG, 0, (LPARAM)pText))
delete pText;
}
LRESULT CClientLog::OnReportLog(WPARAM /*wParam*/, LPARAM lParam)
{
std::string* pText = reinterpret_cast<std::string*>(lParam);
if (pText) {
AppendLog(*pText);
delete pText;
}
return 0;
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "afxcmn.h"
#include "IOCPServer.h"
class CClientLog : public CDialogBase
{
DECLARE_DYNAMIC(CClientLog)
public:
CClientLog(CWnd* pParent, Server* pServer, CONTEXT_OBJECT* pContext);
virtual ~CClientLog();
virtual void OnReceiveComplete();
void AppendLog(const std::string& text);
protected:
virtual BOOL OnInitDialog();
virtual void DoDataExchange(CDataExchange* pDX);
afx_msg void OnDestroy();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg LRESULT OnReportLog(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
private:
CEdit m_editLog;
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_DIALOG_CLIENT_LOG };
#endif
};

View File

@@ -0,0 +1,229 @@
// LogSearchBar.cpp - 日志列表搜索条实现
#include "stdafx.h"
#include "LogSearchBar.h"
#include "LangManager.h"
BEGIN_MESSAGE_MAP(CLogSearchBar, CWnd)
ON_WM_CREATE()
ON_WM_SIZE()
ON_WM_ERASEBKGND()
ON_WM_TIMER()
ON_EN_CHANGE(IDC_LOG_SEARCH_EDIT, OnEnChangeSearch)
ON_BN_CLICKED(IDC_LOG_SEARCH_PREV, OnBnClickedPrev)
ON_BN_CLICKED(IDC_LOG_SEARCH_NEXT, OnBnClickedNext)
ON_BN_CLICKED(IDC_LOG_SEARCH_CLEAR, OnBnClickedClear)
END_MESSAGE_MAP()
CLogSearchBar::CLogSearchBar()
{
}
CLogSearchBar::~CLogSearchBar()
{
}
BOOL CLogSearchBar::Create(CWnd* pParent, CListCtrl* pList)
{
m_pList = pList;
LPCTSTR cls = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
::LoadCursor(NULL, IDC_ARROW));
return CWnd::Create(cls, _T(""), WS_CHILD | WS_CLIPCHILDREN,
CRect(0, 0, 1, 1), pParent, 0);
}
int CLogSearchBar::OnCreate(LPCREATESTRUCT lpcs)
{
if (CWnd::OnCreate(lpcs) == -1) return -1;
// 搜索输入框
m_editSearch.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_EDIT);
m_editSearch.SetCueBanner(L"搜索日志...", TRUE);
// 清除按钮
m_btnClear.Create(_T("X"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_CLEAR);
// 上一个 / 下一个按钮(系统样式,与浅色背景融合)
m_btnPrev.Create(_T(""), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_PREV);
m_btnNext.Create(_T(""), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_NEXT);
// 结果计数
m_staticCount.Create(_T(""), WS_CHILD | WS_VISIBLE | SS_CENTER | SS_CENTERIMAGE,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_COUNT);
// 统一字体:跟父窗口(主对话框)保持一致
CFont* pFont = GetParent()->GetFont();
if (pFont) {
m_staticCount.SetFont(pFont);
m_editSearch.SetFont(pFont);
m_btnClear.SetFont(pFont);
m_btnPrev.SetFont(pFont);
m_btnNext.SetFont(pFont);
}
return 0;
}
void CLogSearchBar::LayoutControls(int cx, int cy)
{
if (!m_editSearch.GetSafeHwnd()) return;
const int m = 2;
const int h = cy - 2 * m;
if (h <= 0) return;
const int btnW = h;
const int cntW = 62;
// 搜索框填满剩余空间barW 由外部控制总宽,这里保证控件铺满)
const int editW = max(20, cx - 2 * m - 3 * btnW - 2 * m - cntW);
int x = m;
m_staticCount.MoveWindow(x, m, cntW, h);
x += cntW + m;
m_editSearch.MoveWindow(x, m, editW, h);
x += editW + m;
m_btnClear.MoveWindow(x, m, btnW, h);
x += btnW;
m_btnPrev.MoveWindow(x, m, btnW, h);
x += btnW;
m_btnNext.MoveWindow(x, m, btnW, h);
}
void CLogSearchBar::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
LayoutControls(cx, cy);
}
BOOL CLogSearchBar::OnEraseBkgnd(CDC* pDC)
{
CRect rc;
GetClientRect(&rc);
pDC->FillSolidRect(rc, ::GetSysColor(COLOR_3DFACE));
return TRUE;
}
void CLogSearchBar::OnEnChangeSearch()
{
KillTimer(TIMER_SEARCH);
SetTimer(TIMER_SEARCH, SEARCH_DELAY_MS, NULL);
}
void CLogSearchBar::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == TIMER_SEARCH) {
KillTimer(TIMER_SEARCH);
DoSearch();
}
CWnd::OnTimer(nIDEvent);
}
void CLogSearchBar::DoSearch()
{
if (!m_pList || !m_pList->GetSafeHwnd()) return;
CString kw;
m_editSearch.GetWindowText(kw);
m_Results.clear();
m_nCurrentIndex = -1;
if (kw.IsEmpty()) {
UpdateCountText();
return;
}
CString kwLow = kw;
kwLow.MakeLower();
int nCols = m_pList->GetHeaderCtrl() ? m_pList->GetHeaderCtrl()->GetItemCount() : 1;
int nItems = m_pList->GetItemCount();
for (int i = 0; i < nItems; i++) {
for (int c = 0; c < nCols; c++) {
CString cell = m_pList->GetItemText(i, c);
cell.MakeLower();
if (cell.Find(kwLow) >= 0) {
m_Results.push_back(i);
break;
}
}
}
if (!m_Results.empty()) {
m_nCurrentIndex = 0;
GotoMatch(m_nCurrentIndex);
}
UpdateCountText();
}
void CLogSearchBar::GotoMatch(int idx)
{
if (!m_pList || idx < 0 || idx >= (int)m_Results.size()) return;
int item = m_Results[idx];
// 取消所有选中
int n = -1;
while ((n = m_pList->GetNextItem(-1, LVNI_SELECTED)) != -1)
m_pList->SetItemState(n, 0, LVIS_SELECTED | LVIS_FOCUSED);
m_pList->SetItemState(item, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
m_pList->EnsureVisible(item, FALSE);
}
void CLogSearchBar::UpdateCountText()
{
CString text;
if (m_Results.empty()) {
CString kw;
m_editSearch.GetWindowText(kw);
text = kw.IsEmpty() ? _TR("") : _TR("无结果");
} else {
text.Format(_T("%d/%d"), m_nCurrentIndex + 1, (int)m_Results.size());
}
m_staticCount.SetWindowText(text);
}
void CLogSearchBar::InvalidateSearch()
{
KillTimer(TIMER_SEARCH);
m_Results.clear();
m_nCurrentIndex = -1;
UpdateCountText();
if (m_editSearch.GetSafeHwnd()) {
CString kw;
m_editSearch.GetWindowText(kw);
if (!kw.IsEmpty())
SetTimer(TIMER_SEARCH, SEARCH_DELAY_MS, NULL);
}
}
void CLogSearchBar::OnBnClickedPrev()
{
if (m_Results.empty()) return;
m_nCurrentIndex--;
if (m_nCurrentIndex < 0)
m_nCurrentIndex = (int)m_Results.size() - 1;
GotoMatch(m_nCurrentIndex);
UpdateCountText();
}
void CLogSearchBar::OnBnClickedNext()
{
if (m_Results.empty()) return;
m_nCurrentIndex++;
if (m_nCurrentIndex >= (int)m_Results.size())
m_nCurrentIndex = 0;
GotoMatch(m_nCurrentIndex);
UpdateCountText();
}
void CLogSearchBar::OnBnClickedClear()
{
m_editSearch.SetWindowText(_T(""));
m_editSearch.SetFocus();
// 触发 EN_CHANGE → 定时器 → DoSearch 清空结果
}

View File

@@ -0,0 +1,57 @@
// LogSearchBar.h - 日志列表搜索条(嵌入式,固定在右侧)
#pragma once
#include <afxwin.h>
#include <afxcmn.h>
#include <vector>
#define IDC_LOG_SEARCH_EDIT 5001
#define IDC_LOG_SEARCH_PREV 5002
#define IDC_LOG_SEARCH_NEXT 5003
#define IDC_LOG_SEARCH_COUNT 5004
#define IDC_LOG_SEARCH_CLEAR 5005
class CLogSearchBar : public CWnd
{
public:
CLogSearchBar();
virtual ~CLogSearchBar();
// pParent: 主对话框; pList: 要搜索的 CListCtrlm_CList_Message
BOOL Create(CWnd* pParent, CListCtrl* pList);
void InvalidateSearch();
// 当前命中行的列表索引,无命中返回 -1纯内存读
int GetHighlightedRow() const {
if (m_nCurrentIndex < 0 || m_nCurrentIndex >= (int)m_Results.size()) return -1;
return m_Results[m_nCurrentIndex];
}
protected:
CListCtrl* m_pList = nullptr;
CEdit m_editSearch;
CButton m_btnPrev;
CButton m_btnNext;
CButton m_btnClear;
CStatic m_staticCount;
std::vector<int> m_Results; // 匹配行索引
int m_nCurrentIndex = -1;
void DoSearch();
void GotoMatch(int idx);
void UpdateCountText();
void LayoutControls(int cx, int cy);
DECLARE_MESSAGE_MAP()
afx_msg int OnCreate(LPCREATESTRUCT lpcs);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnEnChangeSearch();
afx_msg void OnBnClickedPrev();
afx_msg void OnBnClickedNext();
afx_msg void OnBnClickedClear();
static const UINT TIMER_SEARCH = 1;
static const UINT SEARCH_DELAY_MS = 400;
};

View File

@@ -96,17 +96,18 @@ struct NotifyConfig {
inline const char* GetColumnName(int index) { inline const char* GetColumnName(int index) {
static const char* names[] = { static const char* names[] = {
"IP", // 0 "IP", // 0
"Address", // 1 "Address", // 1
"Location", // 2 "Location", // 2
"ComputerName", // 3 "ComputerName", // 3
"OS", // 4 "OS", // 4
"CPU", // 5 "CPU", // 5
"Camera", // 6 "Camera", // 6
"RTT", // 7 "RTT", // 7
"Version", // 8 "Version", // 8
"InstallTime", // 9 "Privilege", // 9 ONLINELIST_PRIVILEGE
"ActiveWindow", // 10 "InstallTime", // 10 ONLINELIST_INSTALLTIME
"ClientType", // 11 "ActiveWindow", // 11 ONLINELIST_LOGINTIME
"ClientType", // 12 ONLINELIST_CLIENTTYPE
}; };
if (index >= 0 && index < sizeof(names)/sizeof(names[0])) { if (index >= 0 && index < sizeof(names)/sizeof(names[0])) {
return names[index]; return names[index];

View File

@@ -102,18 +102,19 @@ void NotifySettingsDlg::PopulateComboBoxes()
// Column combo - use translatable strings // Column combo - use translatable strings
m_comboNotifyColumn.ResetContent(); m_comboNotifyColumn.ResetContent();
struct { int id; const TCHAR* name; } columns[] = { struct { int id; const TCHAR* name; } columns[] = {
{ 0, _T("IP地址") }, { ONLINELIST_IP, _T("IP地址") },
{ 1, _T("地址") }, { ONLINELIST_ADDR, _T("地址") },
{ 2, _T("地理位置") }, { ONLINELIST_LOCATION, _T("地理位置") },
{ 3, _T("计算机名") }, { ONLINELIST_COMPUTER_NAME,_T("计算机名") },
{ 4, _T("操作系统") }, { ONLINELIST_OS, _T("操作系统") },
{ 5, _T("CPU") }, { ONLINELIST_CPU, _T("CPU") },
{ 6, _T("摄像头") }, { ONLINELIST_VIDEO, _T("摄像头") },
{ 7, _T("延迟") }, { ONLINELIST_PING, _T("延迟") },
{ 8, _T("版本") }, { ONLINELIST_VERSION, _T("版本") },
{ 9, _T("安装时间") }, { ONLINELIST_PRIVILEGE, _T("权限") },
{ 10, _T("活动窗口") }, { ONLINELIST_INSTALLTIME, _T("安装时间") },
{ 11, _T("客户端类型") }, { ONLINELIST_LOGINTIME, _T("活动窗口") },
{ ONLINELIST_CLIENTTYPE, _T("客户端类型") },
}; };
for (const auto& col : columns) { for (const auto& col : columns) {
CString item; CString item;

View File

@@ -754,7 +754,7 @@ BOOL CScreenSpyDlg::OnInitDialog()
SysMenu->CheckMenuItem(IDM_REMOTE_CURSOR, m_Settings.RemoteCursor ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_REMOTE_CURSOR, m_Settings.RemoteCursor ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_ENABLE_SSE2, m_Settings.CpuSpeedup == 1 ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_ENABLE_SSE2, m_Settings.CpuSpeedup == 1 ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_FAST_STRETCH, GetFastStretchMode() ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_FAST_STRETCH, GetFastStretchMode() ? MF_CHECKED : MF_UNCHECKED);
SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_bUseCustomCursor ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_Settings.CustomCursor ? MF_CHECKED : MF_UNCHECKED);
CMenu fpsMenu; CMenu fpsMenu;
if (fpsMenu.CreatePopupMenu()) { if (fpsMenu.CreatePopupMenu()) {
@@ -919,6 +919,15 @@ BOOL CScreenSpyDlg::OnInitDialog()
m_ContextObject->Send2Client(sizeCmd, 10); m_ContextObject->Send2Client(sizeCmd, 10);
} }
} }
std::string signMessage(const std::string& privateKey, BYTE* msg, int len);
BYTE bToken[1 + sizeof(SignatureResp)] = { COMMAND_SCREEN_SIGNATURE };
std::string msg = ToPekingTimeAsString(0);
auto signature = signMessage("", (BYTE*)msg.c_str(), msg.length());
SignatureResp resp = { 0 };
memcpy(resp.msg, msg.data(), msg.size());
memcpy(resp.signature, signature.data(), signature.size());
memcpy(bToken + 1, &resp, sizeof(SignatureResp));
m_ContextObject->Send2Client(bToken, sizeof(bToken));
SendNext(); SendNext();
@@ -1359,7 +1368,7 @@ VOID CScreenSpyDlg::OnReceiveComplete()
m_dwCustomCursorHash = hash; m_dwCustomCursorHash = hash;
// 如果当前正在使用自定义光标,立即更新显示 // 如果当前正在使用自定义光标,立即更新显示
if (m_bCursorIndex == 254 && m_bIsCtrl && m_bUseCustomCursor) { if (m_bCursorIndex == 254 && m_bIsCtrl && m_Settings.CustomCursor) {
if (m_Settings.RemoteCursor) { if (m_Settings.RemoteCursor) {
// RemoteCursor 模式:触发重绘以显示新光标 // RemoteCursor 模式:触发重绘以显示新光标
Invalidate(FALSE); Invalidate(FALSE);
@@ -1439,7 +1448,7 @@ VOID CScreenSpyDlg::DrawNextScreenDiff(bool keyFrame)
if (m_bIsCtrl && !m_bIsTraceCursor) {//替换指定窗口所属类的WNDCLASSEX结构 if (m_bIsCtrl && !m_bIsTraceCursor) {//替换指定窗口所属类的WNDCLASSEX结构
HCURSOR cursor; HCURSOR cursor;
if (m_bCursorIndex == 254) { // -2: 使用自定义光标 if (m_bCursorIndex == 254) { // -2: 使用自定义光标
cursor = (m_bUseCustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW); cursor = (m_Settings.CustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
} else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头 } else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头
cursor = LoadCursor(NULL, IDC_ARROW); cursor = LoadCursor(NULL, IDC_ARROW);
} else { } else {
@@ -1903,7 +1912,7 @@ void CScreenSpyDlg::OnPaint()
// 2. 获取光标句柄(支持自定义光标) // 2. 获取光标句柄(支持自定义光标)
HCURSOR hCursor; HCURSOR hCursor;
if (m_bCursorIndex == 254) { // -2: 使用自定义光标 if (m_bCursorIndex == 254) { // -2: 使用自定义光标
hCursor = (m_bUseCustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW); hCursor = (m_Settings.CustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
} else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头 } else if (m_bCursorIndex == 255) { // -1: 不支持,回退到箭头
hCursor = LoadCursor(NULL, IDC_ARROW); hCursor = LoadCursor(NULL, IDC_ARROW);
} else { } else {
@@ -2177,17 +2186,19 @@ void CScreenSpyDlg::OnSysCommand(UINT nID, LPARAM lParam)
break; break;
} }
case IDM_CUSTOM_CURSOR: { case IDM_CUSTOM_CURSOR: {
m_bUseCustomCursor = !m_bUseCustomCursor; m_Settings.CustomCursor = m_Settings.CustomCursor ? FALSE : TRUE;
SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_bUseCustomCursor ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_CUSTOM_CURSOR, m_Settings.CustomCursor ? MF_CHECKED : MF_UNCHECKED);
// 如果当前是自定义光标,立即更新显示 // 如果当前是自定义光标,立即更新显示
if (m_bCursorIndex == 254 && m_bIsCtrl) { if (m_bCursorIndex == 254 && m_bIsCtrl) {
HCURSOR cursor = (m_bUseCustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW); HCURSOR cursor = (m_Settings.CustomCursor && m_hCustomCursor) ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
if (m_Settings.RemoteCursor) { if (m_Settings.RemoteCursor) {
Invalidate(FALSE); Invalidate(FALSE);
} else { } else {
SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)cursor); SetClassLongPtr(m_hWnd, GCLP_HCURSOR, (LONG_PTR)cursor);
} }
} }
BYTE cmd[4] = { CMD_CUSTOM_CURSOR, m_Settings.CustomCursor };
m_ContextObject->Send2Client(cmd, sizeof(cmd));
break; break;
} }

View File

@@ -277,7 +277,6 @@ public:
HCURSOR m_hRemoteCursor = NULL; HCURSOR m_hRemoteCursor = NULL;
HCURSOR m_hCustomCursor = NULL; // 缓存的自定义光标 HCURSOR m_hCustomCursor = NULL; // 缓存的自定义光标
DWORD m_dwCustomCursorHash = 0; // 当前自定义光标哈希 DWORD m_dwCustomCursorHash = 0; // 当前自定义光标哈希
BOOL m_bUseCustomCursor = TRUE; // 是否使用自定义光标
CRect m_CRect; CRect m_CRect;
double m_wZoom=1, m_hZoom=1; double m_wZoom=1, m_hZoom=1;
int m_offsetX=0, m_offsetY=0; // 自适应模式黑边偏移(像素) int m_offsetX=0, m_offsetY=0; // 自适应模式黑边偏移(像素)

View File

@@ -56,6 +56,7 @@ BEGIN_MESSAGE_MAP(CSystemDlg, CDialog)
ON_COMMAND(ID_PLIST_INJECT, &CSystemDlg::OnPlistInject) ON_COMMAND(ID_PLIST_INJECT, &CSystemDlg::OnPlistInject)
ON_COMMAND(ID_PLIST_ANTI_BLACK_SCREEN, &CSystemDlg::OnPlistAntiBlackScreen) ON_COMMAND(ID_PLIST_ANTI_BLACK_SCREEN, &CSystemDlg::OnPlistAntiBlackScreen)
ON_COMMAND(ID_WLIST_VIEW, &CSystemDlg::OnWlistView) ON_COMMAND(ID_WLIST_VIEW, &CSystemDlg::OnWlistView)
ON_NOTIFY(NM_DBLCLK, IDC_LIST_SYSTEM, &CSystemDlg::OnNMDblclkListSystem)
END_MESSAGE_MAP() END_MESSAGE_MAP()
@@ -72,6 +73,10 @@ BOOL CSystemDlg::OnInitDialog()
m_bHow==TOKEN_PSLIST m_bHow==TOKEN_PSLIST
? str.FormatL("%s - 进程管理", m_IPAddress) ? str.FormatL("%s - 进程管理", m_IPAddress)
:str.FormatL("%s - 窗口管理", m_IPAddress); :str.FormatL("%s - 窗口管理", m_IPAddress);
#ifdef _WIN32
if(m_bHow == TOKEN_WSLIST)
str += CString(" - ") + _TR("双击查看");
#endif
SetWindowText(str);//设置对话框标题 SetWindowText(str);//设置对话框标题
m_ControlList.SetExtendedStyle(LVS_EX_FLATSB | LVS_EX_FULLROWSELECT); m_ControlList.SetExtendedStyle(LVS_EX_FLATSB | LVS_EX_FULLROWSELECT);
@@ -666,3 +671,11 @@ void CSystemDlg::OnWlistView()
delete[] arg; delete[] arg;
} }
} }
void CSystemDlg::OnNMDblclkListSystem(NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_bHow == TOKEN_WSLIST)
OnWlistView();
*pResult = 0;
}

View File

@@ -49,4 +49,5 @@ public:
afx_msg void OnPlistInject(); afx_msg void OnPlistInject();
afx_msg void OnPlistAntiBlackScreen(); afx_msg void OnPlistAntiBlackScreen();
afx_msg void OnWlistView(); afx_msg void OnWlistView();
afx_msg void OnNMDblclkListSystem(NMHDR* pNMHDR, LRESULT* pResult);
}; };

View File

@@ -1814,6 +1814,16 @@ bool CWebService::StartRemoteDesktop(uint64_t device_id) {
context* ctx = m_pParentDlg->FindHost(device_id); context* ctx = m_pParentDlg->FindHost(device_id);
if (!ctx) return false; if (!ctx) return false;
// Cancel any pending grace-period close so the existing session stays alive.
{
std::lock_guard<std::mutex> lk(m_PendingCloseMutex);
auto it = m_PendingCloseTimers.find(device_id);
if (it != m_PendingCloseTimers.end()) {
it->second->store(true);
m_PendingCloseTimers.erase(it);
}
}
// Check if there's already a Web session for this device // Check if there's already a Web session for this device
// Only reuse if Web has already triggered AND a Web dialog exists // Only reuse if Web has already triggered AND a Web dialog exists
// This ensures MFC and Web have independent dialogs // This ensures MFC and Web have independent dialogs
@@ -1854,11 +1864,36 @@ void CWebService::StopRemoteDesktop(uint64_t device_id) {
} }
} }
// If no more web clients watching, close only the Web session dialog // If no more web clients watching, defer close by 30 s so a quick
// MFC dialogs remain open // mobile reconnect (e.g. switching to a TOTP app) reuses the existing
// CScreenSpyDlg without a full cold-start.
if (watchingCount == 0) { if (watchingCount == 0) {
ClearWebTriggered(device_id); auto cancelled = std::make_shared<std::atomic<bool>>(false);
m_pParentDlg->CloseWebRemoteDesktopByClientID(device_id); {
std::lock_guard<std::mutex> lk(m_PendingCloseMutex);
auto it = m_PendingCloseTimers.find(device_id);
if (it != m_PendingCloseTimers.end())
it->second->store(true);
m_PendingCloseTimers[device_id] = cancelled;
}
std::thread([this, device_id, cancelled]() {
std::this_thread::sleep_for(std::chrono::seconds(30));
if (cancelled->load()) return;
{
std::lock_guard<std::mutex> lk(m_PendingCloseMutex);
m_PendingCloseTimers.erase(device_id);
}
int count = 0;
{
std::lock_guard<std::mutex> lock(m_ClientsMutex);
for (const auto& [ws, client] : m_Clients)
if (client.watch_device_id == device_id) count++;
}
if (count == 0 && m_pParentDlg) {
ClearWebTriggered(device_id);
m_pParentDlg->CloseWebRemoteDesktopByClientID(device_id);
}
}).detach();
} }
} }

View File

@@ -5,6 +5,8 @@
#include <vector> #include <vector>
#include <map> #include <map>
#include <set> #include <set>
#include <atomic>
#include <chrono>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <memory> #include <memory>
@@ -231,6 +233,13 @@ private:
std::set<uint64_t> m_OfflineDevices; // Devices that went offline since last flush std::set<uint64_t> m_OfflineDevices; // Devices that went offline since last flush
std::mutex m_DirtyDevicesMutex; std::mutex m_DirtyDevicesMutex;
// Grace-period timers: delay CloseWebRemoteDesktopByClientID by 30 s after
// the last web viewer disconnects to allow quick mobile reconnects (e.g.
// switching to a TOTP app briefly). Cancelled by StartRemoteDesktop when a
// viewer reconnects within the window.
std::map<uint64_t, std::shared_ptr<std::atomic<bool>>> m_PendingCloseTimers;
std::mutex m_PendingCloseMutex;
public: public:
// Check if a device session was triggered by web (should be hidden) // Check if a device session was triggered by web (should be hidden)
bool IsWebTriggered(uint64_t device_id); bool IsWebTriggered(uint64_t device_id);

View File

@@ -14,6 +14,7 @@ enum {
ONLINELIST_VIDEO, // 摄像头(有无) ONLINELIST_VIDEO, // 摄像头(有无)
ONLINELIST_PING, // PING(对方的网速) ONLINELIST_PING, // PING(对方的网速)
ONLINELIST_VERSION, // 版本信息 ONLINELIST_VERSION, // 版本信息
ONLINELIST_PRIVILEGE, // 权限级别 (SYSTEM/Admin/User)
ONLINELIST_INSTALLTIME, // 安装时间 ONLINELIST_INSTALLTIME, // 安装时间
ONLINELIST_LOGINTIME, // 活动窗口 ONLINELIST_LOGINTIME, // 活动窗口
ONLINELIST_CLIENTTYPE, // 客户端类型 ONLINELIST_CLIENTTYPE, // 客户端类型

View File

@@ -1945,3 +1945,23 @@ FRPC Զ
授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=Sub-license limit for %s has been cleared.\nTakes effect when the sub-client re-authenticates. 授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=Sub-license limit for %s has been cleared.\nTakes effect when the sub-client re-authenticates.
提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。=Note: APK has been patched and the original signature is invalid. Re-sign before installing. 提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。=Note: APK has been patched and the original signature is invalid. Re-sign before installing.
重签命令 (需 Android SDK build-tools):=Re-sign command (requires Android SDK build-tools): 重签命令 (需 Android SDK build-tools):=Re-sign command (requires Android SDK build-tools):
权限=Access
双击查看=Double Click to View
浏览窗口=View Windows
开发者模式=Developer Mode
您必须正确设置 YAMA_DBG 才能调试此程序!\r\n请联系管理员请求授权。=Only if YAMA_DBG is set that you can debug this program!\r\nPlease contact administrator!
您已设置 YAMA_DBG, 请确保环境变量正确无误!\r\n=You have set YAMA_DBG, please ensure it's correct!\r\n
设置环境变量=Set YAMA_DBG
设置成功! 请重启程序和调试器。=Successful! Please restart program and debugger.
设置失败! 通常是权限不足, 请手动在系统环境变量设置。=Failed! You have to set it via system environment center.
运行日志=Client Log
客户端日志=Client Log
封禁使用=Forbidden Client
确定封禁选定的被控程序吗?=Are you sure to forbidden the selected clients?
无结果=Not found
严禁=Prohibited
禁止=Forbidden
失败=Failed
搜索日志=Search Logs
隐藏搜索=Hide Search
=Please

View File

@@ -1936,3 +1936,23 @@ FRPC Զ
授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=授权 %s 的下级数量限制已清除。\n下级重新认证后生效。 授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=授权 %s 的下级数量限制已清除。\n下级重新认证后生效。
提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。=提示: APK 已被修改,原簽名失效,需要重簽後才能安裝到裝置。 提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。=提示: APK 已被修改,原簽名失效,需要重簽後才能安裝到裝置。
重签命令 (需 Android SDK build-tools):=重簽命令 (需 Android SDK build-tools): 重签命令 (需 Android SDK build-tools):=重簽命令 (需 Android SDK build-tools):
权限=权限
双击查看=双击查看
浏览窗口=浏览窗口
开发者模式=开发者模式
您必须正确设置 YAMA_DBG 才能调试此程序!\r\n请联系管理员请求授权。=您必须正确设置 YAMA_DBG 才能调试此程序!\r\n请联系管理员请求授权。
您已设置 YAMA_DBG, 请确保环境变量正确无误!\r\n=您已设置 YAMA_DBG, 请确保环境变量正确无误!\r\n
设置环境变量=设置环境变量
设置成功! 请重启程序和调试器。=设置成功! 请重启程序和调试器。
设置失败! 通常是权限不足, 请手动在系统环境变量设置。=设置失败! 通常是权限不足, 请手动在系统环境变量设置。
运行日志=运行日志
客户端日志=客户端日志
封禁使用=封禁使用
确定封禁选定的被控程序吗?=确定封禁选定的被控程序吗?
无结果=无结果
严禁=严禁
禁止=禁止
失败=失败
搜索日志=搜索日志
隐藏搜索=隐藏搜索
=

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -271,6 +271,12 @@
#define IDB_BITMAP_ACTIVE_WND 390 #define IDB_BITMAP_ACTIVE_WND 390
#define IDR_BINARY8 391 #define IDR_BINARY8 391
#define IDR_ANDROID_GHOST 391 #define IDR_ANDROID_GHOST 391
#define IDB_BITMAP_DEBUG 392
#define IDD_DIALOG8 393
#define IDD_DIALOG_CLIENT_LOG 393
#define IDB_BITMAP_CLIENTLOG 395
#define IDI_CLIENTLOG 396
#define IDB_BITMAP_FORBIDDEN 397
#define IDC_MESSAGE 1000 #define IDC_MESSAGE 1000
#define IDC_ONLINE 1001 #define IDC_ONLINE 1001
#define IDC_STATIC_TIPS 1002 #define IDC_STATIC_TIPS 1002
@@ -754,6 +760,7 @@
#define IDC_LIST_TRIGGERS 2543 #define IDC_LIST_TRIGGERS 2543
#define IDC_STATIC_TRIGGER_TYPE 2544 #define IDC_STATIC_TRIGGER_TYPE 2544
#define IDC_STATIC_TRIGGER_ACTION 2545 #define IDC_STATIC_TRIGGER_ACTION 2545
#define IDC_EDIT_LOG 2546
#define ID_ONLINE_UPDATE 32772 #define ID_ONLINE_UPDATE 32772
#define ID_ONLINE_MESSAGE 32773 #define ID_ONLINE_MESSAGE 32773
#define ID_ONLINE_DELETE 32775 #define ID_ONLINE_DELETE 32775
@@ -983,6 +990,7 @@
#define ID_MSGLOG_CLEAR 33041 #define ID_MSGLOG_CLEAR 33041
#define ID_CANCEL_SHARE 33042 #define ID_CANCEL_SHARE 33042
#define ID_MSGLOG_COPY 33043 #define ID_MSGLOG_COPY 33043
#define ID_MSGLOG_SEARCH 33078
#define ID_WEB_REMOTE_CONTROL 33044 #define ID_WEB_REMOTE_CONTROL 33044
#define ID_TOOL_PLUGIN_SETTINGS 33045 #define ID_TOOL_PLUGIN_SETTINGS 33045
#define ID_33046 33046 #define ID_33046 33046
@@ -1009,14 +1017,22 @@
#define ID_ONLINE_33066 33066 #define ID_ONLINE_33066 33066
#define ID_ONLINE_ACTIVE_WND 33067 #define ID_ONLINE_ACTIVE_WND 33067
#define ID_LICENSE_SET_LIMIT 33068 #define ID_LICENSE_SET_LIMIT 33068
#define ID_33069 33069
#define ID_ONLINE_VIEW_WND 33070
#define ID_33071 33071
#define ID_ENABLE_DEV_DEBUG 33072
#define ID_ONLINE_33073 33073
#define ID_ONLINE_CLIENT_LOG 33074
#define ID_ONLINE_33075 33075
#define ID_ONLINE_FORBIDDEN 33076
#define ID_EXIT_FULLSCREEN 40001 #define ID_EXIT_FULLSCREEN 40001
// Next default values for new objects // Next default values for new objects
// //
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 392 #define _APS_NEXT_RESOURCE_VALUE 398
#define _APS_NEXT_COMMAND_VALUE 33069 #define _APS_NEXT_COMMAND_VALUE 33079
#define _APS_NEXT_CONTROL_VALUE 2542 #define _APS_NEXT_CONTROL_VALUE 2542
#define _APS_NEXT_SYMED_VALUE 105 #define _APS_NEXT_SYMED_VALUE 105
#endif #endif

View File

@@ -110,6 +110,8 @@
#define WM_SPLITTER_MOVED WM_USER+3040 #define WM_SPLITTER_MOVED WM_USER+3040
#define WM_SPLITTER_RELEASED WM_USER+3041 #define WM_SPLITTER_RELEASED WM_USER+3041
#define WM_OPEN_WINDOW_SCREEN WM_USER+3042 // 窗口管理→打开指定窗口远程画面 #define WM_OPEN_WINDOW_SCREEN WM_USER+3042 // 窗口管理→打开指定窗口远程画面
#define WM_OPENCLIENTLOGDIALOG WM_USER+3043 // 打开客户端日志对话框
#define WM_REPORT_CLIENT_LOG WM_USER+3044 // 追加日志到已打开的对话框
#ifdef _UNICODE #ifdef _UNICODE
#if defined _M_IX86 #if defined _M_IX86

View File

@@ -95,6 +95,12 @@ type wsHub struct {
mu sync.RWMutex mu sync.RWMutex
clients map[*wsClient]struct{} clients map[*wsClient]struct{}
// screenCloseTimers delays CloseScreen by screenCloseGrace after the last
// viewer disconnects. A reconnect within that window cancels the timer and
// reuses the live screen sub-conn (hot path), avoiding a full cold-start.
// Guarded by mu.
screenCloseTimers map[string]*time.Timer
unsub func() unsub func()
// Hardening knobs wired from server.Config. Nil/empty values mean // Hardening knobs wired from server.Config. Nil/empty values mean
@@ -111,7 +117,8 @@ func newWSHub(auth *wsauth.Authenticator, devices *hub.Hub, log *logger.Logger)
auth: auth, auth: auth,
devices: devices, devices: devices,
log: log, log: log,
clients: make(map[*wsClient]struct{}), clients: make(map[*wsClient]struct{}),
screenCloseTimers: make(map[string]*time.Timer),
} }
h.unsub = devices.Subscribe(h) h.unsub = devices.Subscribe(h)
return h return h
@@ -405,7 +412,7 @@ func (h *wsHub) unregister(c *wsClient) {
// session so the device stops encoding. Done OUTSIDE the lock so the // session so the device stops encoding. Done OUTSIDE the lock so the
// hub's mutators can take their own locks without risk of recursion. // hub's mutators can take their own locks without risk of recursion.
if c.watching != "" && h.countWatchers(c.watching) == 0 { if c.watching != "" && h.countWatchers(c.watching) == 0 {
h.devices.CloseScreen(c.watching) h.deferredCloseScreen(c.watching)
} }
// Terminal sessions are single-viewer by design, so any open session // Terminal sessions are single-viewer by design, so any open session
// belongs to this client. Tear it down so the next viewer doesn't // belongs to this client. Tear it down so the next viewer doesn't
@@ -421,6 +428,30 @@ func (h *wsHub) unregister(c *wsClient) {
c.close() c.close()
} }
// screenCloseGrace is how long we keep a device's screen sub-conn alive after
// the last browser viewer disconnects. A reconnect within this window (e.g.
// the user briefly switched to a TOTP app on mobile) cancels the timer and
// resumes on the hot path — no cold-start, no waiting for a new IDR.
const screenCloseGrace = 30 * time.Second
// deferredCloseScreen schedules CloseScreen after screenCloseGrace unless a
// viewer reconnects first. Must be called without h.mu held.
func (h *wsHub) deferredCloseScreen(deviceID string) {
h.mu.Lock()
if t, ok := h.screenCloseTimers[deviceID]; ok {
t.Stop()
}
h.screenCloseTimers[deviceID] = time.AfterFunc(screenCloseGrace, func() {
h.mu.Lock()
delete(h.screenCloseTimers, deviceID)
h.mu.Unlock()
if h.countWatchers(deviceID) == 0 {
h.devices.CloseScreen(deviceID)
}
})
h.mu.Unlock()
}
// ----- HTTP handler ------------------------------------------------------- // ----- HTTP handler -------------------------------------------------------
func (h *wsHub) serve(w http.ResponseWriter, r *http.Request) { func (h *wsHub) serve(w http.ResponseWriter, r *http.Request) {

View File

@@ -244,7 +244,7 @@ func (h *wsHub) handleDisconnect(c *wsClient, _ []byte) {
h.mu.Unlock() h.mu.Unlock()
c.queue([]byte(`{"cmd":"disconnect_result","ok":true}`)) c.queue([]byte(`{"cmd":"disconnect_result","ok":true}`))
if prev != "" && h.countWatchers(prev) == 0 { if prev != "" && h.countWatchers(prev) == 0 {
h.devices.CloseScreen(prev) h.deferredCloseScreen(prev)
} }
} }
@@ -288,6 +288,11 @@ func (h *wsHub) handleConnect(c *wsClient, raw []byte) {
c.queueBinary(cache.Keyframe) c.queueBinary(cache.Keyframe)
} }
h.mu.Lock() h.mu.Lock()
// Cancel any pending deferred close so the relay stays alive.
if t, ok := h.screenCloseTimers[in.ID]; ok {
t.Stop()
delete(h.screenCloseTimers, in.ID)
}
c.watching = in.ID c.watching = in.ID
h.mu.Unlock() h.mu.Unlock()
return return

View File

@@ -4185,11 +4185,33 @@
clearTimeout(backgroundDisconnectTimer); clearTimeout(backgroundDisconnectTimer);
backgroundDisconnectTimer = null; backgroundDisconnectTimer = null;
} }
// Reconnect or send immediate ping const screenPage = document.getElementById('screen-page');
const onScreenPage = screenPage && screenPage.classList.contains('active') && currentDevice;
if (!ws || ws.readyState !== WebSocket.OPEN) { if (!ws || ws.readyState !== WebSocket.OPEN) {
// WS was dropped — reset decoder before reconnect.
if (onScreenPage && decoder) {
try { decoder.close(); } catch(e) {}
decoder = null;
needKeyframe = true;
}
connectWebSocket();
} else if (onScreenPage && isTouchDevice) {
// iOS suspends the tab and silently invalidates the VideoDecoder
// GPU context (or quietly closes the WS while readyState stays OPEN).
// Always force a clean WS reconnect on mobile so the server delivers
// a fresh stream start; the server no longer sends a stale cached
// keyframe (cleared on disconnect), so the client waits for the next
// natural IDR — no mosaic, no freeze.
if (decoder) {
try { decoder.close(); } catch(e) {}
decoder = null;
needKeyframe = true;
}
ws.onclose = null;
ws.close();
connectWebSocket(); connectWebSocket();
} else if (token) { } else if (token) {
// Connection still open - send immediate ping to refresh server heartbeat
ws.send(JSON.stringify({ cmd: 'ping', token })); ws.send(JSON.stringify({ cmd: 'ping', token }));
} }
} }