2 Commits

Author SHA1 Message Date
yuanyuanxiang
36423b1c7c Refactor: Move FileManager to common, add macOS file management support 2026-05-03 10:30:30 +02:00
yuanyuanxiang
a3611d9fc1 Feature: Add terminal support for macOS client with shared PTYHandler 2026-05-03 09:33:47 +02:00
12 changed files with 501 additions and 216 deletions

View File

@@ -494,27 +494,31 @@ make
**系统要求**
- macOS 10.15 (Catalina) 及以上
- 架构支持Intel (x64) 和 Apple Silicon (arm64) 通用二进制
- 需要授予系统权限:屏幕录制、辅助功能、完全磁盘访问
**功能支持**
| 功能 | 状态 | 实现 |
|------|------|------|
| 远程桌面 | ✅ | CoreGraphics 屏幕捕获H.264 硬件编码 |
| 远程桌面 | ✅ | CoreGraphics 屏幕捕获,VideoToolbox H.264 硬件编码 |
| 鼠标控制 | ✅ | CGEvent 模拟,支持双击、拖拽 |
| 键盘控制 | ✅ | CGEvent 模拟,完整键码映射 |
| 光标同步 | ✅ | 实时同步远程光标样式 |
| 远程终端 | ✅ | PTY 交互式 Shellzsh/bash |
| 文件管理 | ✅ | 双向传输、V2 协议、大文件支持 |
| 心跳/RTT | ✅ | RFC 6298 RTT 估算 |
| 文件管理 | | 开发中 |
| 远程终端 | ⏳ | 开发中 |
| 分组管理 | | 持久化配置文件 |
| 进程管理 | ⏳ | 开发中 |
| 剪贴板 | ⏳ | 开发中 |
**编译方式**
```bash
cd macos
mkdir build && cd build
cmake ..
make
./build.sh
# 或手动编译:
# mkdir build && cd build && cmake .. && make
```
---

View File

@@ -479,27 +479,31 @@ make
**System Requirements**:
- macOS 10.15 (Catalina) or later
- Architecture: Universal Binary (Intel x64 + Apple Silicon arm64)
- Required permissions: Screen Recording, Accessibility, Full Disk Access
**Feature Support**:
| Feature | Status | Implementation |
|---------|--------|----------------|
| Remote Desktop | ✅ | CoreGraphics screen capture, H.264 hardware encoding |
| Remote Desktop | ✅ | CoreGraphics screen capture, VideoToolbox H.264 hardware encoding |
| Mouse Control | ✅ | CGEvent simulation, supports double-click, drag |
| Keyboard Control | ✅ | CGEvent simulation, full keycode mapping |
| Cursor Sync | ✅ | Real-time remote cursor style synchronization |
| Remote Terminal | ✅ | PTY interactive shell (zsh/bash) |
| File Management | ✅ | Bidirectional transfer, V2 protocol, large file support |
| Heartbeat/RTT | ✅ | RFC 6298 RTT estimation |
| File Management | | In development |
| Remote Terminal | ⏳ | In development |
| Group Management | | Persistent configuration file |
| Process Management | ⏳ | In development |
| Clipboard | ⏳ | In development |
**Build Instructions**:
```bash
cd macos
mkdir build && cd build
cmake ..
make
./build.sh
# Or manually:
# mkdir build && cd build && cmake .. && make
```
---

View File

@@ -478,27 +478,31 @@ make
**系統要求**
- macOS 10.15 (Catalina) 及以上
- 架構支援Intel (x64) 和 Apple Silicon (arm64) 通用二進位
- 需要授予系統權限:螢幕錄製、輔助使用、完全磁碟存取
**功能支援**
| 功能 | 狀態 | 實作 |
|------|------|------|
| 遠端桌面 | ✅ | CoreGraphics 螢幕擷取H.264 硬體編碼 |
| 遠端桌面 | ✅ | CoreGraphics 螢幕擷取,VideoToolbox H.264 硬體編碼 |
| 滑鼠控制 | ✅ | CGEvent 模擬,支援雙擊、拖曳 |
| 鍵盤控制 | ✅ | CGEvent 模擬,完整鍵碼對應 |
| 游標同步 | ✅ | 即時同步遠端游標樣式 |
| 遠端終端 | ✅ | PTY 互動式 Shellzsh/bash |
| 檔案管理 | ✅ | 雙向傳輸、V2 協定、大檔案支援 |
| 心跳/RTT | ✅ | RFC 6298 RTT 估算 |
| 檔案管理 | | 開發中 |
| 遠端終端 | ⏳ | 開發中 |
| 分組管理 | | 持久化設定檔 |
| 程序管理 | ⏳ | 開發中 |
| 剪貼簿 | ⏳ | 開發中 |
**編譯方式**
```bash
cd macos
mkdir build && cd build
cmake ..
make
./build.sh
# 或手動編譯:
# mkdir build && cd build && cmake .. && make
```
---

View File

@@ -1,8 +1,24 @@
/**
* FileManager.h - Unix File Manager
*
* Implements file transfer between Windows server and Unix client.
* Supports: browse, upload, download, delete, rename, create folder
*
* PLATFORM SUPPORT:
* - Linux: Supported
* - macOS: Supported
* - Windows: NOT SUPPORTED (Windows uses different file APIs)
*/
#pragma once
#if defined(_WIN32) || defined(_WIN64)
#error "FileManager.h is not supported on Windows."
#endif
#include <dirent.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <iconv.h>
#include <unistd.h>
#include <cstring>
#include <string>
@@ -11,15 +27,19 @@
#include <fstream>
#include <sstream>
#include <cerrno>
#ifdef __APPLE__
#include <sys/mount.h> // macOS: statfs for filesystem type
#endif
#include <iconv.h> // Character encoding conversion (GBK <-> UTF-8)
// FileTransferV2 is in the same directory (common/)
#include "FileTransferV2.h"
// 外部声明 clientID在 main.cpp 中定义)
// External declaration of clientID (defined in main.cpp/main.mm)
extern uint64_t g_myClientID;
// ============== Linux File Manager ==============
// Implements file transfer between Windows server and Linux client
// Supports: browse, upload, download, delete, rename, create folder
#define MAX_SEND_BUFFER 65535
class FileManager : public IOCPManager
@@ -222,6 +242,13 @@ private:
// ---- Get root filesystem type ----
static std::string getRootFsType()
{
#ifdef __APPLE__
struct statfs sf;
if (statfs("/", &sf) == 0) {
return std::string(sf.f_fstypename); // "apfs", "hfs", etc.
}
return "apfs";
#else
std::ifstream f("/proc/mounts");
std::string line;
while (std::getline(f, line)) {
@@ -232,6 +259,7 @@ private:
}
}
return "ext4";
#endif
}
// ---- Ensure parent directory exists (mkdir -p for parent of file path) ----
@@ -307,7 +335,11 @@ private:
memcpy(buf + offset + 2, &totalMB, sizeof(unsigned long));
memcpy(buf + offset + 6, &freeMB, sizeof(unsigned long));
#ifdef __APPLE__
const char* typeName = "macOS";
#else
const char* typeName = "Linux";
#endif
int typeNameLen = strlen(typeName) + 1;
memcpy(buf + offset + 10, typeName, typeNameLen);

View File

@@ -1,7 +1,24 @@
/**
* FileTransferV2.h - Unix V2 File Transfer Protocol
*
* Implements V2 file transfer protocol for Unix clients.
* Supports: receive files from server/C2C, send files to server/C2C
*
* PLATFORM SUPPORT:
* - Linux: Supported
* - macOS: Supported
* - Windows: NOT SUPPORTED (Windows uses different file APIs)
*/
#pragma once
#include "common/commands.h"
#include "common/file_upload.h"
#include "client/IOCPClient.h"
#if defined(_WIN32) || defined(_WIN64)
#error "FileTransferV2.h is not supported on Windows."
#endif
#include "commands.h"
#include "file_upload.h"
#include "../client/IOCPClient.h"
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -15,10 +32,6 @@
#include <fstream>
#include <thread>
// ============== Linux V2 File Transfer ==============
// Implements V2 file transfer protocol for Linux client
// Supports: receive files from server/C2C, send files to server/C2C
class FileTransferV2
{
public:

270
common/PTYHandler.h Normal file
View File

@@ -0,0 +1,270 @@
/**
* PTYHandler.h - Unix Pseudo Terminal Handler
*
* This file provides pseudo terminal (PTY) functionality for remote shell access.
*
* PLATFORM SUPPORT:
* - Linux: Supported
* - macOS: Supported
* - Windows: NOT SUPPORTED (Windows uses different terminal APIs)
*
* USAGE:
* #include "common/PTYHandler.h"
*
* PTYHandler* handler = new PTYHandler(clientObject);
* clientObject->setManagerCallBack(handler, ...);
*/
#pragma once
#if defined(_WIN32) || defined(_WIN64)
#error "PTYHandler.h is not supported on Windows. Use Windows ConPTY or other APIs instead."
#endif
// Platform-specific includes
#ifdef __APPLE__
#include <util.h> // macOS: openpty()
#else
#include <pty.h> // Linux: openpty()
#endif
// Common Unix includes
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <thread>
#include <atomic>
#include <stdexcept>
#include "commands.h"
#include "../client/IOCPClient.h"
/**
* PTYHandler - Pseudo Terminal Handler
*
* Manages a pseudo terminal for remote shell access.
* Inherits from IOCPManager to integrate with the IOCP client framework.
*/
class PTYHandler : public IOCPManager
{
public:
// Non-copyable, non-movable (owns system resources)
PTYHandler(const PTYHandler&) = delete;
PTYHandler& operator=(const PTYHandler&) = delete;
PTYHandler(PTYHandler&&) = delete;
PTYHandler& operator=(PTYHandler&&) = delete;
PTYHandler(IOCPClient* client) : m_client(client), m_running(false), m_master_fd(-1), m_slave_fd(-1), m_child_pid(-1)
{
if (!client) {
throw std::invalid_argument("IOCPClient pointer cannot be null");
}
// Create pseudo terminal pair
if (openpty(&m_master_fd, &m_slave_fd, nullptr, nullptr, nullptr) == -1) {
throw std::runtime_error("Failed to create pseudo terminal");
}
// Set master fd to non-blocking mode
int flags = fcntl(m_master_fd, F_GETFL, 0);
fcntl(m_master_fd, F_SETFL, flags | O_NONBLOCK);
// Start shell process
startShell();
}
~PTYHandler()
{
m_running = false;
if (m_readThread.joinable()) {
m_readThread.join();
}
if (m_master_fd >= 0) {
close(m_master_fd);
}
if (m_slave_fd >= 0) {
close(m_slave_fd);
}
if (m_child_pid > 0) {
// Check if child is still running before killing
int status;
pid_t result = waitpid(m_child_pid, &status, WNOHANG);
if (result == 0) {
// Child still running, terminate it
kill(m_child_pid, SIGTERM);
waitpid(m_child_pid, nullptr, 0);
}
// If result == m_child_pid, child already exited and was reaped
// If result == -1, child was already reaped elsewhere
}
}
// Start the PTY read thread
void Start()
{
bool expected = false;
if (!m_running.compare_exchange_strong(expected, true)) return;
m_readThread = std::thread(&PTYHandler::readFromPTY, this);
}
// Handle incoming data from server
virtual VOID OnReceive(PBYTE data, ULONG size) override
{
if (size && data[0] == COMMAND_NEXT) {
Start();
return;
}
// Handle terminal resize command
if (size >= 5 && data[0] == CMD_TERMINAL_RESIZE) {
short cols, rows;
memcpy(&cols, data + 1, sizeof(short));
memcpy(&rows, data + 3, sizeof(short));
SetWindowSize(cols, rows);
return;
}
// Write data to PTY
if (size > 0) {
ssize_t total = 0;
while (total < (ssize_t)size) {
ssize_t written = write(m_master_fd, (char*)data + total, size - total);
if (written == -1) {
if (errno == EAGAIN || errno == EINTR) continue;
break;
}
total += written;
}
}
}
// Set terminal window size
void SetWindowSize(int cols, int rows)
{
struct winsize ws;
ws.ws_col = cols;
ws.ws_row = rows;
ws.ws_xpixel = 0;
ws.ws_ypixel = 0;
if (ioctl(m_master_fd, TIOCSWINSZ, &ws) == -1) {
return;
}
// Send SIGWINCH to child process to notify window size change
if (m_child_pid > 0) {
kill(m_child_pid, SIGWINCH);
}
}
private:
int m_master_fd;
int m_slave_fd;
IOCPClient* m_client;
std::thread m_readThread;
std::atomic<bool> m_running;
pid_t m_child_pid;
void startShell()
{
m_child_pid = fork();
if (m_child_pid == -1) {
close(m_master_fd);
close(m_slave_fd);
throw std::runtime_error("Failed to fork shell process");
}
if (m_child_pid == 0) {
// Child process
setsid(); // Create new session, become session leader
// Set slave PTY as controlling terminal (required for Ctrl+C to work)
// This must be done after setsid() and before dup2()
ioctl(m_slave_fd, TIOCSCTTY, 0);
// Redirect stdin/stdout/stderr to slave PTY
dup2(m_slave_fd, STDIN_FILENO);
dup2(m_slave_fd, STDOUT_FILENO);
dup2(m_slave_fd, STDERR_FILENO);
close(m_master_fd);
close(m_slave_fd);
// Set terminal environment for xterm.js compatibility
setenv("TERM", "xterm-256color", 1);
setenv("COLORTERM", "truecolor", 1);
#ifdef __APPLE__
// macOS locale settings
setenv("LANG", "en_US.UTF-8", 1);
setenv("LC_ALL", "en_US.UTF-8", 1);
// Try zsh first (macOS default), fallback to bash
if (access("/bin/zsh", X_OK) == 0) {
execl("/bin/zsh", "zsh", "-i", nullptr);
}
execl("/bin/bash", "bash", "-i", nullptr);
#else
// Linux locale settings (C.UTF-8 is most portable)
setenv("LANG", "C.UTF-8", 1);
setenv("LC_ALL", "C.UTF-8", 1);
// Start interactive bash
execl("/bin/bash", "bash", "-i", nullptr);
#endif
_exit(1);
}
}
void readFromPTY()
{
char buffer[4096];
while (m_running) {
// Check if child process has exited
int status;
pid_t result = waitpid(m_child_pid, &status, WNOHANG);
if (result == m_child_pid) {
// Shell exited, send close notification
if (m_client) {
BYTE closeToken = TOKEN_TERMINAL_CLOSE;
m_client->Send2Server((char*)&closeToken, 1);
}
m_running = false;
break;
}
ssize_t bytes_read = read(m_master_fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
if (m_client) {
m_client->Send2Server(buffer, bytes_read);
}
} else if (bytes_read == 0) {
// EOF - PTY closed
if (m_client) {
BYTE closeToken = TOKEN_TERMINAL_CLOSE;
m_client->Send2Server((char*)&closeToken, 1);
}
m_running = false;
break;
} else if (bytes_read == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
usleep(10000); // 10ms
} else if (errno == EIO) {
// EIO typically means PTY slave closed (shell exited)
if (m_client) {
BYTE closeToken = TOKEN_TERMINAL_CLOSE;
m_client->Send2Server((char*)&closeToken, 1);
}
m_running = false;
break;
} else {
break;
}
}
}
}
};

View File

@@ -321,6 +321,16 @@ inline const char* getFileName(const char* path)
#endif
#elif defined(_WIN32)
#define Mprintf(format, ...) Logger::getInstance().log(getFileName((__FILE__)), __LINE__, (format), __VA_ARGS__)
#elif defined(__APPLE__)
// macOS: 使用 NSLog 输出到系统日志(可通过 Console.app 查看)
#ifdef Mprintf
#undef Mprintf
#endif
#ifdef __OBJC__
#define Mprintf(format, ...) NSLog(@"%@", [NSString stringWithFormat:@(format), ##__VA_ARGS__])
#else
#define Mprintf(format, ...) printf(format, ##__VA_ARGS__)
#endif
#else
// Linux: 覆盖 commands.h 中的 printf 回退定义,改用 Logger 写文件
#ifdef Mprintf

View File

@@ -3,7 +3,7 @@
#include "client/IOCPClient.h"
#include "LinuxConfig.h"
#include "ClipboardHandler.h"
#include "FileTransferV2.h"
#include "common/FileTransferV2.h"
#include <dlfcn.h>
#include <sys/stat.h>
#include <thread>

View File

@@ -14,7 +14,7 @@
#include <csignal>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <pty.h>
#include "common/PTYHandler.h"
#include <iostream>
#include <stdexcept>
#include <cstdio>
@@ -26,9 +26,9 @@
#include <cmath>
#include "ScreenHandler.h"
#include "SystemManager.h"
#include "FileManager.h"
#include "common/FileManager.h"
#include "ClipboardHandler.h"
#include "FileTransferV2.h"
#include "common/FileTransferV2.h"
#include "common/logger.h"
#define XXH_INLINE_ALL
#include "common/xxhash.h"
@@ -338,187 +338,7 @@ struct RttEstimator {
RttEstimator g_rttEstimator;
int g_heartbeatInterval = 5; // 默认心跳间隔(秒),可被服务端 CMD_MASTERSETTING 更新
// 伪终端处理类继承自IOCPManager.
class PTYHandler : public IOCPManager
{
public:
PTYHandler(IOCPClient* client) : m_client(client), m_running(false)
{
if (!client) {
throw std::invalid_argument("IOCPClient pointer cannot be null");
}
// 创建伪终端
if (openpty(&m_master_fd, &m_slave_fd, nullptr, nullptr, nullptr) == -1) {
throw std::runtime_error("Failed to create pseudo terminal");
}
// 设置伪终端为非阻塞模式
int flags = fcntl(m_master_fd, F_GETFL, 0);
fcntl(m_master_fd, F_SETFL, flags | O_NONBLOCK);
// 启动 Shell 进程
startShell();
}
~PTYHandler()
{
m_running = false;
if (m_readThread.joinable()) m_readThread.join();
close(m_master_fd);
close(m_slave_fd);
if (m_child_pid > 0) {
kill(m_child_pid, SIGTERM);
waitpid(m_child_pid, nullptr, 0);
}
}
// 启动读取线程
void Start()
{
bool expected = false;
if (!m_running.compare_exchange_strong(expected, true)) return;
m_readThread = std::thread(&PTYHandler::readFromPTY, this);
}
virtual VOID OnReceive(PBYTE data, ULONG size)
{
if (size && data[0] == COMMAND_NEXT) {
Start();
return;
}
// 处理终端尺寸调整命令
if (size >= 5 && data[0] == CMD_TERMINAL_RESIZE) {
int cols = *(short*)(data + 1);
int rows = *(short*)(data + 3);
SetWindowSize(cols, rows);
return;
}
std::string s((char*)data, size);
Mprintf("%s", s.c_str());
if (size > 0) {
ssize_t total = 0;
while (total < (ssize_t)size) {
ssize_t written = write(m_master_fd, (char*)data + total, size - total);
if (written == -1) {
if (errno == EAGAIN || errno == EINTR) continue;
Mprintf("OnReceive: write error %d\n", errno);
break;
}
total += written;
}
}
}
// 设置终端窗口尺寸
void SetWindowSize(int cols, int rows)
{
struct winsize ws;
ws.ws_col = cols;
ws.ws_row = rows;
ws.ws_xpixel = 0;
ws.ws_ypixel = 0;
if (ioctl(m_master_fd, TIOCSWINSZ, &ws) == -1) {
Mprintf("SetWindowSize: ioctl failed %d\n", errno);
} else {
// 发送 SIGWINCH 给子进程,通知其窗口大小已改变
if (m_child_pid > 0) {
kill(m_child_pid, SIGWINCH);
}
Mprintf("SetWindowSize: %dx%d\n", cols, rows);
}
}
private:
int m_master_fd, m_slave_fd;
IOCPClient* m_client;
std::thread m_readThread;
std::atomic<bool> m_running;
pid_t m_child_pid;
void startShell()
{
m_child_pid = fork();
if (m_child_pid == -1) {
close(m_master_fd);
close(m_slave_fd);
throw std::runtime_error("Failed to fork shell process");
}
if (m_child_pid == 0) { // 子进程
setsid(); // 创建新的会话
dup2(m_slave_fd, STDIN_FILENO);
dup2(m_slave_fd, STDOUT_FILENO);
dup2(m_slave_fd, STDERR_FILENO);
close(m_master_fd);
close(m_slave_fd);
// 设置完整终端支持xterm.js 终端仿真)
setenv("TERM", "xterm-256color", 1);
setenv("COLORTERM", "truecolor", 1);
// 使用 C.UTF-8 是最通用的 UTF-8 locale几乎所有 Linux 都支持
setenv("LANG", "C.UTF-8", 1);
setenv("LC_ALL", "C.UTF-8", 1);
// 启动交互式 Bash
execl("/bin/bash", "bash", "-i", nullptr);
exit(1);
}
}
void readFromPTY()
{
char buffer[4096];
while (m_running) {
// 检查子进程是否已退出
int status;
pid_t result = waitpid(m_child_pid, &status, WNOHANG);
if (result == m_child_pid) {
// Shell 已退出,发送关闭通知
Mprintf("readFromPTY: shell exited (status=%d)\n", WEXITSTATUS(status));
if (m_client) {
BYTE closeToken = TOKEN_TERMINAL_CLOSE;
m_client->Send2Server((char*)&closeToken, 1);
}
m_running = false;
break;
}
ssize_t bytes_read = read(m_master_fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
if (m_client) {
buffer[bytes_read] = '\0';
Mprintf("%s", buffer);
m_client->Send2Server(buffer, bytes_read);
}
} else if (bytes_read == 0) {
// EOF - PTY 已关闭
Mprintf("readFromPTY: EOF (shell closed)\n");
if (m_client) {
BYTE closeToken = TOKEN_TERMINAL_CLOSE;
m_client->Send2Server((char*)&closeToken, 1);
}
m_running = false;
break;
} else if (bytes_read == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
usleep(10000);
} else if (errno == EIO) {
// EIO 通常表示 PTY slave 已关闭shell 退出)
Mprintf("readFromPTY: EIO (shell closed)\n");
if (m_client) {
BYTE closeToken = TOKEN_TERMINAL_CLOSE;
m_client->Send2Server((char*)&closeToken, 1);
}
m_running = false;
break;
} else {
Mprintf("readFromPTY: read error %d\n", errno);
break;
}
}
}
}
};
// PTYHandler moved to common/PTYHandler.h (shared between Linux and macOS)
void* ShellworkingThread(void* param)
{

View File

@@ -45,6 +45,7 @@ find_library(CARBON_FRAMEWORK Carbon REQUIRED)
find_library(VIDEOTOOLBOX_FRAMEWORK VideoToolbox REQUIRED)
find_library(COREMEDIA_FRAMEWORK CoreMedia REQUIRED)
find_library(COREVIDEO_FRAMEWORK CoreVideo REQUIRED)
find_library(ICONV_LIBRARY iconv REQUIRED)
target_link_libraries(ghost PRIVATE
${COCOA_FRAMEWORK}
@@ -57,6 +58,7 @@ target_link_libraries(ghost PRIVATE
${VIDEOTOOLBOX_FRAMEWORK}
${COREMEDIA_FRAMEWORK}
${COREVIDEO_FRAMEWORK}
${ICONV_LIBRARY}
"${CMAKE_SOURCE_DIR}/lib/libzstd.a"
)

View File

@@ -3,6 +3,8 @@
#import "InputHandler.h"
#import "../client/IOCPClient.h"
#import "../common/commands.h"
#import "../common/FileTransferV2.h"
#import "../common/logger.h"
#import "Permissions.h"
#import <Cocoa/Cocoa.h>
#import <chrono>
@@ -280,6 +282,72 @@ void ScreenHandler::OnReceive(uint8_t* data, ULONG size)
}
break;
case COMMAND_GET_FILE:
// Server requests file download: [cmd:1][targetDir\0][file1\0file2\0...\0]
// Use V2 protocol to upload files
{
if (size < 3) break;
// Parse target directory (GBK encoding)
const char* ptr = (const char*)(data + 1);
const char* end = (const char*)(data + size);
std::string targetDirGbk = ptr;
std::string targetDir = FileTransferV2::gbkToUtf8(targetDirGbk);
ptr += targetDirGbk.length() + 1;
// Parse file list
std::vector<std::string> files;
while (ptr < end && *ptr != '\0') {
std::string fileGbk = ptr;
files.push_back(FileTransferV2::gbkToUtf8(fileGbk));
ptr += fileGbk.length() + 1;
}
// TODO: If no file list, get from clipboard (ClipboardHandler not implemented yet)
if (!files.empty() && !targetDir.empty()) {
NSLog(@">>> COMMAND_GET_FILE: %zu files -> %s", files.size(), targetDir.c_str());
// Use V2 protocol to send files
IOCPClient* client = m_client;
std::thread([files, targetDir, client]() {
// Collect all files (expand directories)
std::vector<std::string> allFiles;
std::vector<std::string> rootCandidates;
for (const auto& path : files) {
struct stat st;
if (stat(path.c_str(), &st) != 0) continue;
if (S_ISDIR(st.st_mode)) {
std::string dirPath = path;
if (dirPath.back() != '/') dirPath += '/';
size_t pos = dirPath.rfind('/', dirPath.length() - 2);
std::string parentPath = (pos != std::string::npos) ? dirPath.substr(0, pos + 1) : dirPath;
rootCandidates.push_back(parentPath);
FileTransferV2::CollectFiles(dirPath, allFiles);
} else {
rootCandidates.push_back(path);
allFiles.push_back(path);
}
}
if (allFiles.empty()) {
NSLog(@"*** No files to send");
return;
}
std::string commonRoot = FileTransferV2::GetCommonRoot(rootCandidates);
NSLog(@">>> Sending %zu files, root=%s", allFiles.size(), commonRoot.c_str());
FileTransferV2::SendFilesV2(allFiles, targetDir, commonRoot, client, g_myClientID);
}).detach();
} else {
NSLog(@"*** COMMAND_GET_FILE: no files or empty target");
}
}
break;
default:
break;
}

View File

@@ -22,6 +22,10 @@
#import "ScreenHandler.h"
#import "InputHandler.h"
#import "SystemManager.h"
#import "../common/PTYHandler.h"
#import "../common/FileManager.h"
#import "../common/FileTransferV2.h"
#import "../common/logger.h"
// Global state
static std::atomic<bool> g_running(true);
@@ -615,6 +619,28 @@ struct RttEstimator {
RttEstimator g_rttEstimator;
int g_heartbeatInterval = 5; // 心跳间隔(秒),默认 5 秒,后续可由服务端动态调整
void* ShellworkingThread(void* param)
{
try {
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
void* clientAddr = ClientObject.get();
NSLog(@">>> Enter ShellworkingThread [%p]", clientAddr);
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
std::unique_ptr<PTYHandler> handler(new PTYHandler(ClientObject.get()));
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
BYTE bToken = TOKEN_TERMINAL_START;
ClientObject->Send2Server((char*)&bToken, 1);
NSLog(@">>> ShellworkingThread [%p] Send: TOKEN_TERMINAL_START", clientAddr);
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit)
Sleep(1000);
}
NSLog(@">>> Leave ShellworkingThread [%p]", clientAddr);
} catch (const std::exception& e) {
NSLog(@"*** ShellworkingThread exception: %s ***", e.what());
}
return NULL;
}
void* ScreenworkingThread(void* param)
{
try {
@@ -641,6 +667,26 @@ void* ScreenworkingThread(void* param)
return NULL;
}
void* FileManagerworkingThread(void* param)
{
try {
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
void* clientAddr = ClientObject.get();
Mprintf(">>> Enter FileManagerworkingThread [%p]\n", clientAddr);
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
std::unique_ptr<FileManager> handler(new FileManager(ClientObject.get()));
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
Mprintf(">>> FileManagerworkingThread [%p] initialized\n", clientAddr);
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit)
Sleep(1000);
}
Mprintf(">>> Leave FileManagerworkingThread [%p]\n", clientAddr);
} catch (const std::exception& e) {
Mprintf("*** FileManagerworkingThread exception: %s ***\n", e.what());
}
return NULL;
}
int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
{
if (szBuffer == nullptr || ulLength == 0)
@@ -651,6 +697,7 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
g_bExit = S_CLIENT_EXIT;
g_running = false; // Stop main loop to prevent reconnection
} else if (szBuffer[0] == COMMAND_SHELL) {
std::thread(ShellworkingThread, nullptr).detach();
Mprintf("** [%p] Received 'SHELL' command ***\n", user);
} else if (szBuffer[0] == COMMAND_SCREEN_SPY) {
std::thread(ScreenworkingThread, nullptr).detach();
@@ -658,7 +705,18 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
} else if (szBuffer[0] == COMMAND_SYSTEM) {
Mprintf("** [%p] Received 'SYSTEM' command ***\n", user);
} else if (szBuffer[0] == COMMAND_LIST_DRIVE) {
std::thread(FileManagerworkingThread, nullptr).detach();
Mprintf("** [%p] Received 'LIST_DRIVE' command ***\n", user);
} else if (szBuffer[0] == COMMAND_C2C_PREPARE) {
// C2C 准备接收通知
FileTransferV2::HandleC2CPrepare(szBuffer, ulLength, nullptr);
Mprintf("** [%p] C2C Prepare received ***\n", user);
} else if (szBuffer[0] == COMMAND_SEND_FILE_V2 || szBuffer[0] == COMMAND_FILE_COMPLETE_V2) {
// V2 文件接收
int result = FileTransferV2::RecvFileChunkV2(szBuffer, ulLength, g_myClientID);
if (result != 0) {
Mprintf("** [%p] V2 File recv error: %d ***\n", user, result);
}
} else if (szBuffer[0] == CMD_HEARTBEAT_ACK) {
if (ulLength >= 1 + sizeof(HeartbeatACK)) {
HeartbeatACK* ack = (HeartbeatACK*)(szBuffer + 1);