添加qiming-rcoder模块

This commit is contained in:
Codex
2026-06-01 13:54:52 +08:00
parent 8092c4b1f8
commit 4b1a580132
539 changed files with 151650 additions and 0 deletions

View File

@@ -0,0 +1,497 @@
# RCoder Agent Runner - 远程桌面服务
本目录包含 `rcoder-agent-runner` 容器的配置文件,提供完整的远程桌面解决方案,包括 VNC 远程桌面、本地输入法透传、音频流传输等功能。
## 📋 目录
- [架构概览](#架构概览)
- [服务端口](#服务端口)
- [VNC 远程桌面](#vnc-远程桌面)
- [本地输入法透传](#本地输入法透传)
- [音频流传输](#音频流传输)
- [前端集成指南](#前端集成指南)
- [API 接口文档](#api-接口文档)
---
## 架构概览
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 用户浏览器 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ noVNC Canvas │ │ IME 隐藏输入框 │ │ Audio Player │ │
│ │ (VNC 画面) │ │ (输入法捕获) │ │ (音频播放) │ │
│ └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ │ ws://host:6080 │ ws://host:6091 │ ws://host:6089│
│ │ (VNC 协议) │ (文本输入) │ (Opus 音频) │
│ ▼ ▼ ▼ │
└────────────┬─────────────────────┬─────────────────────┬───────────────┘
│ │ │
┌────────────┴─────────────────────┴─────────────────────┴───────────────┐
│ 容器内部 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ noVNC Proxy │ │ IME Server │ │ Audio Server │ │ x11vnc │ │
│ │ Port: 6080 │ │ Port: 6091 │ │ Port: 6089 │ │ Port: 5900 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ X11 Display (:0) │ │
│ │ XFCE4 桌面环境 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
```
---
## 服务端口
| 端口 | 服务 | 协议 | 说明 |
|------|------|------|------|
| **6080** | noVNC | WebSocket (VNC) | 远程桌面画面和键鼠输入 |
| **6091** | IME Server | WebSocket (JSON) | 本地输入法文本透传 |
| **6089** | Audio Server | WebSocket (Binary) | 音频流传输 (Opus 编码) |
| **6090** | Audio HTTP | HTTP | 音频播放器静态页面 |
| 5900 | x11vnc | VNC | 原始 VNC 协议(内部使用) |
---
## VNC 远程桌面
### 功能说明
- **基于 noVNC**: 纯 Web VNC 客户端,无需安装插件
- **分辨率**: 默认 1280x800可通过 URL 参数调整
- **自动连接**: 支持 `autoconnect=true` 参数
- **自适应缩放**: 支持 `resize=scale` 参数
### 前端接入
#### 方式一:直接嵌入 iframe
```html
<iframe
src="http://容器IP:6080/vnc.html?autoconnect=true&resize=scale"
width="100%"
height="600"
frameborder="0">
</iframe>
```
#### 方式二:使用 noVNC JavaScript API
```html
<script src="http://容器IP:6080/core/rfb.js" type="module"></script>
<div id="vnc-container"></div>
<script type="module">
import RFB from 'http://容器IP:6080/core/rfb.js';
const rfb = new RFB(
document.getElementById('vnc-container'),
`ws://容器IP:6080`
);
rfb.scaleViewport = true;
rfb.resizeSession = true;
rfb.addEventListener('connect', () => console.log('VNC connected'));
rfb.addEventListener('disconnect', () => console.log('VNC disconnected'));
</script>
```
### URL 参数
| 参数 | 说明 | 示例 |
|------|------|------|
| `autoconnect` | 自动连接 | `true` |
| `resize` | 缩放模式 | `scale` / `remote` |
| `quality` | 画面质量 | `0-9` (9 最高) |
| `compression` | 压缩级别 | `0-9` (9 最高压缩) |
---
## 本地输入法透传
### 功能说明
允许用户使用**宿主机的输入法**(如搜狗输入法、微软拼音等)直接输入到远程桌面,无需在远程桌面内切换输入法。
### 工作原理
```
1. 用户点击 VNC 画面 → 焦点切换到隐藏输入框
2. 用户使用本地输入法输入(如输入 "zhongguo" 选择 "中国"
3. 前端捕获 compositionend 事件,获取 "中国"
4. 通过 WebSocket 发送到 IME Server
5. IME Server 使用 xdotool 将文本输入到远程桌面当前焦点窗口
```
### 前端接入
#### 方式一:使用内置脚本(推荐)
如果使用我们提供的 `vnc.html`IME 透传已内置,无需额外配置。
#### 方式二:手动集成
```html
<!-- 1. 创建隐藏输入框 -->
<textarea id="ime-capture" style="position:fixed;left:-9999px;opacity:0;"></textarea>
<!-- 2. 建立 WebSocket 连接 -->
<script>
const imeWs = new WebSocket('ws://容器IP:6091');
// 连接状态
imeWs.onopen = () => console.log('IME connected');
imeWs.onclose = () => console.log('IME disconnected');
imeWs.onerror = (e) => console.error('IME error:', e);
// 响应处理
imeWs.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.status === 'ok') {
console.log('Text sent successfully');
} else if (data.status === 'error') {
console.error('Error:', data.message);
}
};
// 3. 监听输入法事件
const imeInput = document.getElementById('ime-capture');
imeInput.addEventListener('compositionend', (e) => {
if (e.data && imeWs.readyState === WebSocket.OPEN) {
imeWs.send(JSON.stringify({
type: 'text',
text: e.data
}));
imeInput.value = '';
}
});
// 4. VNC 画面点击时聚焦到隐藏输入框
document.getElementById('vnc-container').addEventListener('click', () => {
imeInput.focus();
});
</script>
```
### WebSocket 协议
#### 发送消息格式
```json
{
"type": "text",
"text": "要输入的文本",
"method": "xdotool" // 可选: "xdotool"(默认) 或 "clipboard"
}
```
#### 响应消息格式
```json
// 成功
{ "status": "ok" }
// 失败
{ "status": "error", "message": "错误信息" }
// 心跳响应
{ "type": "pong" }
```
#### 心跳保活
```json
// 发送
{ "type": "ping" }
// 响应
{ "type": "pong" }
```
---
## 音频流传输
### 功能说明
将远程桌面的音频(如视频播放、系统提示音)通过 WebSocket 实时传输到浏览器播放。
### 技术栈
- **采集**: PulseAudio 虚拟声卡
- **编码**: Opus (48kHz, 立体声)
- **传输**: WebSocket (二进制)
- **播放**: Web Audio API
### 前端接入
#### 方式一:嵌入音频播放器页面
```html
<iframe
src="http://容器IP:6090/index.html"
width="300"
height="50"
frameborder="0">
</iframe>
```
#### 方式二:直接使用 pcmflux 客户端
```html
<!-- 引入 pcmflux 客户端库 -->
<script src="http://容器IP:6090/pcmflux.js"></script>
<button id="audio-toggle">🔊 开启音频</button>
<script>
let audioContext = null;
let audioPlayer = null;
document.getElementById('audio-toggle').addEventListener('click', async () => {
if (!audioContext) {
// 初始化 Audio Context (需要用户交互)
audioContext = new AudioContext({ sampleRate: 48000 });
// 连接 WebSocket
const ws = new WebSocket('ws://容器IP:6089');
ws.binaryType = 'arraybuffer';
// 创建 Opus 解码器和播放器
audioPlayer = new PCMFluxPlayer(audioContext, ws);
audioPlayer.start();
document.getElementById('audio-toggle').textContent = '🔇 关闭音频';
} else {
// 关闭音频
audioPlayer.stop();
audioContext.close();
audioContext = null;
document.getElementById('audio-toggle').textContent = '🔊 开启音频';
}
});
</script>
```
### WebSocket 协议
音频数据通过 WebSocket 以**二进制帧**传输,格式为 Opus 编码的音频包。
```javascript
const ws = new WebSocket('ws://容器IP:6089');
ws.binaryType = 'arraybuffer';
ws.onmessage = (event) => {
const opusData = new Uint8Array(event.data);
// 使用 Opus 解码器解码后播放
decodeAndPlay(opusData);
};
```
### 注意事项
> [!IMPORTANT]
> **浏览器安全限制**: 由于浏览器自动播放策略,音频播放必须由用户交互(如点击按钮)触发。
---
## 前端集成指南
### 完整示例
```html
<!DOCTYPE html>
<html>
<head>
<title>远程桌面</title>
<style>
body { margin: 0; font-family: Arial, sans-serif; }
#container { display: flex; flex-direction: column; height: 100vh; }
#toolbar { padding: 10px; background: #333; color: white; display: flex; gap: 10px; align-items: center; }
#vnc-wrapper { flex: 1; position: relative; }
#vnc-container { width: 100%; height: 100%; }
#ime-capture { position: fixed; left: -9999px; opacity: 0; }
.status { padding: 5px 10px; border-radius: 4px; font-size: 12px; }
.connected { background: #4CAF50; }
.disconnected { background: #f44336; }
</style>
</head>
<body>
<div id="container">
<!-- 工具栏 -->
<div id="toolbar">
<span>远程桌面</span>
<span id="vnc-status" class="status disconnected">VNC: 未连接</span>
<span id="ime-status" class="status disconnected">输入法: 未连接</span>
<button id="audio-btn">🔊 开启音频</button>
</div>
<!-- VNC 画面 -->
<div id="vnc-wrapper">
<div id="vnc-container"></div>
</div>
</div>
<!-- IME 隐藏输入框 -->
<textarea id="ime-capture"></textarea>
<script type="module">
// ========== 配置 ==========
const HOST = location.hostname || 'localhost';
const VNC_PORT = 6080;
const IME_PORT = 6091;
const AUDIO_PORT = 6089;
// ========== VNC 连接 ==========
import RFB from `http://${HOST}:${VNC_PORT}/core/rfb.js`;
const rfb = new RFB(
document.getElementById('vnc-container'),
`ws://${HOST}:${VNC_PORT}`
);
rfb.scaleViewport = true;
rfb.addEventListener('connect', () => {
document.getElementById('vnc-status').textContent = 'VNC: 已连接';
document.getElementById('vnc-status').className = 'status connected';
});
rfb.addEventListener('disconnect', () => {
document.getElementById('vnc-status').textContent = 'VNC: 已断开';
document.getElementById('vnc-status').className = 'status disconnected';
});
// ========== IME 输入法透传 ==========
const imeWs = new WebSocket(`ws://${HOST}:${IME_PORT}`);
const imeInput = document.getElementById('ime-capture');
imeWs.onopen = () => {
document.getElementById('ime-status').textContent = '输入法: 已连接';
document.getElementById('ime-status').className = 'status connected';
};
imeWs.onclose = () => {
document.getElementById('ime-status').textContent = '输入法: 已断开';
document.getElementById('ime-status').className = 'status disconnected';
};
imeInput.addEventListener('compositionend', (e) => {
if (e.data && imeWs.readyState === WebSocket.OPEN) {
imeWs.send(JSON.stringify({ type: 'text', text: e.data }));
imeInput.value = '';
}
});
document.getElementById('vnc-container').addEventListener('click', () => {
imeInput.focus();
});
// ========== 音频 ==========
let audioWs = null;
document.getElementById('audio-btn').addEventListener('click', () => {
if (!audioWs) {
audioWs = new WebSocket(`ws://${HOST}:${AUDIO_PORT}`);
audioWs.binaryType = 'arraybuffer';
// 音频处理逻辑...
document.getElementById('audio-btn').textContent = '🔇 关闭音频';
} else {
audioWs.close();
audioWs = null;
document.getElementById('audio-btn').textContent = '🔊 开启音频';
}
});
</script>
</body>
</html>
```
---
## API 接口文档
### VNC 服务 (Port 6080)
| 端点 | 说明 |
|------|------|
| `GET /vnc.html` | noVNC 客户端页面 |
| `GET /vnc_lite.html` | noVNC 精简版页面 |
| `GET /core/rfb.js` | noVNC RFB 模块 |
| `WS /` | VNC WebSocket 连接 |
### IME 服务 (Port 6091)
| 端点 | 协议 | 说明 |
|------|------|------|
| `WS /` | WebSocket | 文本输入通道 |
**消息格式**:
```typescript
// 请求
interface IMERequest {
type: 'text' | 'ping';
text?: string; // type='text' 时必填
method?: 'xdotool' | 'clipboard'; // 可选,默认 xdotool
}
// 响应
interface IMEResponse {
status?: 'ok' | 'error';
message?: string; // status='error' 时的错误信息
type?: 'pong'; // 心跳响应
}
```
### 音频服务 (Port 6089/6090)
| 端点 | 协议 | 说明 |
|------|------|------|
| `GET :6090/` | HTTP | 音频播放器页面 |
| `GET :6090/pcmflux.js` | HTTP | PCMFlux 客户端库 |
| `WS :6089/` | WebSocket (Binary) | 音频流 (Opus) |
---
## 文件说明
| 文件 | 说明 |
|------|------|
| `Dockerfile` | 容器构建配置 |
| `start-up.sh` | 容器启动脚本,启动所有服务 |
| `ime_server.py` | IME 输入法透传后端服务 |
| `ime_passthrough.js` | IME 输入法透传前端脚本 |
| `audio_server.py` | 音频流传输服务 |
| `audio_static/` | 音频播放器静态文件 |
---
## 常见问题
### Q: 中文输入不生效?
A: 确保点击了 VNC 画面中的文本输入框,且 IME WebSocket 已连接。
### Q: 音频没有声音?
A: 浏览器需要用户交互才能播放音频,请点击"开启音频"按钮。
### Q: VNC 画面模糊?
A: 尝试在 URL 中添加 `?quality=9` 参数提高画质。
### Q: 如何禁用 IME 透传?
A: 启动容器时设置 `ENABLE_IME_PASSTHROUGH=false` 环境变量。
---
## 联系方式
如有问题,请联系后端开发团队。