From 28538594d7b0302d46084e6b735641057d7d53bb Mon Sep 17 00:00:00 2001 From: ETWen Date: Thu, 4 Jun 2026 21:25:30 +0800 Subject: [PATCH] feat: Phase 9 Serial MCP server + app icon & version title (v0.2.0) Phase 9 (AI integration) complete -- AI agents can send/receive serial while the user watches live in the GUI, with the GUI as sole COM-port owner. - ETTerms.SerialMcp: stdio MCP server (net8.0 + ModelContextProtocol SDK) exposing serial_list / serial_attach / serial_write / serial_read (waitFor + timeoutMs) / serial_detach; forwards over named pipe. - GUI SerialBridgeServer (pipe etterms-serial) + SerialBridge endpoint; SessionPage.WriteFromAi echoes MCP-sourced TX tagged [AI] (magenta). - App icon: window title bar / taskbar / exe now use Choco_256x256.ico (embedded via AppAssets); title bar shows ETTerms Version vX.Y.Z. - About page: large Choco icon (Zoom, no crop). - Docs: ARCHITECTURE.md + CLAUDE.md mark Phase 9 done; docs/serial-mcp-guide.md. Co-Authored-By: Claude Opus 4.8 --- ARCHITECTURE.md | 23 +-- CLAUDE.md | 6 +- ETTerms.slnx | 1 + README.md | 30 ++-- README.zh-TW.md | 30 ++-- docs/serial-mcp-guide.md | 142 ++++++++++++++++++ .../ETTerms.SerialMcp.csproj | 17 +++ src/ETTerms.SerialMcp/Program.cs | 17 +++ src/ETTerms.SerialMcp/SerialBridgeClient.cs | 105 +++++++++++++ src/ETTerms.SerialMcp/SerialTools.cs | 35 +++++ src/ETTerms/App/AboutView.cs | 35 +++++ src/ETTerms/App/MainForm.cs | 16 ++ src/ETTerms/Assets/Choco_256x256.ico | Bin 0 -> 95926 bytes src/ETTerms/ETTerms.csproj | 10 +- src/ETTerms/Infrastructure/AppAssets.cs | 32 ++++ src/ETTerms/Sessions/SerialBridge.cs | 44 ++++++ src/ETTerms/Sessions/SerialBridgeServer.cs | 112 ++++++++++++++ src/ETTerms/Sessions/SerialChannel.cs | 6 + src/ETTerms/Sessions/SessionPage.cs | 17 +++ src/ETTerms/Terminal/ScreenBuffer.cs | 34 ++++- src/ETTerms/Terminal/TerminalView.cs | 116 +++++++++++++- 21 files changed, 777 insertions(+), 51 deletions(-) create mode 100644 docs/serial-mcp-guide.md create mode 100644 src/ETTerms.SerialMcp/ETTerms.SerialMcp.csproj create mode 100644 src/ETTerms.SerialMcp/Program.cs create mode 100644 src/ETTerms.SerialMcp/SerialBridgeClient.cs create mode 100644 src/ETTerms.SerialMcp/SerialTools.cs create mode 100644 src/ETTerms/Assets/Choco_256x256.ico create mode 100644 src/ETTerms/Infrastructure/AppAssets.cs create mode 100644 src/ETTerms/Sessions/SerialBridge.cs create mode 100644 src/ETTerms/Sessions/SerialBridgeServer.cs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c31771a..438e6d3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -142,7 +142,8 @@ ETTerms/ │ ├── ShellChannel.cs # Windows ConPTY 本機 Shell │ ├── SessionPage.cs # 一個分頁 = TerminalView + Channel + 狀態 │ ├── SessionManager.cs # 開 / 關 / 列舉所有 active session - │ └── SerialBridgeServer.cs# 🔜 本機 named pipe server:把 serial session 的讀寫橋接給 MCP(Phase 9) + │ ├── SerialBridgeServer.cs# ✅ 本機 named pipe server:把 serial session 的讀寫橋接給 MCP(Phase 9) + │ └── SerialBridge.cs # ✅ SerialBridgeEndpoint:單一 serial session 與 pipe 的橋接點 │ ├── Connections/ # ── 連線資料 ── │ ├── Connection.cs # 連線 metadata 模型 @@ -161,7 +162,7 @@ ETTerms/ ├── AppSettings.cs # 使用者偏好 (JSON, %LocalAppData%\ETTerms\settings.json) └── NativeTheme.cs # 深色標題列 (DWM) │ - └── ETTerms.SerialMcp/ # 🔜 Serial MCP server(stdio)——不自己開 port,經 named pipe 橋接 GUI + └── ETTerms.SerialMcp/ # ✅ Serial MCP server(stdio)——不自己開 port,經 named pipe 橋接 GUI ├── Program.cs # stdio MCP host 進入點 ├── SerialBridgeClient.cs # 連 GUI 的 named pipe,轉發 write / 接收 RX ├── SerialTools.cs # serial_list / attach / write / read / detach 工具(轉發到 pipe) @@ -584,16 +585,16 @@ dotnet publish src\ETTerms\ETTerms.csproj -c Release -r win-x64 --self-contained - [ ] `dotnet publish` + Inno Setup / MSIX 安裝程式(待使用者指示) **驗收條件:** ✅ PDU 指令可用;Settings 重啟保留;Local Shell 行為正常(ConPTY);SFTP 可瀏覽遠端目錄。打包待後續。 -### Phase 9 — AI 整合:Serial MCP Server(GUI 橋接模式)(工作量:M)🔜 規劃中 +### Phase 9 — AI 整合:Serial MCP Server(GUI 橋接模式)(工作量:M)✅ 已完成 **目標:** 讓 Kiro CLI / Claude CLI 等 AI agent 收發 serial,且**使用者能在 GUI 即時看到 AI 的收發**。COM port 由 GUI 唯一持有,MCP 經本機 named pipe 橋接(見 [AI / MCP Integration](#ai--mcp-integrationserial-mcp-server))。 **包含:** -- [ ] GUI:`Sessions/SerialBridgeServer.cs`——本機 named pipe server,暴露 list / attach / write / read,並把 serial session 的 RX 廣播給 client -- [ ] GUI:TerminalView 支援把 MCP 來源的 TX 以 `[AI]` 標色 echo,使用者可區分 AI 與手打輸入 -- [ ] `src/ETTerms.SerialMcp/`:.NET 8 console + 官方 C# MCP SDK(`ModelContextProtocol`),stdio / JSON-RPC -- [ ] `SerialBridgeClient.cs`:連 GUI pipe、轉發 write、累積 RX -- [ ] 工具:`serial_list` / `serial_attach` / `serial_write` / `serial_read`(含 `waitFor` + `timeoutMs`)/ `serial_detach` -- [ ] 註冊說明(`kiro-cli mcp add` / agent.json `mcpServers`)寫入 README,含「需先在 GUI 開好 port」前提 -**驗收條件:** GUI 開一條 Serial(COM3)→ 另一分頁 PowerShell 跑 kiro → AI 經 MCP `serial_attach` COM3 → `serial_write` 送指令、`serial_read` 讀回應,**整個過程在 GUI Tab1 即時可見(AI 的 TX 有 `[AI]` 標色)**;全程只有 GUI 開該 port。 +- [x] GUI:`Sessions/SerialBridgeServer.cs`——本機 named pipe server(`\\.\pipe\etterms-serial`),暴露 list / attach / write / read,並把 serial session 的 RX 廣播給 client;`SerialBridge.cs` 為單一 session 的橋接端點 +- [x] GUI:`SessionPage.WriteFromAi` 把 MCP 來源的 TX 以 `[AI]` 標色(`\x1b[35m`)echo,使用者可區分 AI 與手打輸入 +- [x] `src/ETTerms.SerialMcp/`:.NET 8 console + 官方 C# MCP SDK(`ModelContextProtocol`),stdio / JSON-RPC +- [x] `SerialBridgeClient.cs`:連 GUI pipe、轉發 write、背景累積 RX +- [x] 工具:`serial_list` / `serial_attach` / `serial_write` / `serial_read`(含 `waitFor` + `timeoutMs`)/ `serial_detach` +- [x] 註冊說明(`kiro-cli mcp add` / agent.json `mcpServers`)寫入 [docs/serial-mcp-guide.md](docs/serial-mcp-guide.md),含「需先在 GUI 開好 port」前提 +**驗收條件:** ✅ GUI 開一條 Serial(COM3)→ 另一分頁 PowerShell 跑 kiro → AI 經 MCP `serial_attach` COM3 → `serial_write` 送指令、`serial_read` 讀回應,**整個過程在 GUI Tab1 即時可見(AI 的 TX 有 `[AI]` 標色)**;全程只有 GUI 開該 port。 --- @@ -601,7 +602,7 @@ dotnet publish src\ETTerms\ETTerms.csproj -c Release -r win-x64 --self-contained 這個版本**不做、但未來可能加**: -- **AI / MCP 整合**(🔜 已列為 [Phase 9](#development-phases):Serial MCP Server,讓 AI agent 直接操作 serial;未來可再擴充 SSH / Shell MCP 工具) +- ~~**AI / MCP 整合**~~(✅ 已於 [Phase 9](#development-phases) 實作:Serial MCP Server,讓 AI agent 直接操作 serial;未來可再擴充 SSH / Shell MCP 工具) - ~~**SFTP 檔案瀏覽**~~(✅ 已於 Phase 8 實作:sidebar SFTP 分頁) - **Telnet** session 類型(補一個 `TelnetChannel : ISessionChannel`) - **RDP / VNC** 分頁(KKTerm 用 mstscax.dll;ETTerms 可後期評估) diff --git a/CLAUDE.md b/CLAUDE.md index 433ee52..20f8ce8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,9 @@ ETTerms 是一個 **C# .NET 8 WinForms** 的原生 Windows 終端機工作台, **開發策略:GUI 先行** — 先把視窗外殼 + 分頁 + 連線清單做出來,再逐步補 Serial → SSH → VT100 → 腳本引擎 → Settings/About → PDU/Shell/SFTP。 -**進度:** Phase 1–5 ✅、Phase 6 ✅(TTL 引擎 + Group 同步,SSH 待驗收)、Phase 7 ✅(Settings/About)、Phase 8 ✅(PDU + Shell/ConPTY + SFTP + Settings 擴充)、Phase 9 🔜(Serial MCP server:**GUI 持有 COM port,MCP 經本機 named pipe 橋接**,AI 收發的資料即時顯示在 GUI)。打包待指示。 +**進度:** Phase 1–5 ✅、Phase 6 ✅(TTL 引擎 + Group 同步,SSH 待驗收)、Phase 7 ✅(Settings/About)、Phase 8 ✅(PDU + Shell/ConPTY + SFTP + Settings 擴充)、Phase 9 ✅(Serial MCP server:**GUI 持有 COM port,MCP 經本機 named pipe 橋接**,AI 收發的資料即時以 `[AI]` 標色顯示在 GUI)。打包待指示。 + +**v0.2.0:** Phase 9 完成 — `ETTerms.SerialMcp`(stdio MCP server)+ GUI `SerialBridgeServer`(named pipe `\\.\pipe\etterms-serial`)上線,提供 `serial_list` / `serial_attach` / `serial_write` / `serial_read` / `serial_detach` 五個工具,AI 的 TX 在 GUI 以 `[AI]` 標色即時 echo;視窗 / 工作列 / About 改用 Choco 圖示,標題列顯示版本號。見 [docs/serial-mcp-guide.md](docs/serial-mcp-guide.md)。 **v0.1.2:** 新增 `sprintf2`(TeraTerm 相容 C printf 格式化);`wait` 改為命中關鍵字後須等裝置安靜(`SettleMs` 預設 300ms)才接受並取「最後一次」出現,排除輸出中途的指令回顯(避免腳本搶跑)。 @@ -65,6 +67,6 @@ kiro-cli mcp add --name serial --command dotnet --args "run --project src\ETTerm ## 資料夾用途 - **`src/ETTerms/`** — 主應用程式(WinForms 視窗外殼 + 連線 / 終端機 / 腳本引擎)。 -- **`src/ETTerms.SerialMcp/`** — 🔜 stdio MCP server(給 AI agent 收發 serial)。獨立行程,但**不直接開 COM port**:經本機 named pipe 連到 GUI 的 `SerialBridgeServer`,由 GUI 代為讀寫實體 port;net8.0 console + `ModelContextProtocol` SDK。 +- **`src/ETTerms.SerialMcp/`** — ✅ stdio MCP server(給 AI agent 收發 serial)。獨立行程,但**不直接開 COM port**:經本機 named pipe 連到 GUI 的 `SerialBridgeServer`,由 GUI 代為讀寫實體 port;net8.0 console + `ModelContextProtocol` SDK。 - **`For_AI/`** — AI 協作素材與**參考專案**(`KKTerm-main` UI 參考、`MyTeraTerm` Script 參考)。整個資料夾 gitignored,僅供開發對照。 - 本專案**無 `secret/` 資料夾**:沒有伺服端祕密 / DB 密碼 / compile-time secret,連線密碼一律走 Windows Credential Manager。 diff --git a/ETTerms.slnx b/ETTerms.slnx index e6d6051..497b194 100644 --- a/ETTerms.slnx +++ b/ETTerms.slnx @@ -1,5 +1,6 @@ + diff --git a/README.md b/README.md index e9d9a8a..41fff74 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,9 @@ - Script commands: `pduconnect` / `pductrl` - 🤖 **AI / MCP integration (optional, planned)** - - A standalone stdio **Serial MCP server** exposes the serial port as AI-callable tools for **Kiro CLI / Claude CLI** - - Tools: `serial_list` / `serial_open` / `serial_write` / `serial_read` / `serial_close` - - Just tell the AI to open a COM port, send a command, and read the reply + - The **GUI owns the COM port**; a standalone stdio **Serial MCP server** bridges to it over a local named pipe for **Kiro CLI / Claude CLI** + - Tools: `serial_list` / `serial_attach` / `serial_write` / `serial_read` / `serial_detach` + - AI's serial TX/RX shows live in the GUI tagged `[AI]` — open the port in the GUI first, then let the AI attach - 📊 **Session RX logging** - `logopen` / `logwrite` / `logclose` write session output to file @@ -271,19 +271,21 @@ wait 'login: ' ## 🤖 AI / MCP Integration -> 🔜 **Planned (Phase 9).** Let AI agents (**Kiro CLI / Claude CLI**) drive the serial port directly — issue commands and read output. +> 🔜 **Planned (Phase 9).** Let AI agents (**Kiro CLI / Claude CLI**) send/receive on a serial port **while you watch it live in the ETTerms GUI**. -ETTerms ships a standalone **stdio MCP server** (`src/ETTerms.SerialMcp/`) that wraps the serial port as AI-callable tools. It runs as its own process, kept alive by the MCP client for the whole session, so it holds the COM port open across calls and can capture asynchronous device output. +**Key design: the GUI owns the COM port; the MCP server never opens it.** A COM port can be opened by only one process at a time, so rather than the MCP server grabbing it, the GUI is the sole owner and runs a local **named pipe server** (`SerialBridgeServer`). The standalone `ETTerms.SerialMcp` (launched by Kiro/Claude CLI) is a thin client: every `write` / `read` is forwarded over the pipe to the GUI, which does the real port I/O. AI's TX is echoed into the terminal tagged `[AI]`, so RX/TX flow through the GUI channel and what you see is exactly what the AI sees. -> **Why a separate process?** Each CLI shell command is a fresh process, so `open→write→read` can't keep state across calls. A long-lived MCP server maintains one connection and accumulates received bytes. +Full closed loop — all inside the ETTerms GUI: +- **Tab 1:** a Serial session holding e.g. COM3 (shows traffic live; AI's writes tagged `[AI]`) +- **Tab 2:** a PowerShell (ConPTY) tab running `kiro-cli`, which spawns `ETTerms.SerialMcp`, which connects back to the pipe | Tool | Params | Description | |------|--------|-------------| -| `serial_list` | — | List available COM ports | -| `serial_open` | portName, baudRate, dataBits, parity, stopBits, handshake, newLine | Open and hold the port | -| `serial_write` | text, appendNewLine? | Send text (optional newline) | -| `serial_read` | waitFor?, timeoutMs? | Drain the RX buffer; optionally wait for a string / timeout | -| `serial_close` | — | Close the port | +| `serial_list` | — | List serial sessions currently **open in the GUI** (name + baud) | +| `serial_attach` | portName | Bind to a GUI serial session by COM name (required before write/read) | +| `serial_write` | text, appendNewline? | Send text via the GUI (shown live tagged `[AI]`) | +| `serial_read` | waitFor?, timeoutMs? | Drain accumulated RX; optionally wait for a substring / timeout | +| `serial_detach` | — | Unbind (does **not** close the GUI's port) | Register in Kiro CLI: @@ -291,9 +293,11 @@ Register in Kiro CLI: kiro-cli mcp add --name serial --command dotnet --args "run --project src\ETTerms.SerialMcp\ETTerms.SerialMcp.csproj" ``` -Or add it to `agent.json` under `mcpServers` (Claude CLI uses an equivalent `mcpServers` config). Then just tell the AI: *"list COM ports, open COM3 at 115200, send `AT` and read the reply."* +Or add it to `agent.json` under `mcpServers` (Claude CLI uses an equivalent `mcpServers` config). **Open the target serial connection in the ETTerms GUI first**, then tell the AI: *"list serial sessions, attach to COM3, send `AT` and read the reply."* -> ⚠️ A COM port can be opened by only one process at a time — don't open the same port in both the GUI and the MCP server. +> ⚠️ The COM port is owned solely by the GUI. The MCP server attaches over the pipe — it never opens the port itself, so there's no GUI-vs-server conflict. + +📖 Step-by-step setup: [docs/serial-mcp-guide.md](docs/serial-mcp-guide.md). --- diff --git a/README.zh-TW.md b/README.zh-TW.md index f1268a8..dc6c208 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -66,9 +66,9 @@ - 腳本指令:`pduconnect` / `pductrl` - 🤖 **AI / MCP 整合(選用,規劃中)** - - 獨立的 stdio **Serial MCP server** 把 serial port 暴露成 AI 可呼叫工具,供 **Kiro CLI / Claude CLI** 使用 - - 工具:`serial_list` / `serial_open` / `serial_write` / `serial_read` / `serial_close` - - 直接對 AI 說「開某個 COM port、送指令、讀回應」即可 + - **COM port 由 GUI 持有**;獨立的 stdio **Serial MCP server** 經本機 named pipe 橋接過去,供 **Kiro CLI / Claude CLI** 使用 + - 工具:`serial_list` / `serial_attach` / `serial_write` / `serial_read` / `serial_detach` + - AI 的 serial 收發會即時顯示在 GUI(標 `[AI]`)——先在 GUI 開好 port,再讓 AI attach - 📊 **連線 RX 日誌** - `logopen` / `logwrite` / `logclose` 將工作階段輸出寫檔 @@ -271,19 +271,21 @@ wait 'login: ' ## 🤖 AI / MCP 整合 -> 🔜 **規劃中(Phase 9)。** 讓 AI agent(**Kiro CLI / Claude CLI**)直接對 serial port 下指令、讀輸出。 +> 🔜 **規劃中(Phase 9)。** 讓 AI agent(**Kiro CLI / Claude CLI**)收發 serial,**而且你能在 ETTerms GUI 即時看到 AI 的每筆收發**。 -ETTerms 額外提供一支獨立的 **stdio MCP server**(`src/ETTerms.SerialMcp/`),把 serial port 包成 AI 可呼叫的工具。它與 WinForms 主程式**各自獨立行程**:由 MCP client(Kiro CLI / Claude CLI)啟動並維持整個 session 存活,因此能**持續持有 COM port**——連線狀態可跨多次工具呼叫保留,也能接收裝置主動推送的非同步輸出。 +**關鍵設計:COM port 由 GUI 唯一持有,MCP server 不自己開 port。** 一個 COM port 同時只能被一個行程開啟;因此不讓 MCP server 自己開,而是 **GUI 當 port 的唯一擁有者**,在 GUI 內跑一支本機 **named pipe server**(`SerialBridgeServer`)。獨立的 `ETTerms.SerialMcp`(由 Kiro/Claude CLI 啟動)退化成瘦客戶端:所有 `write` / `read` 都經 pipe 轉發給 GUI,由 GUI 代為讀寫實體 port。AI 的 TX 會以 `[AI]` 標色 echo 進終端機,RX/TX 都流經 GUI channel,**你看到的就是 AI 看到的**。 -> **為何要獨立常駐行程?** CLI agent 的每條 shell 指令都是新行程,`open→write→read` 無法跨呼叫保留狀態(port 一關就斷)。常駐的 MCP server 才能維持一條連線、累積接收緩衝。 +全閉迴路——全部跑在 ETTerms GUI 內: +- **Tab1:** 一條 Serial 連線持有 COM3(即時顯示流量,AI 的寫入標 `[AI]`) +- **Tab2:** 一個 PowerShell(ConPTY)分頁跑 `kiro-cli`,它啟動 `ETTerms.SerialMcp`,再連回 pipe | 工具 | 參數 | 說明 | |------|------|------| -| `serial_list` | — | 列出可用 COM port | -| `serial_open` | portName, baudRate, dataBits, parity, stopBits, handshake, newLine | 開啟並持有 port | -| `serial_write` | text, appendNewLine? | 送出文字(可選附加換行) | -| `serial_read` | waitFor?, timeoutMs? | 取出 RX 緩衝;可等待特定字串或逾時 | -| `serial_close` | — | 關閉 port | +| `serial_list` | — | 列出 **GUI 目前開著的** serial session(名稱 + baud) | +| `serial_attach` | portName | 以 COM 名稱綁定到 GUI 的 serial session(write/read 前必須先 attach) | +| `serial_write` | text, appendNewline? | 經 GUI 送出文字(即時以 `[AI]` 標色顯示) | +| `serial_read` | waitFor?, timeoutMs? | 取出累積的 RX;可等待子字串或逾時 | +| `serial_detach` | — | 解除綁定(**不會**關閉 GUI 的 port) | 在 Kiro CLI 註冊: @@ -291,9 +293,11 @@ ETTerms 額外提供一支獨立的 **stdio MCP server**(`src/ETTerms.SerialMc kiro-cli mcp add --name serial --command dotnet --args "run --project src\ETTerms.SerialMcp\ETTerms.SerialMcp.csproj" ``` -或寫進 `agent.json` 的 `mcpServers`(Claude CLI 用其對應的 `mcpServers` 設定)。註冊後直接對 AI 說:*「列出 COM port、開 COM3 115200、送 `AT` 看回應」*。 +或寫進 `agent.json` 的 `mcpServers`(Claude CLI 用其對應的 `mcpServers` 設定)。**先在 ETTerms GUI 開好要操作的 serial 連線**,再對 AI 說:*「列出 serial session、接上 COM3、送 `AT` 看回應」*。 -> ⚠️ 一個 COM port 同時只能被一個行程開啟——別在 GUI 與 MCP server 同時開同一個 port。 +> ⚠️ COM port 由 GUI 唯一持有;MCP server 經 pipe attach,自己不開 port,因此不會與 GUI 衝突。 + +📖 完整註冊與使用步驟:[docs/serial-mcp-guide.md](docs/serial-mcp-guide.md)。 --- diff --git a/docs/serial-mcp-guide.md b/docs/serial-mcp-guide.md new file mode 100644 index 0000000..07ab3f4 --- /dev/null +++ b/docs/serial-mcp-guide.md @@ -0,0 +1,142 @@ +# Serial MCP 使用手冊 + +> 讓 **Kiro CLI / Claude CLI** 等 AI agent 直接收發 serial,**而且你能在 ETTerms GUI 即時看到 AI 的每一筆收發**。 + +--- + +## 這是什麼 + +`ETTerms.SerialMcp` 是一支獨立的 stdio MCP server。重點設計: + +- **COM port 由 ETTerms GUI 唯一持有**,MCP server 自己**不開 port**。 +- GUI 內跑一支本機 named pipe(`\\.\pipe\etterms-serial`);MCP server 經這條 pipe 把 `attach / write / read` 轉發給 GUI,由 GUI 代為讀寫實體 port。 +- AI 送出的資料會以 `[AI]` 標色 echo 進 GUI 終端機,所以**你看到的就是 AI 看到的**。 + +``` +ETTerms GUI(唯一開 COM32 的人) +├─ Tab1: Serial 連線 COM32(即時顯示,AI 的寫入標 [AI]) +├─ named pipe: \\.\pipe\etterms-serial +└─ Tab2: PowerShell 跑 kiro-cli → 啟動 ETTerms.SerialMcp → 連回 pipe +``` + +--- + +## 前置需求 + +1. 已安裝 .NET 8(`dotnet --list-runtimes` 有 `WindowsDesktop 8.0.x`)。 +2. 已編譯出 MCP 執行檔: + ```powershell + cd <你的 repo>\ETTerms + dotnet build ETTerms.slnx + ``` + 產物在:`src\ETTerms.SerialMcp\bin\Debug\net8.0\ETTerms.SerialMcp.exe` +3. 已安裝並可在終端機執行 `kiro-cli`。 + +--- + +## 步驟 1:註冊 MCP 到 Kiro + +用**編譯好的 exe** 註冊(不要用 `dotnet run`——它會把建置訊息印到 stdout,污染 JSON-RPC)。 +建議用 **global** scope,這樣不論從哪個工作目錄啟動 kiro 都抓得到: + +```powershell +kiro-cli mcp add --name serial --scope global --force ` + --command "<你的 repo>\ETTerms\src\ETTerms.SerialMcp\bin\Debug\net8.0\ETTerms.SerialMcp.exe" +``` + +成功會顯示: +``` +✓ Added MCP server 'serial' to global config in C:\Users\\.kiro\settings\mcp.json +``` + +> 設定檔位置: +> - global:`~\.kiro\settings\mcp.json` +> - workspace:`<專案>\.kiro\settings\mcp.json`(只在該目錄啟動 kiro 才生效) + +--- + +## 步驟 2:重啟 kiro session + +⚠️ **MCP server 只在 kiro session 啟動時載入。** 若你在註冊前就開著 kiro chat,必須重開: + +1. 在 kiro chat 內輸入 `/quit` 回到 PowerShell。 +2. 重新執行 `kiro-cli`。 +3. 輸入 `/mcp`,應看到: + ``` + serial Status: ✓ Initialized + Tools: serial_list, serial_attach, serial_write, serial_read, serial_detach + ``` + +> `/mcp` 顯示 Initialized 只代表 MCP server 起得來,**還沒**連 pipe(連 pipe 是第一次呼叫 serial 工具時才發生)。 + +--- + +## 步驟 3:使用 + +**先在 ETTerms GUI 開好要操作的 serial 連線**(雙擊側欄連線開分頁),AI 才能 attach 上去。 + +然後直接對 AI 講白話即可,例如: + +> 列出目前的 serial session,接上 COM32,送 `root` 然後讀回應。 + +AI 會依序呼叫 `serial_list` → `serial_attach` → `serial_write` → `serial_read`, +而你在 GUI 的 COM32 分頁會即時看到紫色 `[AI] root` 與裝置回應。 + +### ⚠️ 重要:session 名稱 = COM port 名稱 + +GUI 裡連線的**顯示名稱**(例如 `BP1`)與橋接用的**session 名稱不同**。 +`serial_list` 回傳的是 **COM port 名稱**(例如 `COM32`),attach 時要用它: + +| GUI 顯示 | serial_list 回傳 / attach 用 | +|---------|------------------------------| +| BP1 | `COM32` | +| BP2 | `COM17` | + +--- + +## 工具清單 + +| 工具 | 參數 | 說明 | +|------|------|------| +| `serial_list` | — | 列出 GUI 目前開著的 serial session(名稱 + baud) | +| `serial_attach` | `portName`(如 `COM32`) | 綁定到某個 GUI session;write/read 前必須先 attach | +| `serial_write` | `text`, `appendNewline`(預設 true) | 送出文字(GUI 以 `[AI]` 標色顯示) | +| `serial_read` | `waitFor`?(等待子字串), `timeoutMs`?(預設 3000) | 取出累積的 RX,回傳後清空 buffer | +| `serial_detach` | — | 解除綁定(**不會**關閉 GUI 的 port) | + +--- + +## 疑難排解 + +| 症狀 | 原因 | 解法 | +|------|------|------| +| `/mcp` 顯示 `0 configured` | 還沒註冊,或 kiro session 早於註冊就開了 | 執行步驟 1,再 `/quit` 重開 kiro | +| `/mcp` 註冊了但仍看不到 | 用了 workspace scope,但 kiro 的工作目錄不同 | 改用 `--scope global --force` 重註冊 | +| 工具回 `bridge not connected; is the ETTerms GUI running?` | ETTerms GUI 沒開 | 先開 GUI(GUI 啟動時才會起 pipe server) | +| 工具回 `no open serial session '...' in GUI` | 該 COM port 在 GUI 沒開,或名稱打錯 | 先在 GUI 開該連線;用 `serial_list` 確認正確的 COM 名稱 | +| pipe 行為怪異 / 收不到 | 同時有別的程式佔用 pipe | pipe 一次只服務一個 client,關掉其他連 `etterms-serial` 的程式 | +| 註冊後 stdout 有奇怪輸出 | 用了 `dotnet run` 當 command | 改用編譯好的 `.exe` 路徑 | + +--- + +## 移除 + +```powershell +kiro-cli mcp remove --name serial +``` + +--- + +## 進階:用 agent.json 設定(替代 mcp add) + +也可直接寫進 agent 設定的 `mcpServers`(global:`~\.kiro\settings\mcp.json`): + +```json +{ + "mcpServers": { + "serial": { + "command": "<你的 repo>\\ETTerms\\src\\ETTerms.SerialMcp\\bin\\Debug\\net8.0\\ETTerms.SerialMcp.exe" + } + } +} +``` diff --git a/src/ETTerms.SerialMcp/ETTerms.SerialMcp.csproj b/src/ETTerms.SerialMcp/ETTerms.SerialMcp.csproj new file mode 100644 index 0000000..acd2b24 --- /dev/null +++ b/src/ETTerms.SerialMcp/ETTerms.SerialMcp.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + ETTerms.SerialMcp + ETTerms.SerialMcp + + + + + + + + diff --git a/src/ETTerms.SerialMcp/Program.cs b/src/ETTerms.SerialMcp/Program.cs new file mode 100644 index 0000000..c62c723 --- /dev/null +++ b/src/ETTerms.SerialMcp/Program.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +// stdio MCP server:把 ETTerms GUI 持有的 serial session 暴露給 AI(Kiro / Claude CLI)。 +// 自己不開 COM port,所有收發經 named pipe 轉給 GUI 的 SerialBridgeServer。 +var builder = Host.CreateApplicationBuilder(args); + +// stdio 傳輸:stdout 專供 JSON-RPC,log 一律走 stderr,否則會污染協議。 +builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace); + +builder.Services + .AddMcpServer() + .WithStdioServerTransport() + .WithToolsFromAssembly(); + +await builder.Build().RunAsync(); diff --git a/src/ETTerms.SerialMcp/SerialBridgeClient.cs b/src/ETTerms.SerialMcp/SerialBridgeClient.cs new file mode 100644 index 0000000..af767e4 --- /dev/null +++ b/src/ETTerms.SerialMcp/SerialBridgeClient.cs @@ -0,0 +1,105 @@ +using System.IO.Pipes; +using System.Text; +using System.Text.Json; + +namespace ETTerms.SerialMcp; + +/// +/// 連到 ETTerms GUI 的 SerialBridgeServer(named pipe \\.\pipe\etterms-serial)。 +/// 轉發 list / attach / write / detach,背景累積 RX 供 serial_read 消費。 +/// 單例:整個 MCP server 生命週期共用同一條 pipe,連線狀態跨工具呼叫保留。 +/// +public sealed class SerialBridgeClient +{ + public static readonly SerialBridgeClient Instance = new(); + private const string PipeName = "etterms-serial"; + + private readonly SemaphoreSlim _connLock = new(1, 1); + private readonly SemaphoreSlim _reqLock = new(1, 1); + private readonly object _wlock = new(); + private readonly object _rxlock = new(); + private readonly StringBuilder _rx = new(); + + private StreamWriter? _writer; + private TaskCompletionSource? _pending; + + private async Task EnsureConnected() + { + await _connLock.WaitAsync(); + try + { + if (_writer != null) return; + var pipe = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.Asynchronous); + await pipe.ConnectAsync(3000); + _writer = new StreamWriter(pipe, new UTF8Encoding(false)) { AutoFlush = true }; + _ = Task.Run(() => ReadLoop(new StreamReader(pipe, new UTF8Encoding(false)), pipe)); + } + finally { _connLock.Release(); } + } + + private async Task ReadLoop(StreamReader reader, NamedPipeClientStream pipe) + { + try + { + string? line; + while ((line = await reader.ReadLineAsync()) != null) + { + JsonElement doc; + try { doc = JsonDocument.Parse(line).RootElement.Clone(); } + catch { continue; } + if (doc.TryGetProperty("op", out var op) && op.GetString() == "rx") + { + var d = doc.TryGetProperty("data", out var dd) ? dd.GetString() ?? "" : ""; + lock (_rxlock) _rx.Append(d); + } + else _pending?.TrySetResult(doc); + } + } + catch { } + finally { lock (_wlock) _writer = null; try { pipe.Dispose(); } catch { } } + } + + private async Task Request(object req) + { + await EnsureConnected(); + await _reqLock.WaitAsync(); + try + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _pending = tcs; + lock (_wlock) + { + if (_writer == null) throw new IOException("bridge not connected; is the ETTerms GUI running?"); + _writer.WriteLine(JsonSerializer.Serialize(req)); + } + using var cts = new CancellationTokenSource(5000); + using var reg = cts.Token.Register(() => tcs.TrySetException(new TimeoutException("no response from ETTerms bridge"))); + return (await tcs.Task).GetRawText(); + } + finally { _pending = null; _reqLock.Release(); } + } + + public Task List() => Request(new { op = "list" }); + public Task Attach(string portName) => Request(new { op = "attach", session = portName }); + public Task Write(string text, bool newline) => Request(new { op = "write", data = text, newline }); + public Task Detach() => Request(new { op = "detach" }); + + /// 取出累積的 RX;可等待子字串或逾時。回傳後清空 buffer。 + public async Task Read(string? waitFor, int timeoutMs) + { + await EnsureConnected(); + var deadline = DateTime.UtcNow.AddMilliseconds(timeoutMs <= 0 ? 1000 : timeoutMs); + while (true) + { + string snap; + lock (_rxlock) snap = _rx.ToString(); + bool hit = string.IsNullOrEmpty(waitFor) ? snap.Length > 0 : snap.Contains(waitFor); + if (hit || DateTime.UtcNow >= deadline) + { + lock (_rxlock) _rx.Clear(); + return snap; + } + await Task.Delay(50); + } + } +} diff --git a/src/ETTerms.SerialMcp/SerialTools.cs b/src/ETTerms.SerialMcp/SerialTools.cs new file mode 100644 index 0000000..f96f14a --- /dev/null +++ b/src/ETTerms.SerialMcp/SerialTools.cs @@ -0,0 +1,35 @@ +using System.ComponentModel; +using ModelContextProtocol.Server; + +namespace ETTerms.SerialMcp; + +/// +/// 暴露給 AI 的 serial 工具。COM port 由 ETTerms GUI 持有,這些工具經 named pipe 橋接過去; +/// AI 的寫入會即時顯示在 GUI 終端機(標 [AI])。使用前須先在 GUI 開好該 serial 連線。 +/// +[McpServerToolType] +public static class SerialTools +{ + [McpServerTool, Description("List serial sessions currently open in the ETTerms GUI (name + baud). The GUI owns the COM port.")] + public static Task serial_list() => SerialBridgeClient.Instance.List(); + + [McpServerTool, Description("Attach to a serial session already open in the ETTerms GUI by COM port name (e.g. COM3). Required before write/read. Does not open the port itself.")] + public static Task serial_attach( + [Description("COM port name of an already-open GUI session, e.g. COM3")] string portName) + => SerialBridgeClient.Instance.Attach(portName); + + [McpServerTool, Description("Send text to the attached serial session. The GUI displays it live tagged [AI].")] + public static Task serial_write( + [Description("Text to send")] string text, + [Description("Append the session newline (acts like pressing Enter)")] bool appendNewline = true) + => SerialBridgeClient.Instance.Write(text, appendNewline); + + [McpServerTool, Description("Read output accumulated from the attached serial session. Optionally wait for a substring up to timeoutMs. Returns and clears the buffer.")] + public static Task serial_read( + [Description("Substring to wait for; null/empty returns whatever has arrived")] string? waitFor = null, + [Description("Max time to wait in milliseconds")] int timeoutMs = 3000) + => SerialBridgeClient.Instance.Read(waitFor, timeoutMs); + + [McpServerTool, Description("Detach from the serial session. Does NOT close the GUI's port (the GUI keeps owning it).")] + public static Task serial_detach() => SerialBridgeClient.Instance.Detach(); +} diff --git a/src/ETTerms/App/AboutView.cs b/src/ETTerms/App/AboutView.cs index 81da377..49c70e7 100644 --- a/src/ETTerms/App/AboutView.cs +++ b/src/ETTerms/App/AboutView.cs @@ -1,6 +1,7 @@ using System.Drawing; using System.Reflection; using System.Windows.Forms; +using ETTerms.Infrastructure; namespace ETTerms.App; @@ -54,6 +55,9 @@ public sealed class AboutView : UserControl p.Controls.Add(MakeLine(" TTL Script Engine (ported from MyTeraTerm)", Theme.UiFont, Theme.TextDim)); })); + // Brand card — large app icon + logo + left.Controls.Add(MakeBrandCard()); + // ═══ RIGHT PANEL — Changelog (scrollable) ═══ var right = new FlowLayoutPanel { @@ -110,6 +114,37 @@ public sealed class AboutView : UserControl return card; } + /// 底部品牌卡:大的 Choco 圖示 + ETTerms 標誌圖。 + private static Panel MakeBrandCard() + { + var card = new Panel + { + Width = 340, Height = 300, Margin = new Padding(0, 0, 0, 12), + BackColor = Theme.TabBack, Padding = new Padding(12) + }; + var flow = new FlowLayoutPanel + { + Dock = DockStyle.Fill, FlowDirection = FlowDirection.TopDown, + WrapContents = false, BackColor = Theme.TabBack, AutoSize = false + }; + + // 大圖示 (256x256),等比縮放塞滿框、不裁切 + var iconImg = AppAssets.AppIcon(256); + if (iconImg != null) + { + flow.Controls.Add(new PictureBox + { + Width = 312, Height = 268, Margin = new Padding(0, 4, 0, 4), + SizeMode = PictureBoxSizeMode.Zoom, + BackColor = Theme.TabBack, Image = iconImg.ToBitmap() + }); + iconImg.Dispose(); + } + + card.Controls.Add(flow); + return card; + } + private static Label MakeLine(string text, Font font, Color color, ContentAlignment align = ContentAlignment.MiddleLeft) { return new Label diff --git a/src/ETTerms/App/MainForm.cs b/src/ETTerms/App/MainForm.cs index 93534b1..f754f7d 100644 --- a/src/ETTerms/App/MainForm.cs +++ b/src/ETTerms/App/MainForm.cs @@ -1,3 +1,4 @@ +using System.Reflection; using System.Windows.Forms; using ETTerms.App.Workspace; using ETTerms.Infrastructure; @@ -17,16 +18,30 @@ public partial class MainForm : Form private readonly AboutView _about = new(); private readonly StatusStrip _status = new(); private readonly ToolStripStatusLabel _statusLabel = new(); + private readonly Sessions.SerialBridgeServer _bridge = new(); public MainForm() { InitializeComponent(); + ApplyIconAndTitle(); BuildLayout(); WireEvents(); AppSettings.Instance.ApplyWindowPosition(this); + _bridge.Start(); // 本機 named pipe,供 ETTerms.SerialMcp 橋接 serial AppLogger.Info("MainForm initialized"); } + /// 設定視窗 / 工作列圖示,並在標題列加上版本號。 + private void ApplyIconAndTitle() + { + var icon = AppAssets.AppIcon(); + if (icon != null) Icon = icon; + + var v = Assembly.GetExecutingAssembly().GetName().Version; + var versionStr = v != null ? $"{v.Major}.{v.Minor}.{v.Build}" : "0.1.0"; + Text = $"ETTerms Version v{versionStr}"; + } + private void BuildLayout() { Controls.Add(_workspace); // Fill @@ -74,6 +89,7 @@ public partial class MainForm : Form protected override void OnFormClosed(FormClosedEventArgs e) { + _bridge.Dispose(); AppSettings.Instance.SaveWindowPosition(this); AppLogger.LogApplicationClose(); base.OnFormClosed(e); diff --git a/src/ETTerms/Assets/Choco_256x256.ico b/src/ETTerms/Assets/Choco_256x256.ico new file mode 100644 index 0000000000000000000000000000000000000000..4e596b6cf3b0dabfd48560bacac46d6764e1cb36 GIT binary patch literal 95926 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 +/// 存取內嵌進組件的圖示(見 ETTerms.csproj 的 EmbeddedResource)。 +/// Manifest 名稱規則:RootNamespace.資料夾.檔名 → ETTerms.Assets.xxx。 +/// +public static class AppAssets +{ + private const string IconResource = "ETTerms.Assets.Choco_256x256.ico"; + + /// 視窗 / 工作列圖示。找不到資源時回傳 null。 + public static Icon? AppIcon() => LoadIcon(IconResource); + + /// Choco 圖示,指定尺寸(About 用大圖)。 + public static Icon? AppIcon(int size) + { + var icon = LoadIcon(IconResource); + if (icon == null) return null; + var sized = new Icon(icon, new Size(size, size)); + icon.Dispose(); + return sized; + } + + private static Icon? LoadIcon(string name) + { + using var s = Assembly.GetExecutingAssembly().GetManifestResourceStream(name); + return s == null ? null : new Icon(s); + } +} diff --git a/src/ETTerms/Sessions/SerialBridge.cs b/src/ETTerms/Sessions/SerialBridge.cs new file mode 100644 index 0000000..4a6a61c --- /dev/null +++ b/src/ETTerms/Sessions/SerialBridge.cs @@ -0,0 +1,44 @@ +namespace ETTerms.Sessions; + +/// +/// 一個可被 MCP 橋接的 serial session 端點:提供寫入(含 [AI] echo 回 GUI)與 RX 轉發。 +/// 由 建立並註冊到 。 +/// +public sealed class SerialBridgeEndpoint +{ + private readonly Action _write; + + public string Name { get; } + public int BaudRate { get; } + + /// 實體 port 收到的資料(背景緒觸發),由 pipe server 廣播給 MCP client。 + public event Action? Rx; + + public SerialBridgeEndpoint(string name, int baudRate, Action write) + { + Name = name; + BaudRate = baudRate; + _write = write; + } + + public void Write(string text, bool appendNewline) => _write(text, appendNewline); + + public void FeedRx(byte[] data) => Rx?.Invoke(data); +} + +/// +/// GUI 目前開著、可供 MCP 橋接的 serial session 登錄表。port 永遠由 GUI 持有, +/// MCP(ETTerms.SerialMcp)經 named pipe attach 到這裡的端點來收發。 +/// +public static class SerialBridge +{ + private static readonly object _lock = new(); + private static readonly List _eps = new(); + + public static void Register(SerialBridgeEndpoint ep) { lock (_lock) _eps.Add(ep); } + public static void Unregister(SerialBridgeEndpoint ep) { lock (_lock) _eps.Remove(ep); } + public static SerialBridgeEndpoint[] All { get { lock (_lock) return _eps.ToArray(); } } + + public static SerialBridgeEndpoint? Find(string name) => + All.FirstOrDefault(e => string.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase)); +} diff --git a/src/ETTerms/Sessions/SerialBridgeServer.cs b/src/ETTerms/Sessions/SerialBridgeServer.cs new file mode 100644 index 0000000..bd1e842 --- /dev/null +++ b/src/ETTerms/Sessions/SerialBridgeServer.cs @@ -0,0 +1,112 @@ +using System.IO; +using System.IO.Pipes; +using System.Text; +using System.Text.Json; +using ETTerms.Infrastructure; + +namespace ETTerms.Sessions; + +/// +/// 本機 named pipe server:把 GUI 持有的 serial session 橋接給 MCP client(ETTerms.SerialMcp)。 +/// 協議=換行分隔 JSON(list / attach / write / detach),RX 由本端主動以 {"op":"rx"} 推送。 +/// 一次服務一個 client(單一 AI agent 已足夠),斷線後自動回到等待下一個連線。 +/// +public sealed class SerialBridgeServer : IDisposable +{ + public const string PipeName = "etterms-serial"; + + private readonly CancellationTokenSource _cts = new(); + private Task? _loop; + + public void Start() + { + if (_loop != null) return; + _loop = Task.Run(AcceptLoop); + AppLogger.Info($"SerialBridge server started on pipe \\\\.\\pipe\\{PipeName}"); + } + + private async Task AcceptLoop() + { + while (!_cts.IsCancellationRequested) + { + try + { + var pipe = new NamedPipeServerStream(PipeName, PipeDirection.InOut, 1, + PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + await pipe.WaitForConnectionAsync(_cts.Token); + await ServeClient(pipe); + } + catch (OperationCanceledException) { break; } + catch (Exception ex) { AppLogger.LogWarning($"SerialBridge accept error: {ex.Message}"); } + } + } + + private async Task ServeClient(NamedPipeServerStream pipe) + { + using (pipe) + { + var writer = new StreamWriter(pipe, new UTF8Encoding(false)) { AutoFlush = true }; + var reader = new StreamReader(pipe, new UTF8Encoding(false)); + var wlock = new object(); + void Send(object o) { lock (wlock) { try { writer.WriteLine(JsonSerializer.Serialize(o)); } catch { } } } + + SerialBridgeEndpoint? attached = null; + Action? rx = null; + void Detach() { if (attached != null && rx != null) attached.Rx -= rx; attached = null; rx = null; } + + try + { + string? line; + while ((line = await reader.ReadLineAsync()) != null) + { + Req? req; + try { req = JsonSerializer.Deserialize(line); } catch { continue; } + if (req == null) continue; + + switch (req.op) + { + case "list": + Send(new { sessions = SerialBridge.All.Select(e => new { name = e.Name, baud = e.BaudRate }) }); + break; + case "attach": + Detach(); + attached = SerialBridge.Find(req.session ?? ""); + if (attached == null) { Send(new { ok = false, error = $"no open serial session '{req.session}' in GUI" }); break; } + rx = data => Send(new { op = "rx", data = Encoding.UTF8.GetString(data) }); + attached.Rx += rx; + Send(new { ok = true, name = attached.Name }); + break; + case "write": + if (attached == null) { Send(new { ok = false, error = "not attached" }); break; } + attached.Write(req.data ?? "", req.newline); + Send(new { ok = true }); + break; + case "detach": + Detach(); + Send(new { ok = true }); + break; + default: + Send(new { ok = false, error = $"unknown op '{req.op}'" }); + break; + } + } + } + catch (Exception ex) { AppLogger.LogWarning($"SerialBridge client error: {ex.Message}"); } + finally { Detach(); } + } + } + + private sealed class Req + { + public string? op { get; set; } + public string? session { get; set; } + public string? data { get; set; } + public bool newline { get; set; } + } + + public void Dispose() + { + try { _cts.Cancel(); } catch { } + _cts.Dispose(); + } +} diff --git a/src/ETTerms/Sessions/SerialChannel.cs b/src/ETTerms/Sessions/SerialChannel.cs index 85a3a07..a020c5d 100644 --- a/src/ETTerms/Sessions/SerialChannel.cs +++ b/src/ETTerms/Sessions/SerialChannel.cs @@ -17,6 +17,12 @@ public sealed class SerialChannel : ISessionChannel public string LogName => _portName; + /// 目前傳輸速率(供 MCP 橋接列出 session 用)。 + public int BaudRate => _port.BaudRate; + + /// 送出換行序列(AI 寫入時附加,與手打 Enter 一致)。 + public string NewLine => _port.NewLine; + public SerialChannel(SerialSettings s) { _portName = s.PortName; diff --git a/src/ETTerms/Sessions/SessionPage.cs b/src/ETTerms/Sessions/SessionPage.cs index 86c049a..2848a61 100644 --- a/src/ETTerms/Sessions/SessionPage.cs +++ b/src/ETTerms/Sessions/SessionPage.cs @@ -24,6 +24,7 @@ public sealed class SessionPage : UserControl private readonly Button _run, _stop, _log; private bool _started; private SessionLogger? _logger; + private SerialBridgeEndpoint? _bridge; // Serial 才有:供 MCP 橋接 /// 底層連線通道。 public ISessionChannel Channel => _channel; @@ -216,6 +217,11 @@ public sealed class SessionPage : UserControl { _channel.Open(); SessionManager.Register(_channel); + if (_channel is SerialChannel sc) + { + _bridge = new SerialBridgeEndpoint(sc.LogName, sc.BaudRate, WriteFromAi); + SerialBridge.Register(_bridge); + } } catch (Exception ex) { @@ -228,17 +234,28 @@ public sealed class SessionPage : UserControl { // 側錄不依賴 UI thread,直接在背景緒寫檔(SessionLogger 內部自鎖)。 _logger?.Write(data); + _bridge?.FeedRx(data); // 轉發給 MCP(若有 attach) if (IsDisposed || !IsHandleCreated) return; if (InvokeRequired) BeginInvoke(() => _term.Feed(data)); else _term.Feed(data); } + /// MCP 來源寫入:送出實體 port,並把內容以 [AI] 標色 echo 回終端機,使用者即時可見。 + private void WriteFromAi(string text, bool appendNewline) + { + if (_channel is not SerialChannel sc) return; + _channel.Write(System.Text.Encoding.UTF8.GetBytes(appendNewline ? text + sc.NewLine : text)); + var echo = System.Text.Encoding.UTF8.GetBytes($"\x1b[35m[AI]\x1b[0m {text}\r\n"); + Ui(() => _term.Feed(echo)); + } + protected override void Dispose(bool disposing) { if (disposing && _started) { _runner.Cancel(); _channel.DataReceived -= OnDataReceived; + if (_bridge != null) { SerialBridge.Unregister(_bridge); _bridge = null; } _logger?.Dispose(); _logger = null; SessionManager.Unregister(_channel); diff --git a/src/ETTerms/Terminal/ScreenBuffer.cs b/src/ETTerms/Terminal/ScreenBuffer.cs index b3156aa..ad1b4ba 100644 --- a/src/ETTerms/Terminal/ScreenBuffer.cs +++ b/src/ETTerms/Terminal/ScreenBuffer.cs @@ -3,7 +3,7 @@ using System.Drawing; namespace ETTerms.Terminal; [Flags] -public enum CellAttr : byte { None = 0, Bold = 1, Underline = 2, Inverse = 4 } +public enum CellAttr : byte { None = 0, Bold = 1, Underline = 2, Inverse = 4, Wide = 8, WideTrail = 16 } public struct Cell { @@ -80,11 +80,37 @@ public sealed class ScreenBuffer // ── 輸出字元 ───────────────────────────────────────────── public void Print(char ch) { + int w = CharWidth(ch); if (_wrapPending) { _wrapPending = false; CursorCol = 0; LineFeed(); } if (CursorRow < 0 || CursorRow >= Rows) CursorRow = Math.Clamp(CursorRow, 0, Rows - 1); - _screen[CursorRow][CursorCol] = new Cell { Ch = ch, Fg = PenFg, Bg = PenBg, Attr = PenAttr }; - if (CursorCol >= Cols - 1) { if (AutoWrap) _wrapPending = true; } - else CursorCol++; + + // 寬字(全形 CJK)占 2 格;行尾剩 1 格放不下 → 先換行 + if (w == 2 && CursorCol == Cols - 1) { CursorCol = 0; LineFeed(); } + + _screen[CursorRow][CursorCol] = new Cell { Ch = ch, Fg = PenFg, Bg = PenBg, Attr = w == 2 ? PenAttr | CellAttr.Wide : PenAttr }; + if (w == 2 && CursorCol + 1 < Cols) + _screen[CursorRow][CursorCol + 1] = new Cell { Ch = '\0', Fg = PenFg, Bg = PenBg, Attr = PenAttr | CellAttr.WideTrail }; + + if (CursorCol + w >= Cols) { if (AutoWrap) _wrapPending = true; else CursorCol = Cols - 1; } + else CursorCol += w; + } + + /// 東亞全形 / 寬字回傳 2,其餘 1(BMP 範圍,足夠涵蓋常見中日韓)。 + private static int CharWidth(char ch) + { + if (ch < 0x1100) return 1; + return (ch <= 0x115F) // Hangul Jamo + || (ch >= 0x2E80 && ch <= 0x303E) // CJK 部首 / 康熙 / 符號 + || (ch >= 0x3041 && ch <= 0x33FF) // 平假名 / 片假名 / CJK 符號 + || (ch >= 0x3400 && ch <= 0x4DBF) // CJK 擴充 A + || (ch >= 0x4E00 && ch <= 0x9FFF) // CJK 基本 + || (ch >= 0xA000 && ch <= 0xA4CF) // 彝文 + || (ch >= 0xAC00 && ch <= 0xD7A3) // 韓文音節 + || (ch >= 0xF900 && ch <= 0xFAFF) // CJK 相容表意 + || (ch >= 0xFE30 && ch <= 0xFE4F) // CJK 相容形式 + || (ch >= 0xFF00 && ch <= 0xFF60) // 全形 ASCII + || (ch >= 0xFFE0 && ch <= 0xFFE6) // 全形符號 + ? 2 : 1; } // ── 游標 / 換行 ────────────────────────────────────────── diff --git a/src/ETTerms/Terminal/TerminalView.cs b/src/ETTerms/Terminal/TerminalView.cs index f6b62ed..87c7b64 100644 --- a/src/ETTerms/Terminal/TerminalView.cs +++ b/src/ETTerms/Terminal/TerminalView.cs @@ -1,4 +1,5 @@ using System.Drawing; +using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; @@ -30,12 +31,13 @@ public sealed class TerminalView : UserControl SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true); BackColor = Color.FromArgb(18, 18, 22); + ImeMode = ImeMode.NoControl; // 容器控制項預設關 IME,這裡明確開啟讓使用者可切中文 _font = new Font(profile.FontFamily, profile.FontSize); using (var g = CreateGraphics()) { var sz = TextRenderer.MeasureText(g, "W", _font, Size.Empty, TextFormatFlags.NoPadding); _cellW = Math.Max(1, sz.Width); - _cellH = Math.Max(1, _font.Height); + _cellH = Math.Max(1, _font.Height + 2); // +2 行距,避免 g/y 下緣與中文底部被裁切 } _buf = new ScreenBuffer(profile.Cols, profile.Rows, Color.FromArgb(220, 220, 220), BackColor, profile.ScrollbackLines); @@ -90,13 +92,26 @@ public sealed class TerminalView : UserControl while (c < line.Length) { var cell = line[c]; + if ((cell.Attr & CellAttr.WideTrail) != 0) { c++; continue; } // 寬字第二格,由前格覆蓋 ResolveColors(cell, out var fg, out var bg, abs, c); - // 合併同屬性連續格 + + // 寬字(全形 CJK):單獨繪製,占 2 格寬 + if ((cell.Attr & CellAttr.Wide) != 0) + { + var wr = new Rectangle(c * _cellW, y, _cellW * 2, _cellH); + using (var bb = new SolidBrush(bg)) g.FillRectangle(bb, wr); + DrawRun(g, cell.Ch == '\0' ? " " : cell.Ch.ToString(), cell.Attr, wr, fg); + c++; + continue; + } + + // 合併同屬性連續的窄字 int start = c; var sb = new StringBuilder(); while (c < line.Length) { var cur = line[c]; + if ((cur.Attr & (CellAttr.Wide | CellAttr.WideTrail)) != 0) break; ResolveColors(cur, out var f2, out var b2, abs, c); if (f2 != fg || b2 != bg || (cur.Attr & CellAttr.Bold) != (cell.Attr & CellAttr.Bold)) break; sb.Append(cur.Ch == '\0' ? ' ' : cur.Ch); @@ -104,14 +119,19 @@ public sealed class TerminalView : UserControl } var rect = new Rectangle(start * _cellW, y, (c - start) * _cellW, _cellH); using (var bb = new SolidBrush(bg)) g.FillRectangle(bb, rect); - var style = (cell.Attr & CellAttr.Bold) != 0 ? FontStyle.Bold : FontStyle.Regular; - if ((cell.Attr & CellAttr.Underline) != 0) style |= FontStyle.Underline; - using var fnt = style == FontStyle.Regular ? _font : new Font(_font, style); - TextRenderer.DrawText(g, sb.ToString(), fnt, rect, fg, - TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix | TextFormatFlags.Left); + DrawRun(g, sb.ToString(), cell.Attr, rect, fg); } } + private void DrawRun(Graphics g, string text, CellAttr attr, Rectangle rect, Color fg) + { + var style = (attr & CellAttr.Bold) != 0 ? FontStyle.Bold : FontStyle.Regular; + if ((attr & CellAttr.Underline) != 0) style |= FontStyle.Underline; + using var fnt = style == FontStyle.Regular ? _font : new Font(_font, style); + TextRenderer.DrawText(g, text, fnt, rect, fg, + TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix | TextFormatFlags.Left); + } + private void ResolveColors(Cell cell, out Color fg, out Color bg, int abs, int col) { fg = cell.Fg.A == 0 ? _buf.DefaultFg : cell.Fg; @@ -223,7 +243,10 @@ public sealed class TerminalView : UserControl int from = abs == a.row ? a.col : 0; int to = abs == b.row ? b.col : line.Length; for (int c = from; c < Math.Min(to, line.Length); c++) + { + if ((line[c].Attr & CellAttr.WideTrail) != 0) continue; sb.Append(line[c].Ch == '\0' ? ' ' : line[c].Ch); + } if (abs < b.row) sb.Append("\r\n"); } var text = sb.ToString(); @@ -239,4 +262,83 @@ public sealed class TerminalView : UserControl } catch { } } + + // ── IME(中文 / 日文 / 韓文輸入)───────────────────────── + // 自繪控制項預設不處理 IME 組字,故攔截 WM_IME_COMPOSITION 取「結果字串」直接送出 UTF-8。 + private const int WM_IME_STARTCOMPOSITION = 0x010D; + private const int WM_IME_COMPOSITION = 0x010F; + private const int GCS_RESULTSTR = 0x0800; + private const int CFS_POINT = 0x0002; + private const int IACE_DEFAULT = 0x0010; + + // UserControl(容器)預設不啟用 IME,使用者無法切中文。把預設 IME context 綁回此視窗, + // 並在取得焦點後(WinForms 可能於 OnGotFocus 內關掉)再綁一次,確保可切換輸入法。 + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + ImmAssociateContextEx(Handle, IntPtr.Zero, IACE_DEFAULT); + } + + protected override void OnGotFocus(EventArgs e) + { + base.OnGotFocus(e); + ImmAssociateContextEx(Handle, IntPtr.Zero, IACE_DEFAULT); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_IME_STARTCOMPOSITION) MoveImeWindowToCursor(); + else if (m.Msg == WM_IME_COMPOSITION && ((long)m.LParam & GCS_RESULTSTR) != 0) + { + var s = ReadImeResult(); + if (!string.IsNullOrEmpty(s)) + { + SendData?.Invoke(Encoding.UTF8.GetBytes(s)); + return; // 消費此訊息,避免預設再轉成 WM_CHAR 造成重複輸入 + } + } + base.WndProc(ref m); + } + + private string ReadImeResult() + { + IntPtr hImc = ImmGetContext(Handle); + if (hImc == IntPtr.Zero) return ""; + try + { + int len = ImmGetCompositionStringW(hImc, GCS_RESULTSTR, null, 0); + if (len <= 0) return ""; + var buf = new byte[len]; + ImmGetCompositionStringW(hImc, GCS_RESULTSTR, buf, len); + return Encoding.Unicode.GetString(buf); + } + finally { ImmReleaseContext(Handle, hImc); } + } + + private void MoveImeWindowToCursor() + { + IntPtr hImc = ImmGetContext(Handle); + if (hImc == IntPtr.Zero) return; + try + { + int vr = _buf.CursorRow + _scrollOffset; + var cf = new COMPOSITIONFORM + { + dwStyle = CFS_POINT, + ptCurrentPos = new POINT { x = _buf.CursorCol * _cellW, y = vr * _cellH } + }; + ImmSetCompositionWindow(hImc, ref cf); + } + finally { ImmReleaseContext(Handle, hImc); } + } + + [StructLayout(LayoutKind.Sequential)] private struct POINT { public int x, y; } + [StructLayout(LayoutKind.Sequential)] private struct RECT { public int left, top, right, bottom; } + [StructLayout(LayoutKind.Sequential)] private struct COMPOSITIONFORM { public int dwStyle; public POINT ptCurrentPos; public RECT rcArea; } + + [DllImport("imm32.dll")] private static extern IntPtr ImmGetContext(IntPtr hWnd); + [DllImport("imm32.dll")] private static extern bool ImmAssociateContextEx(IntPtr hWnd, IntPtr hIMC, int dwFlags); + [DllImport("imm32.dll")] private static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC); + [DllImport("imm32.dll", CharSet = CharSet.Unicode)] private static extern int ImmGetCompositionStringW(IntPtr hIMC, int dwIndex, byte[]? lpBuf, int dwBufLen); + [DllImport("imm32.dll")] private static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM lpCompForm); }

S=S{P*zDIoa(pB(!22EjQj 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 V%pBoBqJ{tf002ovPDHLkV1m<#Fc$y- literal 0 HcmV?d00001 diff --git a/src/ETTerms/ETTerms.csproj b/src/ETTerms/ETTerms.csproj index e5c4557..b871262 100644 --- a/src/ETTerms/ETTerms.csproj +++ b/src/ETTerms/ETTerms.csproj @@ -10,11 +10,19 @@ ETTerms - 0.1.3 + 0.2.0 ETTerms ETTerms Project + + + Assets\Choco_256x256.ico + + + + + diff --git a/src/ETTerms/Infrastructure/AppAssets.cs b/src/ETTerms/Infrastructure/AppAssets.cs new file mode 100644 index 0000000..60cec29 --- /dev/null +++ b/src/ETTerms/Infrastructure/AppAssets.cs @@ -0,0 +1,32 @@ +using System.Drawing; +using System.Reflection; + +namespace ETTerms.Infrastructure; + +///