Chapter 179 · XUI 专业控件

Terminal 进程与 SSH

终端与子进程桥接:ConPTY(Windows)/ PTY(POSIX)/ SSH 远程连接模式。

179

概念

Terminal 控件本身只负责渲染和输入,需与进程/远程连接桥接:
• ConPTY(Windows 10+):CreatePseudoConsole API 创建伪终端
• PTY(Linux/macOS):openpty / forkpty 创建伪终端对
• SSH:libssh2 等库建立远程 Shell 通道
数据流向:进程 stdout → TerminalWrite;用户输入 → InputCallback → 进程 stdin

API 速查

函数说明
int xuiTerminalResize(pW, iCols, iRows)同步终端尺寸
int xuiTerminalGetCursor(pW, pCol, pRow)获取光标位置
int xuiTerminalSetInputCallback(pW, onData, pUser)用户输入回调
int xuiTerminalWrite(pW, pData, iSize)进程输出→终端
int xuiTerminalFlush(pW)刷新显示

编译本章示例

gcc -O2 -I. -o ch179.exe ch179_main.c src\xui_*.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch179.exe

示例:ConPTY 桥接(Windows)

// 输入回调:用户键入 → 写入 ConPTY
static void OnTermInput(xui_widget pW,
    const void* pData, int iSize, void* pUser)
{
    HANDLE hIn = (HANDLE)pUser;
    DWORD written;
    WriteFile(hIn, pData, iSize, &written, NULL);
}

// 创建 ConPTY 后设置回调
xuiTerminalSetInputCallback(pTerm, OnTermInput, hConPTYIn);

// 读取线程:ConPTY 输出 → Terminal
void ReadThread(void* arg) {
    char buf[4096];
    DWORD nRead;
    while (ReadFile(hConPTYOut, buf, 4096, &nRead, NULL)) {
        xuiTerminalWrite(pTerm, buf, nRead);
        xuiTerminalFlush(pTerm);
    }
}

// 窗口大小变化时同步
xuiTerminalResize(pTerm, iNewCols, iNewRows);
// 同时调用 ResizePseudoConsole(hConPTY, coord)

完整代码

将以下代码保存为 ch179_main.c,编译后运行即可看到终端与 ConPTY 桥接效果(启动 cmd.exe):

#include "xge.h"
#include "xui.h"
#include <windows.h>

static xui_context g_pCtx;
static xui_widget g_pTerm;
static HANDLE g_hPTYIn;
static HANDLE g_hPTYOut;

static void onTermInput(const char* pData, int iSize, void* pUser) {
    (void)pUser;
    DWORD written;
    WriteFile(g_hPTYIn, pData, iSize, &written, NULL);
}

static DWORD WINAPI readThread(LPVOID arg) {
    (void)arg;
    char buf[4096];
    DWORD nRead;
    while (ReadFile(g_hPTYOut, buf, 4096, &nRead, NULL)) {
        xuiTerminalWrite(g_pTerm, buf, nRead);
        xuiTerminalFlush(g_pTerm);
    }
    return 0;
}

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x1E1E1EFF);
    xui_vec2_t pos;
    xgeMouseGet(&pos.x, &pos.y);
    xuiInputPointerMove(g_pCtx, pos.x, pos.y);
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        xuiInputPointerDown(g_pCtx, pos.x, pos.y, 0);
    else
        xuiInputPointerUp(g_pCtx, pos.x, pos.y, 0);
    xuiUpdate(g_pCtx, xgeGetDelta());
    xuiLayout(g_pCtx);
    xuiRender(g_pCtx, NULL, NULL, 0);
    return XGE_OK;
}

int main(void) {
    xge_desc_t d = { 0 };
    d.iWidth = 800; d.iHeight = 600;
    d.sTitle = "ch179 - ConPTY";
    d.iFlags = XGE_INIT_VSYNC;
    xgeInit(&d);

    xuiCreate(&g_pCtx);
    extern const xui_proxy_t tXgeProxy;
    xui_proxy_t tXgeProxy = xuiProxyXge();
xuiSetProxy(g_pCtx, &tXgeProxy);
    xuiSetViewportSize(g_pCtx, 800, 600);

    // 创建终端
    xui_terminal_desc_t tdesc = { 0 };
    xuiTerminalCreate(g_pCtx, &g_pTerm, &tdesc);
    xuiTerminalResize(g_pTerm, 80, 24);
    xuiTerminalSetInputCallback(g_pTerm, onTermInput, NULL);

    // 创建管道模拟 ConPTY(简化示例)
    SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, TRUE };
    CreatePipe(&g_hPTYOut, &g_hPTYIn, &sa, 0);

    // 启动读取线程
    CreateThread(NULL, 0, readThread, NULL, 0, NULL);

    // 写入欢迎信息
    xuiTerminalWriteText(g_pTerm, "ConPTY Bridge Demo\r\n");
    xuiTerminalFlush(g_pTerm);

    xui_widget pRoot = xuiGetRootWidget(g_pCtx);
    xuiWidgetAddChild(pRoot, g_pTerm);
    xuiWidgetSetRect(g_pTerm, (xui_rect_t){0, 0, 800, 600});

    xgeRun(onFrame, NULL);
    xuiDestroy(g_pCtx);
    xgeUnit();
    return 0;
}

代码讲解

  • InputCallback:用户键入数据通过回调写入 ConPTY/管道的 stdin 端。
  • readThread:后台线程持续读取进程 stdout,通过 TerminalWrite 显示。
  • CreatePipe:简化示例用管道模拟,实际应使用 CreatePseudoConsole API。
  • Resize 同步:窗口尺寸变化时需同时调用 TerminalResize 和 ResizePseudoConsole。

要点与陷阱

  • 线程安全:ReadThread 中调用 Write 需确保在 UI 线程执行(Post 消息)。
  • Resize 同步:Terminal 和 ConPTY/PTY 尺寸必须同步更新。
  • SSH:SSH 通道数据同样通过 Write/InputCallback 桥接。
  • 编码:ConPTY 输出 UTF-8,Terminal 默认 UTF-8 解码。

进阶练习

  1. 多终端标签:Tabs + 多个 Terminal 实例,每个连接不同 Shell。
  2. SSH 管理器:ComboBox 选择主机,连接远程 Shell。
Terminal 进程与 SSH效果
图:Terminal 进程与 SSH渲染效果