Compare commits
2 Commits
9ae5529458
...
36423b1c7c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36423b1c7c | ||
|
|
a3611d9fc1 |
16
ReadMe.md
16
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
16
ReadMe_EN.md
16
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
16
ReadMe_TW.md
16
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
270
common/PTYHandler.h
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
188
linux/main.cpp
188
linux/main.cpp
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user