Feature: Add terminal support for macOS client with shared PTYHandler
This commit is contained in:
184
linux/main.cpp
184
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>
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user