From 36423b1c7c525c30a3b7007f67b45f01ff7462bf Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Sun, 3 May 2026 09:57:46 +0200 Subject: [PATCH] Refactor: Move FileManager to common, add macOS file management support --- ReadMe.md | 16 ++++--- ReadMe_EN.md | 16 ++++--- ReadMe_TW.md | 16 ++++--- {linux => common}/FileManager.h | 44 ++++++++++++++++--- {linux => common}/FileTransferV2.h | 27 +++++++++--- common/logger.h | 10 +++++ linux/ScreenHandler.h | 2 +- linux/main.cpp | 4 +- macos/CMakeLists.txt | 2 + macos/ScreenHandler.mm | 68 ++++++++++++++++++++++++++++++ macos/main.mm | 36 +++++++++++++++- 11 files changed, 206 insertions(+), 35 deletions(-) rename {linux => common}/FileManager.h (96%) rename {linux => common}/FileTransferV2.h (97%) diff --git a/ReadMe.md b/ReadMe.md index 70ea6cc..215ffc9 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -494,27 +494,31 @@ make **系统要求**: - macOS 10.15 (Catalina) 及以上 +- 架构支持:Intel (x64) 和 Apple Silicon (arm64) 通用二进制 - 需要授予系统权限:屏幕录制、辅助功能、完全磁盘访问 **功能支持**: | 功能 | 状态 | 实现 | |------|------|------| -| 远程桌面 | ✅ | CoreGraphics 屏幕捕获,H.264 硬件编码 | +| 远程桌面 | ✅ | CoreGraphics 屏幕捕获,VideoToolbox H.264 硬件编码 | | 鼠标控制 | ✅ | CGEvent 模拟,支持双击、拖拽 | | 键盘控制 | ✅ | CGEvent 模拟,完整键码映射 | | 光标同步 | ✅ | 实时同步远程光标样式 | +| 远程终端 | ✅ | PTY 交互式 Shell(zsh/bash) | +| 文件管理 | ✅ | 双向传输、V2 协议、大文件支持 | | 心跳/RTT | ✅ | RFC 6298 RTT 估算 | -| 文件管理 | ⏳ | 开发中 | -| 远程终端 | ⏳ | 开发中 | +| 分组管理 | ✅ | 持久化配置文件 | +| 进程管理 | ⏳ | 开发中 | +| 剪贴板 | ⏳ | 开发中 | **编译方式**: ```bash cd macos -mkdir build && cd build -cmake .. -make +./build.sh +# 或手动编译: +# mkdir build && cd build && cmake .. && make ``` --- diff --git a/ReadMe_EN.md b/ReadMe_EN.md index 4c9515f..d4a1a40 100644 --- a/ReadMe_EN.md +++ b/ReadMe_EN.md @@ -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 ``` --- diff --git a/ReadMe_TW.md b/ReadMe_TW.md index 31df503..5460154 100644 --- a/ReadMe_TW.md +++ b/ReadMe_TW.md @@ -478,27 +478,31 @@ make **系統要求**: - macOS 10.15 (Catalina) 及以上 +- 架構支援:Intel (x64) 和 Apple Silicon (arm64) 通用二進位 - 需要授予系統權限:螢幕錄製、輔助使用、完全磁碟存取 **功能支援**: | 功能 | 狀態 | 實作 | |------|------|------| -| 遠端桌面 | ✅ | CoreGraphics 螢幕擷取,H.264 硬體編碼 | +| 遠端桌面 | ✅ | CoreGraphics 螢幕擷取,VideoToolbox H.264 硬體編碼 | | 滑鼠控制 | ✅ | CGEvent 模擬,支援雙擊、拖曳 | | 鍵盤控制 | ✅ | CGEvent 模擬,完整鍵碼對應 | | 游標同步 | ✅ | 即時同步遠端游標樣式 | +| 遠端終端 | ✅ | PTY 互動式 Shell(zsh/bash) | +| 檔案管理 | ✅ | 雙向傳輸、V2 協定、大檔案支援 | | 心跳/RTT | ✅ | RFC 6298 RTT 估算 | -| 檔案管理 | ⏳ | 開發中 | -| 遠端終端 | ⏳ | 開發中 | +| 分組管理 | ✅ | 持久化設定檔 | +| 程序管理 | ⏳ | 開發中 | +| 剪貼簿 | ⏳ | 開發中 | **編譯方式**: ```bash cd macos -mkdir build && cd build -cmake .. -make +./build.sh +# 或手動編譯: +# mkdir build && cd build && cmake .. && make ``` --- diff --git a/linux/FileManager.h b/common/FileManager.h similarity index 96% rename from linux/FileManager.h rename to common/FileManager.h index 2ab8cf3..ae3c0b6 100644 --- a/linux/FileManager.h +++ b/common/FileManager.h @@ -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 #include #include -#include #include #include #include @@ -11,15 +27,19 @@ #include #include #include + +#ifdef __APPLE__ +#include // macOS: statfs for filesystem type +#endif + +#include // 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); diff --git a/linux/FileTransferV2.h b/common/FileTransferV2.h similarity index 97% rename from linux/FileTransferV2.h rename to common/FileTransferV2.h index 243ac01..97abf82 100644 --- a/linux/FileTransferV2.h +++ b/common/FileTransferV2.h @@ -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 #include #include @@ -15,10 +32,6 @@ #include #include -// ============== 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: diff --git a/common/logger.h b/common/logger.h index e685d58..950efb5 100644 --- a/common/logger.h +++ b/common/logger.h @@ -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 diff --git a/linux/ScreenHandler.h b/linux/ScreenHandler.h index 8fdcb35..b491435 100644 --- a/linux/ScreenHandler.h +++ b/linux/ScreenHandler.h @@ -3,7 +3,7 @@ #include "client/IOCPClient.h" #include "LinuxConfig.h" #include "ClipboardHandler.h" -#include "FileTransferV2.h" +#include "common/FileTransferV2.h" #include #include #include diff --git a/linux/main.cpp b/linux/main.cpp index 292cce3..b346d68 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -26,9 +26,9 @@ #include #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" diff --git a/macos/CMakeLists.txt b/macos/CMakeLists.txt index d3bd762..eef3bcf 100644 --- a/macos/CMakeLists.txt +++ b/macos/CMakeLists.txt @@ -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" ) diff --git a/macos/ScreenHandler.mm b/macos/ScreenHandler.mm index 8e1f92b..a5cf032 100644 --- a/macos/ScreenHandler.mm +++ b/macos/ScreenHandler.mm @@ -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 #import @@ -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 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 allFiles; + std::vector 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; } diff --git a/macos/main.mm b/macos/main.mm index 13bc188..57302fd 100644 --- a/macos/main.mm +++ b/macos/main.mm @@ -22,7 +22,10 @@ #import "ScreenHandler.h" #import "InputHandler.h" #import "SystemManager.h" -#import "common/PTYHandler.h" +#import "../common/PTYHandler.h" +#import "../common/FileManager.h" +#import "../common/FileTransferV2.h" +#import "../common/logger.h" // Global state static std::atomic g_running(true); @@ -664,6 +667,26 @@ void* ScreenworkingThread(void* param) return NULL; } +void* FileManagerworkingThread(void* param) +{ + try { + std::unique_ptr 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 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) @@ -682,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);