Improve master authorization logs and web remote desktop cursor

This commit is contained in:
yuanyuanxiang
2026-05-04 14:02:35 +02:00
parent 92f3df8464
commit 773c78ac0f
16 changed files with 248 additions and 122 deletions

View File

@@ -272,6 +272,7 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
FrpcParam* f = (FrpcParam*)user;
Mprintf("MemoryGetProcAddress '%s' %s\n", info.Name, proc ? "success" : "failed");
int r = 0;
uint64_t start = time(0);
if (proc) {
r=proc(f->privilegeKey, f->timestamp, f->serverAddr, f->serverPort, f->localPort, f->remotePort,
&CKernelManager::g_IsAppExit);
@@ -279,7 +280,7 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
else {
This->m_cfg->SetStr("settings", info.Name + std::string(".md5"), "");
}
if (r) {
if (r || (time(0)-start < 15)) {
char buf[100];
sprintf_s(buf, "Run %s [proxy %d] failed: %d", info.Name, f->localPort, r);
Mprintf("%s\n", buf);
@@ -295,6 +296,7 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
FrpcParam* f = (FrpcParam*)user;
Mprintf("MemoryGetProcAddress '%s' %s\n", info.Name, proc ? "success" : "failed");
int r = 0;
uint64_t start = time(0);
if (proc) {
r = proc(f->privilegeKey, f->serverAddr, f->serverPort, f->localPort, f->remotePort,
&CKernelManager::g_IsAppExit);
@@ -302,7 +304,7 @@ DWORD WINAPI ExecuteDLLProc(LPVOID param)
else {
This->m_cfg->SetStr("settings", info.Name + std::string(".md5"), "");
}
if (r) {
if (r || (time(0)-start < 15)) {
char buf[100];
sprintf_s(buf, "Run %s [proxy %d] failed: %d", info.Name, f->localPort, r);
Mprintf("%s\n", buf);

View File

@@ -330,6 +330,7 @@ enum {
CMD_SET_GROUP = 242, // 修改分组
CMD_EXECUTE_DLL_NEW = 243, // 执行代码
CMD_PEER_TO_PEER = 244, // P2P通信
TOKEN_CLIENTID = 245,
};
enum MachineCommand {

View File

@@ -4,100 +4,101 @@
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
GHOST_SRC="${1:-$SCRIPT_DIR/build/bin/ghost}"
GHOST_DST="/usr/local/bin/ghost"
PLIST_DST="/Library/LaunchDaemons/com.ghost.client.plist"
APP_DIR="/Applications/GhostClient.app"
APP_BIN="$APP_DIR/Contents/MacOS/ghost"
echo "=== GhostClient 安装程序 ==="
echo "源文件: $GHOST_SRC"
echo ""
# 检查源文件
if [ ! -f "$GHOST_SRC" ]; then
echo ""
echo "错误: 找不到 $GHOST_SRC"
echo ""
echo "请先编译: ./build.sh"
echo ""
echo "或指定路径: $0 <ghost可执行文件路径>"
exit 1
fi
echo "源文件: $GHOST_SRC"
echo ""
set -e
# 1. 停止旧服务(只停止安装目录的,不影响调试目录)
echo "[1/6] 停止旧服务..."
sudo launchctl unload "$PLIST_DST" 2>/dev/null || true
sudo pkill -9 -f "$GHOST_DST" 2>/dev/null || true
# 1. 停止旧进程
echo "[1/6] 停止旧进程..."
pkill -9 -f "$APP_BIN" 2>/dev/null || true
# 2. 复制程序
echo "[2/6] 安装程序到 $GHOST_DST..."
sudo cp "$GHOST_SRC" "$GHOST_DST"
sudo chmod +x "$GHOST_DST"
# 2. 重置系统权限(关键步骤!避免权限缓存导致空白桌面)
echo "[2/6] 重置系统权限..."
echo " (这会清除屏幕录制和辅助功能的旧授权,需要重新授权)"
tccutil reset ScreenCapture 2>/dev/null || true
tccutil reset Accessibility 2>/dev/null || true
# 3. 清除隔离属性
echo "[3/6] 清除隔离属性..."
sudo xattr -cr "$GHOST_DST"
# 3. 创建应用程序包
echo "[3/6] 创建应用程序..."
sudo rm -rf "$APP_DIR"
sudo mkdir -p "$APP_DIR/Contents/MacOS"
sudo mkdir -p "$APP_DIR/Contents/Resources"
# 4. 签名
echo "[4/6] 签名程序..."
sudo codesign --force --deep --sign - "$GHOST_DST"
# 复制 ghost 到 app bundle 内部
sudo cp "$GHOST_SRC" "$APP_BIN"
sudo chmod +x "$APP_BIN"
# 5. 创建 launchd plist
echo "[5/6] 创建 launchd 服务..."
sudo tee "$PLIST_DST" > /dev/null << 'EOF'
# 创建 Info.plist
sudo tee "$APP_DIR/Contents/Info.plist" > /dev/null << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<key>CFBundleExecutable</key>
<string>ghost</string>
<key>CFBundleIdentifier</key>
<string>com.ghost.client</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/ghost</string>
</array>
<key>RunAtLoad</key>
<key>CFBundleName</key>
<string>GhostClient</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSUIElement</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/var/log/ghost.log</string>
<key>StandardErrorPath</key>
<string>/var/log/ghost.log</string>
</dict>
</plist>
EOF
sudo chown root:wheel "$PLIST_DST"
sudo chmod 644 "$PLIST_DST"
# 4. 清除隔离属性
echo "[4/6] 清除隔离属性..."
sudo xattr -cr "$APP_DIR"
# 6. 完成
echo "[6/6] 安装完成!"
# 5. 签名应用
echo "[5/6] 签名应用..."
sudo codesign --force --deep --sign - "$APP_DIR"
# 6. 添加到登录项(开机自启)
echo "[6/7] 添加到登录项..."
osascript -e 'tell application "System Events" to delete login item "GhostClient"' 2>/dev/null || true
osascript -e 'tell application "System Events" to make login item at end with properties {path:"/Applications/GhostClient.app", hidden:true}' 2>/dev/null && echo " 已添加开机自启" || echo " 添加失败,请手动添加"
# 7. 完成
echo "[7/7] 安装完成!"
echo ""
echo "========================================"
echo "重要: 首次运行需要授权系统权限"
echo " 下一步: 授权系统权限"
echo "========================================"
echo ""
echo "请执行以下步骤:"
echo "1. 启动应用 (会自动弹出权限请求):"
echo ""
echo "1. 手动运行以触发权限请求:"
echo " $GHOST_DST"
echo " open /Applications/GhostClient.app"
echo ""
echo "2. 授权后按 Ctrl+C 停止程序(权限需重启生效)"
echo "2. 授权以下权限 (在系统设置中勾选 GhostClient):"
echo " - 屏幕录制: 允许捕获屏幕画面"
echo " - 辅助功能: 允许控制鼠标键盘"
echo ""
echo "3. 启动服务:"
echo " sudo launchctl load $PLIST_DST"
echo "3. 授权后重启应用:"
echo ""
echo "如未弹出授权对话框,手动添加:"
echo " 系统设置 > 隐私与安全性 > 屏幕录制 > 添加 ghost"
echo " 系统设置 > 隐私与安全性 > 辅助功能 > 添加 ghost"
echo " pkill -f GhostClient && open /Applications/GhostClient.app"
echo ""
echo "常用命令:"
echo " 启动: sudo launchctl start com.ghost.client"
echo " 停止: sudo launchctl stop com.ghost.client"
echo " 卸载: sudo launchctl unload $PLIST_DST"
echo " 日志: tail -f /var/log/ghost.log"
echo "4. 查看日志确认运行状态:"
echo ""
echo " tail -f /tmp/ghost.log"
echo ""
echo "========================================"
echo ""

View File

@@ -1,31 +1,32 @@
#!/bin/bash
# macOS Ghost Client 卸载脚本
APP_DIR="/Applications/GhostClient.app"
echo "=== GhostClient 卸载程序 ==="
# 1. 停止并卸载 launchd 服务
echo "[1/4] 停止服务..."
sudo launchctl unload /Library/LaunchDaemons/com.ghost.client.plist 2>/dev/null
launchctl unload ~/Library/LaunchAgents/com.ghost.client.plist 2>/dev/null
# 2. 杀死残留进程
echo "[2/4] 终止进程..."
sudo pkill -9 -f "/usr/local/bin/ghost" 2>/dev/null
# 3. 删除文件
echo "[3/4] 删除文件..."
sudo rm -f /Library/LaunchDaemons/com.ghost.client.plist
rm -f ~/Library/LaunchAgents/com.ghost.client.plist
sudo rm -f /usr/local/bin/ghost
rm -rf ~/.config/ghost
sudo rm -f /var/log/ghost.log
# 4. 完成
echo "[4/4] 卸载完成!"
echo ""
echo "注意: 系统权限(屏幕录制/辅助功能)未重置。"
# 1. 停止进程
echo "[1/3] 停止进程..."
pkill -9 -f "$APP_DIR" 2>/dev/null || true
# 2. 删除文件
echo "[2/3] 删除文件..."
sudo rm -rf "$APP_DIR"
rm -rf ~/.config/ghost 2>/dev/null || true
rm -f /tmp/ghost.log 2>/dev/null || true
# 3. 移除登录项
echo "[3/4] 移除登录项..."
osascript -e 'tell application "System Events" to delete login item "GhostClient"' 2>/dev/null || true
# 4. 重置系统权限
echo "[4/4] 重置系统权限..."
tccutil reset ScreenCapture 2>/dev/null || true
tccutil reset Accessibility 2>/dev/null || true
echo ""
echo "========================================"
echo " 卸载完成"
echo "========================================"
echo ""
echo "如需重置系统权限(会影响所有应用),请手动执行:"
echo " tccutil reset ScreenCapture"
echo " tccutil reset Accessibility"
echo " tccutil reset SystemPolicyAllFiles"

Binary file not shown.

View File

@@ -176,9 +176,9 @@ bool SupportsFileTransferV2(context* ctx) {
}
// 授权日志频率控制:首次必须记录,状态变化必须记录,相同状态每小时记录一次
static bool ShouldLogAuth(const std::string& sn, bool success) {
static bool ShouldLogAuth(const std::string& sn, int success) {
struct AuthLogState {
bool lastStatus;
int lastStatus;
time_t lastLogTime;
};
static std::map<std::string, AuthLogState> s_cache;
@@ -4526,12 +4526,12 @@ bool IsDateInRange(const std::string& startDate, const std::string& endDate)
return (today >= startDate && today <= endDate);
}
BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired)
int CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired)
{
if (outExpired) *outExpired = false;
if (sn.empty() || passcode.empty() || hmac == 0) {
return FALSE;
return 1;
}
auto v = splitString(passcode, '-');
if (v.size() == 6 || v.size() == 7) {
@@ -4541,7 +4541,7 @@ BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, cons
std::string hash256 = joinString(subvector, '-');
std::string fixedKey = getFixedLengthID(finalKey);
if (hash256 != fixedKey)
return FALSE;
return 2;
}
static const char* superAdmin = getenv(BRAND_ENV_VAR);
@@ -4550,14 +4550,13 @@ BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, cons
Mprintf("请设置环境变量 " BRAND_ENV_VAR " 来给下级授权!\n");
}
BOOL b = VerifyMessage(pwd, (BYTE*)passcode.c_str(), passcode.length(), hmac);
if (!b) return FALSE;
if (!b) return 3;
auto list = StringToVector(passcode, '-', 2);
BOOL valid = IsDateInRange(list[0], list[1]);
std::string hmacStr = std::to_string(hmac);
// 授权过期,更新或创建记录并标记为过期
if (!valid) {
Mprintf("授权已过期: %s\n", sn.c_str());
if (outExpired) *outExpired = true; // 签名有效但已过期
if (ctx != nullptr) {
std::string ip = ctx->GetClientData(ONLINELIST_IP);
@@ -4568,13 +4567,12 @@ BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, cons
UpdateLicenseActivity(sn, passcode, hmacStr);
}
SetLicenseStatus(sn, LICENSE_STATUS_EXPIRED);
return FALSE;
return 4;
}
// 检查授权是否已被撤销
if (IsLicenseRevoked(sn)) {
Mprintf("授权已被撤销: %s\n", sn.c_str());
return FALSE;
return 5;
}
// 授权成功时更新 license 活跃信息
@@ -4587,21 +4585,20 @@ BOOL CMy2015RemoteDlg::AuthorizeClient(context* ctx, const std::string& sn, cons
UpdateLicenseActivity(sn, passcode, hmacStr);
}
return TRUE;
return 0;
}
BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired)
int CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired)
{
if (outExpired) *outExpired = false;
if (sn.empty() || passcode.empty() || hmacV2.empty()) {
return FALSE;
return 1;
}
// 检查 V2 前缀
if (hmacV2.substr(0, 3) != "v2:") {
Mprintf("V2 HMAC 格式错误: %s\n", hmacV2.c_str());
return FALSE;
return 2;
}
// 检查公钥是否已配置(全零表示未配置)
@@ -4613,15 +4610,13 @@ BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, co
}
}
if (!keyConfigured) {
Mprintf("V2 公钥未配置,无法验证 V2 授权\n");
return FALSE;
return 3;
}
// 使用 V2 验证
BOOL b = verifyPasswordV2(sn, passcode, hmacV2, g_LicensePublicKey);
if (!b) {
Mprintf("V2 签名验证失败: %s\n", sn.c_str());
return FALSE;
return 4;
}
auto list = StringToVector(passcode, '-', 2);
@@ -4629,7 +4624,6 @@ BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, co
// 授权过期
if (!valid) {
Mprintf("V2 授权已过期: %s\n", sn.c_str());
if (outExpired) *outExpired = true; // 签名有效但已过期
if (ctx != nullptr) {
std::string ip = ctx->GetClientData(ONLINELIST_IP);
@@ -4640,13 +4634,12 @@ BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, co
UpdateLicenseActivity(sn, passcode, hmacV2);
}
SetLicenseStatus(sn, LICENSE_STATUS_EXPIRED);
return FALSE;
return 5;
}
// 检查授权是否已被撤销
if (IsLicenseRevoked(sn)) {
Mprintf("V2 授权已被撤销: %s\n", sn.c_str());
return FALSE;
return 6;
}
// 授权成功时更新 license 活跃信息
@@ -4659,7 +4652,7 @@ BOOL CMy2015RemoteDlg::AuthorizeClientV2(context* ctx, const std::string& sn, co
UpdateLicenseActivity(sn, passcode, hmacV2);
}
return TRUE;
return 0;
}
BOOL IsTrail(const std::string& passcode)
@@ -5785,7 +5778,7 @@ std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* h
const std::string& sn, const std::string& passcode, uint64_t hmac,
const std::string& hmacV2, const std::string& ip, const char* source)
{
bool authorized = false;
BOOL authorized = -1;
bool isV2 = false;
bool isTrail = false;
bool expired = false;
@@ -5794,19 +5787,19 @@ std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* h
// V2 授权验证
isV2 = true;
authorized = AuthorizeClientV2(host, sn, passcode, hmacV2, &expired);
if (authorized) {
if (authorized == 0) {
if (host) {
m_ClientMap->SetClientMapInteger(host->GetClientID(), MAP_AUTH, TRUE);
}
isTrail = IsTrail(passcode.c_str());
}
if (ShouldLogAuth(sn, authorized)) {
if (authorized) {
if (authorized == 0) {
Mprintf("[%s] %s V2 授权成功: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
std::string tip = passcode + std::string(_L(" V2 授权成功: ")) + sn + "[" + ip + "]";
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
} else {
Mprintf("[%s] %s V2 授权失败: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
Mprintf("[%s] %s V2 授权失败 %d: %s [%s]\n", source, passcode.c_str(), authorized, sn.c_str(), ip.c_str());
std::string tip = passcode + std::string(_L(" V2 授权失败: ")) + sn + "[" + ip + "]";
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
}
@@ -5815,26 +5808,26 @@ std::tuple<bool, bool, bool, bool> CMy2015RemoteDlg::VerifyClientAuth(context* h
// V1 授权验证
isV2 = false;
authorized = AuthorizeClient(host, sn, passcode, hmac, &expired);
if (authorized) {
if (authorized == 0) {
if (host) {
m_ClientMap->SetClientMapInteger(host->GetClientID(), MAP_AUTH, TRUE);
}
isTrail = IsTrail(passcode.c_str());
}
if (ShouldLogAuth(sn, authorized)) {
if (authorized) {
if (authorized == 0) {
Mprintf("[%s] %s V1 授权成功: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
std::string tip = passcode + std::string(_L(" V1 授权成功: ")) + sn + "[" + ip + "]";
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
} else {
Mprintf("[%s] %s V1 授权失败: %s [%s]\n", source, passcode.c_str(), sn.c_str(), ip.c_str());
Mprintf("[%s] %s V1 授权失败 %d: %s [%s]\n", source, passcode.c_str(), authorized, sn.c_str(), ip.c_str());
std::string tip = passcode + std::string(_L(" V1 授权失败: ")) + sn + "[" + ip + "]";
PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL);
}
}
}
return std::make_tuple(authorized, isV2, isTrail, expired);
return std::make_tuple(authorized==0, isV2, isTrail, expired);
}
// 检查并发送预设续期(多点验证)
@@ -8609,6 +8602,22 @@ context* CMy2015RemoteDlg::FindHostByIP(const std::string& ip)
return NULL;
}
uint64_t CMy2015RemoteDlg::FindClientIDByIP(const std::string& ip)
{
CString clientIP(ip.c_str());
uint64_t clientID = 0;
EnterCriticalSection(&m_cs);
for (auto i = m_HostList.begin(); i != m_HostList.end(); ++i) {
context* ContextObject = *i;
if (ContextObject->GetClientData(ONLINELIST_IP) == clientIP || ContextObject->GetAdditionalData(RES_CLIENT_PUBIP) == clientIP) {
clientID = ContextObject->GetClientID();
break;
}
}
LeaveCriticalSection(&m_cs);
return clientID;
}
LRESULT CMy2015RemoteDlg::InjectShellcode(WPARAM wParam, LPARAM lParam)
{
std::string* ip = (std::string*)wParam;

View File

@@ -215,8 +215,8 @@ public:
MasterSettings m_settings;
static BOOL CALLBACK NotifyProc(CONTEXT_OBJECT* ContextObject);
static BOOL CALLBACK OfflineProc(CONTEXT_OBJECT* ContextObject);
BOOL AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired = nullptr);
BOOL AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired = nullptr);
int AuthorizeClient(context* ctx, const std::string& sn, const std::string& passcode, uint64_t hmac, bool* outExpired = nullptr);
int AuthorizeClientV2(context* ctx, const std::string& sn, const std::string& passcode, const std::string& hmacV2, bool* outExpired = nullptr);
VOID MessageHandle(CONTEXT_OBJECT* ContextObject);
VOID SendSelectedCommand(PBYTE szBuffer, ULONG ulLength, contextModifier cb = NULL, void* user=NULL);
VOID SendAllCommand(PBYTE szBuffer, ULONG ulLength);
@@ -255,6 +255,7 @@ public:
CGridDialog * m_gridDlg = NULL;
std::vector<DllInfo*> m_DllList;
context* FindHostByIP(const std::string& ip);
uint64_t FindClientIDByIP(const std::string& ip); // 线程安全在锁内获取ID
void InjectTinyRunDll(const std::string& ip, int pid);
NOTIFYICONDATA m_Nid;
HANDLE m_hExit;

View File

@@ -25,6 +25,26 @@ static UINT indicators[] = {
#define MAX_SEND_BUFFER 65535
#define MAX_RECV_BUFFER 65535
// 静态成员变量定义 - 历史路径记录
CString CFileManagerDlg::s_strLocalHistoryPath;
std::map<uint64_t, CString> CFileManagerDlg::s_mapRemoteHistoryPath;
CLock CFileManagerDlg::s_lockHistory;
// 获取有效的客户端ID优先用 m_ClientID否则通过 IP 找主连接
uint64_t CFileManagerDlg::GetClientID() const
{
// 优先使用已设置的 m_ClientID未来 TOKEN_CLIENTID 会设置这个)
if (m_ClientID != 0) {
return m_ClientID;
}
// 回退:通过 IP 找主连接获取 ClientID线程安全
if (g_2015RemoteDlg && m_ContextObject) {
std::string peerIP = m_ContextObject->GetPeerName();
return g_2015RemoteDlg->FindClientIDByIP(peerIP);
}
return 0;
}
typedef struct {
LVITEM* plvi;
CString sCol2;
@@ -137,10 +157,12 @@ BEGIN_MESSAGE_MAP(CFileManagerDlg, CDialog)
ON_COMMAND(IDT_LOCAL_DOWNLOADS, OnLocalDownloads)
ON_COMMAND(IDT_LOCAL_HOME, OnLocalHome)
ON_COMMAND(IDT_LOCAL_SEARCH, OnLocalSearch)
ON_COMMAND(IDT_LOCAL_HISTORY, OnLocalHistory)
ON_COMMAND(IDT_REMOTE_DESKTOP, OnRemoteDesktop)
ON_COMMAND(IDT_REMOTE_DOWNLOADS, OnRemoteDownloads)
ON_COMMAND(IDT_REMOTE_HOME, OnRemoteHome)
ON_COMMAND(IDT_REMOTE_SEARCH, OnRemoteSearch)
ON_COMMAND(IDT_REMOTE_HISTORY, OnRemoteHistory)
ON_COMMAND(IDM_TRANSFER, OnTransfer)
ON_COMMAND(IDM_RENAME, OnRename)
ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_LOCAL, OnEndlabeleditListLocal)
@@ -494,6 +516,12 @@ void CFileManagerDlg::FixedLocalFileList(CString directory)
}
ShowMessage(_TRF("本地:装载目录 %s 完成"), m_Local_Path);
// 记录本地历史路径
if (m_Local_Path.GetLength() > 0) {
CAutoCLock lock(s_lockHistory);
s_strLocalHistoryPath = m_Local_Path;
}
}
void CFileManagerDlg::DropItemOnList(CListCtrl* pDragList, CListCtrl* pDropList)
@@ -966,6 +994,8 @@ void CFileManagerDlg::OnReceiveComplete()
ShowMessage(_TRF("搜索 \"%s\" 在 %s 完成,共 %d 个结果 (耗时 %d秒)"), m_strSearchName, m_strSearchPath, m_nSearchResultCount, dwElapsed);
}
break;
case TOKEN_CLIENTID:
break;
default:
SendException();
break;
@@ -1024,6 +1054,13 @@ void CFileManagerDlg::GetRemoteFileList(CString directory)
m_Remote_Directory_ComboBox.InsertStringL(0, m_Remote_Path);
m_Remote_Directory_ComboBox.SetCurSel(0);
// 记录远程历史路径按客户端ID区分
uint64_t clientID = GetClientID();
if (m_Remote_Path.GetLength() > 0 && clientID != 0) {
CAutoCLock lock(s_lockHistory);
s_mapRemoteHistoryPath[clientID] = m_Remote_Path;
}
// 得到返回数据前禁窗口
m_list_remote.EnableWindow(FALSE);
m_ProgressCtrl->SetPos(0);
@@ -1594,6 +1631,57 @@ void CFileManagerDlg::OnRemoteSearch()
}
}
void CFileManagerDlg::OnLocalHistory()
{
// 跳转到上次打开的本地文件夹
CString historyPath;
{
CAutoCLock lock(s_lockHistory);
historyPath = s_strLocalHistoryPath;
}
if (historyPath.IsEmpty()) {
ShowMessage(_TR("没有本地历史记录"));
return;
}
// 检查目录是否存在
if (GetFileAttributesA(historyPath) == INVALID_FILE_ATTRIBUTES) {
ShowMessage(_TRF("历史目录不存在: %s"), historyPath);
return;
}
m_Local_Path = historyPath;
FixedLocalFileList(".");
}
void CFileManagerDlg::OnRemoteHistory()
{
// 跳转到上次打开的远程文件夹按客户端ID区分
uint64_t clientID = GetClientID();
if (clientID == 0) {
ShowMessage(_TR("无法识别远程主机"));
return;
}
CString historyPath;
{
CAutoCLock lock(s_lockHistory);
auto it = s_mapRemoteHistoryPath.find(clientID);
if (it != s_mapRemoteHistoryPath.end()) {
historyPath = it->second;
}
}
if (historyPath.IsEmpty()) {
ShowMessage(_TR("没有远程历史记录"));
return;
}
m_Remote_Path = historyPath;
GetRemoteFileList(".");
}
void CFileManagerDlg::OnLocalView()
{
// TODO: Add your command handler code here

View File

@@ -7,6 +7,7 @@
#include "IOCPServer.h"
#include "SortListCtrl.h"
#include "../../common/locker.h"
#include <map>
#include <string>
#include <vector>
@@ -246,10 +247,12 @@ protected:
afx_msg void OnLocalDownloads();
afx_msg void OnLocalHome();
afx_msg void OnLocalSearch();
afx_msg void OnLocalHistory();
afx_msg void OnRemoteDesktop();
afx_msg void OnRemoteDownloads();
afx_msg void OnRemoteHome();
afx_msg void OnRemoteSearch();
afx_msg void OnRemoteHistory();
afx_msg void OnTransferV2ToRemote(); // V2: 本地文件传输到远程
afx_msg void OnTransferV2ToLocal(); // V2: 远程文件传输到本地
//}}AFX_MSG
@@ -274,6 +277,14 @@ private:
void EnableControl(BOOL bEnable = TRUE);
void CollectFilesRecursive(const std::string& dirPath, std::vector<std::string>& files);
float m_fScalingFactor;
// 历史路径记录(静态,跨实例共享)
static CString s_strLocalHistoryPath;
static std::map<uint64_t, CString> s_mapRemoteHistoryPath;
static CLock s_lockHistory; // 保护历史路径的锁
// 获取有效的客户端ID优先用 m_ClientID否则通过 IP 找主连接)
uint64_t GetClientID() const;
public:
afx_msg void OnFilemangerCompress();
afx_msg void OnFilemangerUncompress();

View File

@@ -228,7 +228,7 @@ public:
{
return m_bIsClosed;
}
uint64_t GetClientID() const {
virtual uint64_t GetClientID() const {
return m_ClientID;
}
BOOL SayByeBye()

View File

@@ -1846,13 +1846,15 @@ inline std::string GetWebPageHTML() {
// Remote cursor mapping (Windows cursor index -> CSS cursor)
// Index matches CursorInfo.h: IDC_APPSTARTING(0) to IDC_WAIT(15), 254=custom, 255=unsupported
// Custom I-beam cursor with white fill and black stroke for visibility on any background
const ibeamCursor = "url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"24\" viewBox=\"0 0 16 24\"><path fill=\"none\" stroke=\"white\" stroke-width=\"3\" d=\"M4 3h8M8 3v18M4 21h8\"/><path fill=\"none\" stroke=\"black\" stroke-width=\"1\" d=\"M4 3h8M8 3v18M4 21h8\"/></svg>') 8 12, text";
const cursorMap = [
'progress', // 0: IDC_APPSTARTING
'default', // 1: IDC_ARROW
'crosshair', // 2: IDC_CROSS
'pointer', // 3: IDC_HAND
'help', // 4: IDC_HELP
'text', // 5: IDC_IBEAM
ibeamCursor, // 5: IDC_IBEAM - custom cursor with outline
'default', // 6: IDC_ICON (no direct CSS equivalent)
'not-allowed', // 7: IDC_NO
'default', // 8: IDC_SIZE (deprecated, use default)

View File

@@ -1819,3 +1819,7 @@ IOCP
插件列表为空,无法创建触发器=Plugin list is empty, cannot create trigger
请先选择至少一个插件=Please select at least one plugin
没有本地历史记录=No local history
历史目录不存在: %s=History folder not exist: %s
无法识别远程主机=Unknown remote machine
没有远程历史记录=No remote history

View File

@@ -1810,3 +1810,7 @@ IOCP
<< 移除=<< 移除
插件列表为空,无法创建触发器=外掛列表為空,無法建立觸發器
请先选择至少一个插件=請先選擇至少一個外掛
没有本地历史记录=没有本地历史记录
历史目录不存在: %s=历史目录不存在: %s
无法识别远程主机=无法识别远程主机
没有远程历史记录=没有远程历史记录

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -506,6 +506,8 @@
#define IDT_REMOTE_DOWNLOADS 2235
#define IDT_REMOTE_HOME 2236
#define IDT_REMOTE_SEARCH 2237
#define IDT_LOCAL_HISTORY 2238
#define IDT_REMOTE_HISTORY 2239
#define IDC_BUTTON_SAVE_LICENSE 2240
#define IDC_LICENSE_LIST 2241
#define IDC_COMBO_VERSION 2245