dev-vnc 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dev_vnc-1.0.0.dist-info/METADATA +281 -0
- dev_vnc-1.0.0.dist-info/RECORD +10 -0
- dev_vnc-1.0.0.dist-info/WHEEL +5 -0
- dev_vnc-1.0.0.dist-info/entry_points.txt +2 -0
- dev_vnc-1.0.0.dist-info/licenses/LICENSE +21 -0
- dev_vnc-1.0.0.dist-info/top_level.txt +1 -0
- devvnc/__init__.py +11 -0
- devvnc/cli.py +123 -0
- devvnc/config.py +126 -0
- devvnc/server.py +378 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dev-vnc
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 通用开发用远程桌面服务 - 用于 SSH 远程连接时的 GUI 应用调试
|
|
5
|
+
Author-email: Henry <henry@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/HenryHenry-Space/dev-vnc
|
|
8
|
+
Project-URL: Repository, https://github.com/HenryHenry-Space/dev-vnc
|
|
9
|
+
Project-URL: Documentation, https://github.com/HenryHenry-Space/dev-vnc/blob/main/README.md
|
|
10
|
+
Keywords: vnc,remote-desktop,development,gui,novnc,xvfb
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Desktop Environment
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
31
|
+
Requires-Dist: isort>=5.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# Dev VNC Server
|
|
36
|
+
|
|
37
|
+
🖥️ 通用开发用远程桌面服务 - 用于 SSH 远程连接时的 GUI 应用调试
|
|
38
|
+
🖥️ A general-purpose remote desktop service for development, designed for GUI debugging over SSH
|
|
39
|
+
|
|
40
|
+
## 功能特点 / Features
|
|
41
|
+
|
|
42
|
+
- 🚀 一键启动虚拟桌面环境 / One-click virtual desktop startup
|
|
43
|
+
- 🌐 支持浏览器访问 (noVNC) / Browser access via noVNC
|
|
44
|
+
- 🔌 支持 VNC 客户端连接 / VNC client support
|
|
45
|
+
- ⚙️ 可配置分辨率、端口、窗口管理器 / Configurable resolution, ports, and window manager
|
|
46
|
+
- 🐍 提供 Python CLI 接口 / Python CLI included
|
|
47
|
+
- 📦 易于安装和管理 / Easy to install and manage
|
|
48
|
+
|
|
49
|
+
## 快速开始 / Quick Start
|
|
50
|
+
|
|
51
|
+
### 安装 / Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 克隆项目 / Clone repository
|
|
55
|
+
cd /home/henry/workspace/dev_app_vnc
|
|
56
|
+
|
|
57
|
+
# 运行安装脚本 / Run install script
|
|
58
|
+
./scripts/install.sh
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 使用 / Usage
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# 启动远程桌面 / Start remote desktop
|
|
65
|
+
dev-vnc start
|
|
66
|
+
|
|
67
|
+
# 查看状态 / Show status
|
|
68
|
+
dev-vnc status
|
|
69
|
+
|
|
70
|
+
# 查看访问信息 / Show access info
|
|
71
|
+
dev-vnc info
|
|
72
|
+
|
|
73
|
+
# 在 VNC 环境中运行程序 / Run command in VNC
|
|
74
|
+
dev-vnc run python my_gui_app.py
|
|
75
|
+
|
|
76
|
+
# 停止服务 / Stop service
|
|
77
|
+
dev-vnc stop
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 访问方式 / Access
|
|
81
|
+
|
|
82
|
+
### 浏览器访问 (推荐) / Browser (recommended)
|
|
83
|
+
|
|
84
|
+
启动服务后,打开浏览器访问 / After starting the service, open in browser:
|
|
85
|
+
- `http://localhost:6080/vnc.html`
|
|
86
|
+
- `http://<服务器IP>:6080/vnc.html`
|
|
87
|
+
|
|
88
|
+
### VNC 客户端 / VNC Client
|
|
89
|
+
|
|
90
|
+
使用任意 VNC 客户端连接 / Connect with any VNC client:
|
|
91
|
+
- 地址 / Address: `localhost:5999` 或 `<服务器IP>:5999`
|
|
92
|
+
- 密码 / Password: `devvnc123` (可配置 / configurable)
|
|
93
|
+
|
|
94
|
+
## 配置 / Configuration
|
|
95
|
+
|
|
96
|
+
### 配置文件 / Config file
|
|
97
|
+
|
|
98
|
+
配置文件位于 `~/.config/dev-vnc/config.env` / Config file location: `~/.config/dev-vnc/config.env`
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# 显示器编号 / Display number
|
|
102
|
+
DEV_VNC_DISPLAY=99
|
|
103
|
+
|
|
104
|
+
# VNC 端口 / VNC port
|
|
105
|
+
DEV_VNC_PORT=5999
|
|
106
|
+
|
|
107
|
+
# noVNC Web 端口 / noVNC web port
|
|
108
|
+
DEV_VNC_NOVNC_PORT=6080
|
|
109
|
+
|
|
110
|
+
# 分辨率 / Resolution
|
|
111
|
+
DEV_VNC_RESOLUTION=1920x1080x24
|
|
112
|
+
|
|
113
|
+
# VNC 密码 / VNC password
|
|
114
|
+
DEV_VNC_PASSWORD=devvnc123
|
|
115
|
+
|
|
116
|
+
# 窗口管理器 (fluxbox, openbox, i3) / Window manager
|
|
117
|
+
DEV_VNC_WM=fluxbox
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 环境变量 / Environment variables
|
|
121
|
+
|
|
122
|
+
也可以通过环境变量覆盖配置 / Override with environment variables:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
DEV_VNC_RESOLUTION=2560x1440x24 dev-vnc start
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 命令参考 / Command reference
|
|
129
|
+
|
|
130
|
+
| 命令 / Command | 说明 / Description |
|
|
131
|
+
|------|------|
|
|
132
|
+
| `dev-vnc start` | 启动远程桌面服务 / Start remote desktop |
|
|
133
|
+
| `dev-vnc stop` | 停止远程桌面服务 / Stop remote desktop |
|
|
134
|
+
| `dev-vnc restart` | 重启服务 / Restart service |
|
|
135
|
+
| `dev-vnc status` | 显示服务状态 / Show status |
|
|
136
|
+
| `dev-vnc info` | 显示访问信息 / Show access info |
|
|
137
|
+
| `dev-vnc logs [type]` | 显示日志 (vnc/novnc/all) / Show logs |
|
|
138
|
+
| `dev-vnc run <cmd>` | 在 VNC 环境中运行命令 / Run command in VNC |
|
|
139
|
+
| `dev-vnc config` | 显示当前配置 / Show configuration |
|
|
140
|
+
| `dev-vnc install-deps` | 安装系统依赖 / Install dependencies |
|
|
141
|
+
| `dev-vnc help` | 显示帮助信息 / Show help |
|
|
142
|
+
|
|
143
|
+
## Python CLI
|
|
144
|
+
|
|
145
|
+
也可以使用 Python CLI / You can also use the Python CLI:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# 安装 / Install
|
|
149
|
+
pip install -e .
|
|
150
|
+
|
|
151
|
+
# 使用 / Usage
|
|
152
|
+
devvnc start
|
|
153
|
+
devvnc status
|
|
154
|
+
devvnc run python my_app.py
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 系统要求 / System requirements
|
|
158
|
+
|
|
159
|
+
### 支持的操作系统 / Supported OS
|
|
160
|
+
|
|
161
|
+
- Ubuntu / Debian
|
|
162
|
+
- Fedora / CentOS / RHEL
|
|
163
|
+
- Arch Linux
|
|
164
|
+
|
|
165
|
+
### 依赖 / Dependencies
|
|
166
|
+
|
|
167
|
+
- Xvfb
|
|
168
|
+
- x11vnc
|
|
169
|
+
- fluxbox (或其他窗口管理器)
|
|
170
|
+
- noVNC
|
|
171
|
+
- websockify
|
|
172
|
+
- Python 3.8+
|
|
173
|
+
|
|
174
|
+
## 典型使用场景 / Typical use cases
|
|
175
|
+
|
|
176
|
+
### 远程 GUI 开发 / Remote GUI development
|
|
177
|
+
|
|
178
|
+
在 SSH 连接的远程服务器上调试 GUI 应用 / Debug GUI apps over SSH:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# SSH 连接到服务器 / SSH into server
|
|
182
|
+
ssh user@server
|
|
183
|
+
|
|
184
|
+
# 启动远程桌面 / Start remote desktop
|
|
185
|
+
dev-vnc start
|
|
186
|
+
|
|
187
|
+
# 在本地浏览器打开 / Open in local browser
|
|
188
|
+
|
|
189
|
+
# 运行 GUI 应用 / Run GUI app
|
|
190
|
+
dev-vnc run python my_gui_app.py
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### CI/CD 中的 GUI 测试 / GUI testing in CI/CD
|
|
194
|
+
|
|
195
|
+
在无头环境中运行 GUI 测试 / Run GUI tests in headless environments:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# 启动虚拟桌面 / Start virtual desktop
|
|
199
|
+
dev-vnc start
|
|
200
|
+
|
|
201
|
+
# 运行 GUI 测试 / Run GUI tests
|
|
202
|
+
dev-vnc run pytest tests/gui/
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 容器中的 GUI 应用 / GUI apps in containers
|
|
206
|
+
|
|
207
|
+
在 Docker 容器中运行 GUI 应用 / Run GUI apps in Docker:
|
|
208
|
+
|
|
209
|
+
```dockerfile
|
|
210
|
+
FROM ubuntu:22.04
|
|
211
|
+
|
|
212
|
+
# 安装依赖 / Install dependencies
|
|
213
|
+
RUN apt-get update && apt-get install -y \
|
|
214
|
+
xvfb x11vnc fluxbox novnc websockify
|
|
215
|
+
|
|
216
|
+
# 复制 dev-vnc / Copy dev-vnc
|
|
217
|
+
COPY . /app/dev-vnc
|
|
218
|
+
RUN /app/dev-vnc/scripts/install.sh
|
|
219
|
+
|
|
220
|
+
EXPOSE 5999 6080
|
|
221
|
+
|
|
222
|
+
CMD ["dev-vnc", "start"]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## 项目结构 / Project structure
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
dev_app_vnc/
|
|
229
|
+
├── README.md
|
|
230
|
+
├── pyproject.toml
|
|
231
|
+
├── config/
|
|
232
|
+
│ └── config.env.example
|
|
233
|
+
├── devvnc/
|
|
234
|
+
│ ├── __init__.py
|
|
235
|
+
│ ├── cli.py
|
|
236
|
+
│ ├── server.py
|
|
237
|
+
│ └── config.py
|
|
238
|
+
├── scripts/
|
|
239
|
+
│ ├── dev-vnc-server.sh
|
|
240
|
+
│ └── install.sh
|
|
241
|
+
├── tests/
|
|
242
|
+
│ └── test_server.py
|
|
243
|
+
└── docs/
|
|
244
|
+
└── ...
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## 故障排除 / Troubleshooting
|
|
248
|
+
|
|
249
|
+
### 服务无法启动 / Service won't start
|
|
250
|
+
|
|
251
|
+
1. 检查依赖是否安装 / Check dependencies:
|
|
252
|
+
```bash
|
|
253
|
+
dev-vnc install-deps
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
2. 检查端口是否被占用 / Check port usage:
|
|
257
|
+
```bash
|
|
258
|
+
netstat -tlnp | grep -E '5999|6080'
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
3. 查看日志 / View logs:
|
|
262
|
+
```bash
|
|
263
|
+
dev-vnc logs
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 无法连接 / Cannot connect
|
|
267
|
+
|
|
268
|
+
1. 检查防火墙设置 / Check firewall:
|
|
269
|
+
```bash
|
|
270
|
+
sudo ufw allow 5999
|
|
271
|
+
sudo ufw allow 6080
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
2. 确认服务正在运行 / Confirm service is running:
|
|
275
|
+
```bash
|
|
276
|
+
dev-vnc status
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## License
|
|
280
|
+
|
|
281
|
+
MIT License
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
dev_vnc-1.0.0.dist-info/licenses/LICENSE,sha256=Mj3Envh08AqG-T_wD4DEsSQyvMuHlCpexHW6MGPNgqE,1064
|
|
2
|
+
devvnc/__init__.py,sha256=61LvlgTLyY419ScFFhdcGGTN3piUzgg5Hd9SGQ9mWeI,270
|
|
3
|
+
devvnc/cli.py,sha256=uxVrKPkWDX30GtPsxrZrniaXV5RnO8Ui2WQ0za2sQEQ,3481
|
|
4
|
+
devvnc/config.py,sha256=anqx4PgbcFpN8nN7mOYAnMqdjYNOitXuzflhy1j2Qwo,4963
|
|
5
|
+
devvnc/server.py,sha256=h4UW6pFiewMNe-bCC5aDaTTiVu713tM7DWzRcIeC-aw,14523
|
|
6
|
+
dev_vnc-1.0.0.dist-info/METADATA,sha256=ms2V07CA5vUaIahoHr5jwGRvWeqVa6xJmpGfU0t-6G0,7134
|
|
7
|
+
dev_vnc-1.0.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
8
|
+
dev_vnc-1.0.0.dist-info/entry_points.txt,sha256=z_grZxOt_qi3F9HqdyyZI9eJPJV9T0uldJ5QNx2FWT0,43
|
|
9
|
+
dev_vnc-1.0.0.dist-info/top_level.txt,sha256=AEN9P3W06V9MUhGFU9RfuSDk5znjXAQBpS00buKUYpw,7
|
|
10
|
+
dev_vnc-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Henry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
devvnc
|
devvnc/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dev VNC Server - 通用开发用远程桌面服务 / Remote desktop service for development
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "1.0.0"
|
|
6
|
+
__author__ = "Henry"
|
|
7
|
+
|
|
8
|
+
from .server import DevVNCServer
|
|
9
|
+
from .config import DevVNCConfig
|
|
10
|
+
|
|
11
|
+
__all__ = ["DevVNCServer", "DevVNCConfig", "__version__"]
|
devvnc/cli.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dev VNC Server - 命令行接口 / Command-line interface
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .server import DevVNCServer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main(args: Optional[List[str]] = None) -> int:
|
|
14
|
+
"""主入口 / Entry point"""
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="devvnc",
|
|
17
|
+
description="Dev VNC Server - 通用开发用远程桌面服务",
|
|
18
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
19
|
+
epilog="""
|
|
20
|
+
示例:
|
|
21
|
+
devvnc start # 启动服务
|
|
22
|
+
devvnc stop # 停止服务
|
|
23
|
+
devvnc status # 查看状态
|
|
24
|
+
devvnc run python app.py # 在 VNC 环境中运行命令
|
|
25
|
+
|
|
26
|
+
环境变量:
|
|
27
|
+
DEV_VNC_DISPLAY 显示器编号 (默认: 99)
|
|
28
|
+
DEV_VNC_PORT VNC 端口 (默认: 5999)
|
|
29
|
+
DEV_VNC_NOVNC_PORT noVNC 端口 (默认: 6080)
|
|
30
|
+
DEV_VNC_RESOLUTION 分辨率 (默认: 1920x1080x24)
|
|
31
|
+
DEV_VNC_PASSWORD VNC 密码 (默认: devvnc123)
|
|
32
|
+
DEV_VNC_WM 窗口管理器 (默认: fluxbox)
|
|
33
|
+
"""
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--version", "-V",
|
|
38
|
+
action="version",
|
|
39
|
+
version=f"%(prog)s {__version__}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
subparsers = parser.add_subparsers(dest="command", help="可用命令")
|
|
43
|
+
|
|
44
|
+
# start
|
|
45
|
+
start_parser = subparsers.add_parser("start", help="启动远程桌面服务")
|
|
46
|
+
|
|
47
|
+
# stop
|
|
48
|
+
stop_parser = subparsers.add_parser("stop", help="停止远程桌面服务")
|
|
49
|
+
|
|
50
|
+
# restart
|
|
51
|
+
restart_parser = subparsers.add_parser("restart", help="重启远程桌面服务")
|
|
52
|
+
|
|
53
|
+
# status
|
|
54
|
+
status_parser = subparsers.add_parser("status", help="显示服务状态")
|
|
55
|
+
|
|
56
|
+
# info
|
|
57
|
+
info_parser = subparsers.add_parser("info", help="显示访问信息")
|
|
58
|
+
|
|
59
|
+
# config
|
|
60
|
+
config_parser = subparsers.add_parser("config", help="显示当前配置")
|
|
61
|
+
|
|
62
|
+
# logs
|
|
63
|
+
logs_parser = subparsers.add_parser("logs", help="显示日志")
|
|
64
|
+
logs_parser.add_argument(
|
|
65
|
+
"type",
|
|
66
|
+
nargs="?",
|
|
67
|
+
default="all",
|
|
68
|
+
choices=["vnc", "novnc", "all"],
|
|
69
|
+
help="日志类型"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# run
|
|
73
|
+
run_parser = subparsers.add_parser("run", help="在 VNC 环境中运行命令")
|
|
74
|
+
run_parser.add_argument("cmd", nargs=argparse.REMAINDER, help="要运行的命令")
|
|
75
|
+
|
|
76
|
+
# 解析参数 / Parse arguments
|
|
77
|
+
parsed = parser.parse_args(args)
|
|
78
|
+
|
|
79
|
+
if not parsed.command:
|
|
80
|
+
parser.print_help()
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
# 创建服务器实例 / Create server instance
|
|
84
|
+
server = DevVNCServer()
|
|
85
|
+
|
|
86
|
+
# 执行命令 / Execute command
|
|
87
|
+
if parsed.command == "start":
|
|
88
|
+
return 0 if server.start() else 1
|
|
89
|
+
|
|
90
|
+
elif parsed.command == "stop":
|
|
91
|
+
return 0 if server.stop() else 1
|
|
92
|
+
|
|
93
|
+
elif parsed.command == "restart":
|
|
94
|
+
return 0 if server.restart() else 1
|
|
95
|
+
|
|
96
|
+
elif parsed.command == "status":
|
|
97
|
+
server.show_status()
|
|
98
|
+
server.show_info()
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
elif parsed.command == "info":
|
|
102
|
+
server.show_info()
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
elif parsed.command == "config":
|
|
106
|
+
server.show_config()
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
elif parsed.command == "logs":
|
|
110
|
+
server.show_logs(parsed.type)
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
elif parsed.command == "run":
|
|
114
|
+
if not parsed.cmd:
|
|
115
|
+
print("❌ 请指定要运行的命令 / Please specify a command to run")
|
|
116
|
+
return 1
|
|
117
|
+
return server.run_command(parsed.cmd)
|
|
118
|
+
|
|
119
|
+
return 0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
sys.exit(main())
|
devvnc/config.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dev VNC Server 配置管理 / Configuration management
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class DevVNCConfig:
|
|
13
|
+
"""VNC 服务器配置 / VNC server configuration"""
|
|
14
|
+
|
|
15
|
+
# 显示器配置 / Display settings
|
|
16
|
+
display_num: int = 99
|
|
17
|
+
vnc_port: int = 5999
|
|
18
|
+
novnc_port: int = 6080
|
|
19
|
+
resolution: str = "1920x1080x24"
|
|
20
|
+
|
|
21
|
+
# 认证 / Authentication
|
|
22
|
+
password: str = "devvnc123"
|
|
23
|
+
|
|
24
|
+
# 窗口管理器 / Window manager
|
|
25
|
+
window_manager: str = "fluxbox"
|
|
26
|
+
|
|
27
|
+
# 目录 / Directories
|
|
28
|
+
log_dir: Path = field(default_factory=lambda: Path.home() / ".dev-vnc" / "logs")
|
|
29
|
+
run_dir: Path = field(default_factory=lambda: Path.home() / ".dev-vnc" / "run")
|
|
30
|
+
config_dir: Path = field(default_factory=lambda: Path.home() / ".config" / "dev-vnc")
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_env(cls) -> "DevVNCConfig":
|
|
34
|
+
"""从环境变量加载配置 / Load config from environment"""
|
|
35
|
+
config = cls()
|
|
36
|
+
|
|
37
|
+
# 先尝试加载配置文件 / Try loading config file first
|
|
38
|
+
config_file = os.environ.get(
|
|
39
|
+
"DEV_VNC_CONFIG",
|
|
40
|
+
str(Path.home() / ".config" / "dev-vnc" / "config.env")
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if os.path.exists(config_file):
|
|
44
|
+
config._load_env_file(config_file)
|
|
45
|
+
|
|
46
|
+
# 环境变量覆盖 / Override with environment variables
|
|
47
|
+
config.display_num = int(os.environ.get("DEV_VNC_DISPLAY", config.display_num))
|
|
48
|
+
config.vnc_port = int(os.environ.get("DEV_VNC_PORT", config.vnc_port))
|
|
49
|
+
config.novnc_port = int(os.environ.get("DEV_VNC_NOVNC_PORT", config.novnc_port))
|
|
50
|
+
config.resolution = os.environ.get("DEV_VNC_RESOLUTION", config.resolution)
|
|
51
|
+
config.password = os.environ.get("DEV_VNC_PASSWORD", config.password)
|
|
52
|
+
config.window_manager = os.environ.get("DEV_VNC_WM", config.window_manager)
|
|
53
|
+
|
|
54
|
+
if log_dir := os.environ.get("DEV_VNC_LOG_DIR"):
|
|
55
|
+
config.log_dir = Path(log_dir)
|
|
56
|
+
if run_dir := os.environ.get("DEV_VNC_RUN_DIR"):
|
|
57
|
+
config.run_dir = Path(run_dir)
|
|
58
|
+
|
|
59
|
+
return config
|
|
60
|
+
|
|
61
|
+
def _load_env_file(self, filepath: str) -> None:
|
|
62
|
+
"""加载 .env 文件 / Load .env file"""
|
|
63
|
+
try:
|
|
64
|
+
with open(filepath, "r") as f:
|
|
65
|
+
for line in f:
|
|
66
|
+
line = line.strip()
|
|
67
|
+
if line and not line.startswith("#") and "=" in line:
|
|
68
|
+
key, value = line.split("=", 1)
|
|
69
|
+
key = key.strip()
|
|
70
|
+
value = value.strip().strip('"').strip("'")
|
|
71
|
+
|
|
72
|
+
# 展开 $HOME / Expand $HOME
|
|
73
|
+
value = value.replace("$HOME", str(Path.home()))
|
|
74
|
+
|
|
75
|
+
if key == "DEV_VNC_DISPLAY":
|
|
76
|
+
self.display_num = int(value)
|
|
77
|
+
elif key == "DEV_VNC_PORT":
|
|
78
|
+
self.vnc_port = int(value)
|
|
79
|
+
elif key == "DEV_VNC_NOVNC_PORT":
|
|
80
|
+
self.novnc_port = int(value)
|
|
81
|
+
elif key == "DEV_VNC_RESOLUTION":
|
|
82
|
+
self.resolution = value
|
|
83
|
+
elif key == "DEV_VNC_PASSWORD":
|
|
84
|
+
self.password = value
|
|
85
|
+
elif key == "DEV_VNC_WM":
|
|
86
|
+
self.window_manager = value
|
|
87
|
+
elif key == "DEV_VNC_LOG_DIR":
|
|
88
|
+
self.log_dir = Path(value)
|
|
89
|
+
elif key == "DEV_VNC_RUN_DIR":
|
|
90
|
+
self.run_dir = Path(value)
|
|
91
|
+
except Exception:
|
|
92
|
+
pass # 忽略解析错误 / Ignore parse errors
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def display(self) -> str:
|
|
96
|
+
"""返回 DISPLAY 环境变量值 / Return DISPLAY env value"""
|
|
97
|
+
return f":{self.display_num}"
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def pid_file(self) -> Path:
|
|
101
|
+
"""PID 文件路径 / PID file path"""
|
|
102
|
+
return self.run_dir / "server.pid"
|
|
103
|
+
|
|
104
|
+
def ensure_dirs(self) -> None:
|
|
105
|
+
"""确保所有目录存在 / Ensure directories exist"""
|
|
106
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
self.run_dir.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
|
|
110
|
+
# VNC 目录 / VNC directory
|
|
111
|
+
vnc_dir = Path.home() / ".vnc"
|
|
112
|
+
vnc_dir.mkdir(exist_ok=True)
|
|
113
|
+
|
|
114
|
+
def to_dict(self) -> dict:
|
|
115
|
+
"""转换为字典 / Convert to dict"""
|
|
116
|
+
return {
|
|
117
|
+
"display_num": self.display_num,
|
|
118
|
+
"vnc_port": self.vnc_port,
|
|
119
|
+
"novnc_port": self.novnc_port,
|
|
120
|
+
"resolution": self.resolution,
|
|
121
|
+
"password": self.password,
|
|
122
|
+
"window_manager": self.window_manager,
|
|
123
|
+
"log_dir": str(self.log_dir),
|
|
124
|
+
"run_dir": str(self.run_dir),
|
|
125
|
+
"config_dir": str(self.config_dir),
|
|
126
|
+
}
|
devvnc/server.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dev VNC Server - 服务器核心实现 / Core server implementation
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import socket
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, List, Dict
|
|
11
|
+
|
|
12
|
+
from .config import DevVNCConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DevVNCServer:
|
|
16
|
+
"""VNC 远程桌面服务器 / VNC remote desktop server"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, config: Optional[DevVNCConfig] = None):
|
|
19
|
+
self.config = config or DevVNCConfig.from_env()
|
|
20
|
+
self._processes: Dict[str, subprocess.Popen] = {}
|
|
21
|
+
|
|
22
|
+
def is_running(self) -> bool:
|
|
23
|
+
"""检查服务是否正在运行 / Check whether the service is running"""
|
|
24
|
+
if self.config.pid_file.exists():
|
|
25
|
+
try:
|
|
26
|
+
pid = int(self.config.pid_file.read_text().strip())
|
|
27
|
+
# 检查进程是否存在 / Check whether the process exists
|
|
28
|
+
os.kill(pid, 0)
|
|
29
|
+
return True
|
|
30
|
+
except (ProcessLookupError, ValueError):
|
|
31
|
+
pass
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
def get_status(self) -> Dict[str, bool]:
|
|
35
|
+
"""获取各组件状态 / Get component status"""
|
|
36
|
+
status = {
|
|
37
|
+
"xvfb": False,
|
|
38
|
+
"x11vnc": False,
|
|
39
|
+
"novnc": False,
|
|
40
|
+
"window_manager": False,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# 检查 Xvfb / Check Xvfb
|
|
44
|
+
status["xvfb"] = self._check_process(f"Xvfb :{self.config.display_num}")
|
|
45
|
+
|
|
46
|
+
# 检查 x11vnc / Check x11vnc
|
|
47
|
+
status["x11vnc"] = self._check_process(f"x11vnc.*:{self.config.display_num}")
|
|
48
|
+
|
|
49
|
+
# 检查 websockify (noVNC) / Check websockify (noVNC)
|
|
50
|
+
status["novnc"] = self._check_process(f"websockify.*{self.config.novnc_port}")
|
|
51
|
+
|
|
52
|
+
# 检查窗口管理器 / Check window manager
|
|
53
|
+
status["window_manager"] = self._check_process(self.config.window_manager)
|
|
54
|
+
|
|
55
|
+
return status
|
|
56
|
+
|
|
57
|
+
def _check_process(self, pattern: str) -> bool:
|
|
58
|
+
"""检查进程是否存在 / Check whether a process exists"""
|
|
59
|
+
try:
|
|
60
|
+
result = subprocess.run(
|
|
61
|
+
["pgrep", "-f", pattern],
|
|
62
|
+
capture_output=True,
|
|
63
|
+
text=True
|
|
64
|
+
)
|
|
65
|
+
return result.returncode == 0
|
|
66
|
+
except Exception:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def _kill_process(self, pattern: str) -> None:
|
|
70
|
+
"""终止匹配的进程 / Terminate matching processes"""
|
|
71
|
+
try:
|
|
72
|
+
subprocess.run(
|
|
73
|
+
["pkill", "-f", pattern],
|
|
74
|
+
capture_output=True
|
|
75
|
+
)
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def start(self) -> bool:
|
|
80
|
+
"""启动服务 / Start the service"""
|
|
81
|
+
if self.is_running():
|
|
82
|
+
print("⚠️ 服务已在运行 / Service is already running")
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
self.config.ensure_dirs()
|
|
86
|
+
self._check_dependencies()
|
|
87
|
+
self._setup_vnc_password()
|
|
88
|
+
|
|
89
|
+
print("\n🚀 启动远程桌面服务... / Starting remote desktop service...\n")
|
|
90
|
+
|
|
91
|
+
# 清理旧进程 / Clean up old processes
|
|
92
|
+
self._cleanup()
|
|
93
|
+
time.sleep(1)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
# 1. 启动 Xvfb / Start Xvfb
|
|
97
|
+
print(f"📺 启动虚拟显示器 (Display :{self.config.display_num})... / Starting virtual display")
|
|
98
|
+
self._start_xvfb()
|
|
99
|
+
time.sleep(2)
|
|
100
|
+
|
|
101
|
+
# 2. 启动窗口管理器 / Start window manager
|
|
102
|
+
print(f"🪟 启动窗口管理器 ({self.config.window_manager})... / Starting window manager")
|
|
103
|
+
self._start_window_manager()
|
|
104
|
+
time.sleep(1)
|
|
105
|
+
|
|
106
|
+
# 3. 启动 VNC / Start VNC
|
|
107
|
+
print(f"🔌 启动 VNC 服务器 (端口 {self.config.vnc_port})... / Starting VNC server")
|
|
108
|
+
self._start_vnc()
|
|
109
|
+
time.sleep(1)
|
|
110
|
+
|
|
111
|
+
# 4. 启动 noVNC / Start noVNC
|
|
112
|
+
print(f"🌐 启动 noVNC Web 服务器 (端口 {self.config.novnc_port})... / Starting noVNC web server")
|
|
113
|
+
self._start_novnc()
|
|
114
|
+
time.sleep(1)
|
|
115
|
+
|
|
116
|
+
# 保存 PID / Save PID
|
|
117
|
+
self.config.pid_file.write_text(str(os.getpid()))
|
|
118
|
+
|
|
119
|
+
print("\n✅ 远程桌面服务已成功启动! / Remote desktop service started!")
|
|
120
|
+
self.show_info()
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
print(f"\n❌ 启动失败: {e} / Start failed")
|
|
125
|
+
self._cleanup()
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
def stop(self) -> bool:
|
|
129
|
+
"""停止服务 / Stop the service"""
|
|
130
|
+
print("\n🛑 停止远程桌面服务... / Stopping remote desktop service...")
|
|
131
|
+
|
|
132
|
+
self._cleanup()
|
|
133
|
+
|
|
134
|
+
# 清理 PID 文件 / Clean PID files
|
|
135
|
+
for pid_file in self.config.run_dir.glob("*.pid"):
|
|
136
|
+
pid_file.unlink(missing_ok=True)
|
|
137
|
+
|
|
138
|
+
print("✅ 远程桌面服务已停止 / Remote desktop service stopped")
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
def restart(self) -> bool:
|
|
142
|
+
"""重启服务 / Restart the service"""
|
|
143
|
+
self.stop()
|
|
144
|
+
time.sleep(2)
|
|
145
|
+
return self.start()
|
|
146
|
+
|
|
147
|
+
def _cleanup(self) -> None:
|
|
148
|
+
"""清理所有相关进程 / Clean all related processes"""
|
|
149
|
+
self._kill_process(f"Xvfb :{self.config.display_num}")
|
|
150
|
+
self._kill_process(f"x11vnc.*:{self.config.display_num}")
|
|
151
|
+
self._kill_process(f"websockify.*{self.config.novnc_port}")
|
|
152
|
+
self._kill_process(self.config.window_manager)
|
|
153
|
+
|
|
154
|
+
def _check_dependencies(self) -> None:
|
|
155
|
+
"""检查依赖 / Check dependencies"""
|
|
156
|
+
missing = []
|
|
157
|
+
|
|
158
|
+
for cmd in ["Xvfb", "x11vnc", "websockify"]:
|
|
159
|
+
if not self._command_exists(cmd):
|
|
160
|
+
missing.append(cmd)
|
|
161
|
+
|
|
162
|
+
if not self._command_exists(self.config.window_manager):
|
|
163
|
+
missing.append(self.config.window_manager)
|
|
164
|
+
|
|
165
|
+
if missing:
|
|
166
|
+
raise RuntimeError(
|
|
167
|
+
f"缺少依赖: {', '.join(missing)} / Missing dependencies\n"
|
|
168
|
+
f"请运行: dev-vnc install-deps / Please run: dev-vnc install-deps"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def _command_exists(self, cmd: str) -> bool:
|
|
172
|
+
"""检查命令是否存在 / Check whether a command exists"""
|
|
173
|
+
try:
|
|
174
|
+
result = subprocess.run(
|
|
175
|
+
["which", cmd],
|
|
176
|
+
capture_output=True
|
|
177
|
+
)
|
|
178
|
+
return result.returncode == 0
|
|
179
|
+
except Exception:
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
def _setup_vnc_password(self) -> None:
|
|
183
|
+
"""设置 VNC 密码 / Set VNC password"""
|
|
184
|
+
passwd_file = Path.home() / ".vnc" / "passwd"
|
|
185
|
+
|
|
186
|
+
if not passwd_file.exists():
|
|
187
|
+
try:
|
|
188
|
+
subprocess.run(
|
|
189
|
+
["x11vnc", "-storepasswd", self.config.password, str(passwd_file)],
|
|
190
|
+
capture_output=True,
|
|
191
|
+
text=True,
|
|
192
|
+
input="y\n"
|
|
193
|
+
)
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
def _start_xvfb(self) -> None:
|
|
198
|
+
"""启动 Xvfb / Start Xvfb"""
|
|
199
|
+
cmd = [
|
|
200
|
+
"Xvfb",
|
|
201
|
+
f":{self.config.display_num}",
|
|
202
|
+
"-screen", "0", self.config.resolution
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
proc = subprocess.Popen(
|
|
206
|
+
cmd,
|
|
207
|
+
stdout=subprocess.DEVNULL,
|
|
208
|
+
stderr=subprocess.DEVNULL
|
|
209
|
+
)
|
|
210
|
+
self._processes["xvfb"] = proc
|
|
211
|
+
self._save_pid("xvfb", proc.pid)
|
|
212
|
+
|
|
213
|
+
def _start_window_manager(self) -> None:
|
|
214
|
+
"""启动窗口管理器 / Start window manager"""
|
|
215
|
+
env = os.environ.copy()
|
|
216
|
+
env["DISPLAY"] = self.config.display
|
|
217
|
+
|
|
218
|
+
proc = subprocess.Popen(
|
|
219
|
+
[self.config.window_manager],
|
|
220
|
+
env=env,
|
|
221
|
+
stdout=subprocess.DEVNULL,
|
|
222
|
+
stderr=subprocess.DEVNULL
|
|
223
|
+
)
|
|
224
|
+
self._processes["wm"] = proc
|
|
225
|
+
self._save_pid("wm", proc.pid)
|
|
226
|
+
|
|
227
|
+
def _start_vnc(self) -> None:
|
|
228
|
+
"""启动 VNC 服务器 / Start VNC server"""
|
|
229
|
+
passwd_file = Path.home() / ".vnc" / "passwd"
|
|
230
|
+
log_file = self.config.log_dir / "x11vnc.log"
|
|
231
|
+
|
|
232
|
+
cmd = [
|
|
233
|
+
"x11vnc",
|
|
234
|
+
"-display", self.config.display,
|
|
235
|
+
"-forever",
|
|
236
|
+
"-shared",
|
|
237
|
+
"-rfbport", str(self.config.vnc_port),
|
|
238
|
+
"-rfbauth", str(passwd_file),
|
|
239
|
+
"-bg",
|
|
240
|
+
"-o", str(log_file)
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
subprocess.run(cmd, capture_output=True)
|
|
244
|
+
|
|
245
|
+
def _start_novnc(self) -> None:
|
|
246
|
+
"""启动 noVNC / Start noVNC"""
|
|
247
|
+
# 查找 noVNC 路径 / Find noVNC path
|
|
248
|
+
novnc_paths = [
|
|
249
|
+
"/usr/share/novnc",
|
|
250
|
+
"/usr/share/javascript/novnc",
|
|
251
|
+
"/usr/share/webapps/novnc",
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
novnc_path = None
|
|
255
|
+
for path in novnc_paths:
|
|
256
|
+
if os.path.isdir(path):
|
|
257
|
+
novnc_path = path
|
|
258
|
+
break
|
|
259
|
+
|
|
260
|
+
if not novnc_path:
|
|
261
|
+
print("⚠️ noVNC 未找到,仅提供 VNC 连接 / noVNC not found, VNC only")
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
log_file = self.config.log_dir / "websockify.log"
|
|
265
|
+
|
|
266
|
+
with open(log_file, "w") as f:
|
|
267
|
+
proc = subprocess.Popen(
|
|
268
|
+
[
|
|
269
|
+
"websockify",
|
|
270
|
+
f"--web={novnc_path}",
|
|
271
|
+
str(self.config.novnc_port),
|
|
272
|
+
f"localhost:{self.config.vnc_port}"
|
|
273
|
+
],
|
|
274
|
+
stdout=f,
|
|
275
|
+
stderr=f
|
|
276
|
+
)
|
|
277
|
+
self._processes["novnc"] = proc
|
|
278
|
+
self._save_pid("novnc", proc.pid)
|
|
279
|
+
|
|
280
|
+
def _save_pid(self, name: str, pid: int) -> None:
|
|
281
|
+
"""保存 PID / Save PID"""
|
|
282
|
+
pid_file = self.config.run_dir / f"{name}.pid"
|
|
283
|
+
pid_file.write_text(str(pid))
|
|
284
|
+
|
|
285
|
+
def get_local_ip(self) -> str:
|
|
286
|
+
"""获取本机 IP 地址 / Get local IP address"""
|
|
287
|
+
try:
|
|
288
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
289
|
+
s.connect(("8.8.8.8", 80))
|
|
290
|
+
ip = s.getsockname()[0]
|
|
291
|
+
s.close()
|
|
292
|
+
return ip
|
|
293
|
+
except Exception:
|
|
294
|
+
return "localhost"
|
|
295
|
+
|
|
296
|
+
def show_info(self) -> None:
|
|
297
|
+
"""显示访问信息 / Show access information"""
|
|
298
|
+
local_ip = self.get_local_ip()
|
|
299
|
+
|
|
300
|
+
print("""
|
|
301
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
302
|
+
║ 🖥️ 远程桌面访问信息 ║
|
|
303
|
+
║ 🖥️ Remote Desktop Access ║
|
|
304
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
305
|
+
""")
|
|
306
|
+
print(" 📍 浏览器访问 (推荐) / Browser access (recommended):")
|
|
307
|
+
print(f" http://{local_ip}:{self.config.novnc_port}/vnc.html")
|
|
308
|
+
print(f" http://localhost:{self.config.novnc_port}/vnc.html (本机 / local)")
|
|
309
|
+
print()
|
|
310
|
+
print(" 🔌 VNC 客户端连接 / VNC client:")
|
|
311
|
+
print(f" 地址 / Address: {local_ip}:{self.config.vnc_port}")
|
|
312
|
+
print(f" 密码 / Password: {self.config.password}")
|
|
313
|
+
print()
|
|
314
|
+
print(" 🚀 在远程桌面中运行 GUI 程序 / Run GUI apps in remote desktop:")
|
|
315
|
+
print(f" export DISPLAY={self.config.display}")
|
|
316
|
+
print(" your-gui-application")
|
|
317
|
+
print()
|
|
318
|
+
print(" 💡 快捷命令 / Quick command:")
|
|
319
|
+
print(" devvnc run <command>")
|
|
320
|
+
print()
|
|
321
|
+
|
|
322
|
+
def show_status(self) -> None:
|
|
323
|
+
"""显示状态 / Show status"""
|
|
324
|
+
status = self.get_status()
|
|
325
|
+
|
|
326
|
+
print("""
|
|
327
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
328
|
+
║ 📊 服务状态 ║
|
|
329
|
+
║ 📊 Service Status ║
|
|
330
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
331
|
+
""")
|
|
332
|
+
|
|
333
|
+
def status_icon(running: bool) -> str:
|
|
334
|
+
return "✅ 运行中 / Running" if running else "❌ 未运行 / Stopped"
|
|
335
|
+
|
|
336
|
+
print(f" Xvfb: {status_icon(status['xvfb'])}")
|
|
337
|
+
print(f" x11vnc: {status_icon(status['x11vnc'])}")
|
|
338
|
+
print(f" noVNC: {status_icon(status['novnc'])}")
|
|
339
|
+
print(f" {self.config.window_manager}: {status_icon(status['window_manager'])}")
|
|
340
|
+
print()
|
|
341
|
+
|
|
342
|
+
def show_config(self) -> None:
|
|
343
|
+
"""显示配置 / Show configuration"""
|
|
344
|
+
print("""
|
|
345
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
346
|
+
║ ⚙️ 当前配置 ║
|
|
347
|
+
║ ⚙️ Current Configuration ║
|
|
348
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
349
|
+
""")
|
|
350
|
+
for key, value in self.config.to_dict().items():
|
|
351
|
+
print(f" {key}: {value}")
|
|
352
|
+
print()
|
|
353
|
+
|
|
354
|
+
def show_logs(self, log_type: str = "all") -> None:
|
|
355
|
+
"""显示日志 / Show logs"""
|
|
356
|
+
if log_type in ("vnc", "all"):
|
|
357
|
+
vnc_log = self.config.log_dir / "x11vnc.log"
|
|
358
|
+
if vnc_log.exists():
|
|
359
|
+
print("=== VNC 日志 / VNC Logs ===")
|
|
360
|
+
print(vnc_log.read_text()[-5000:]) # 最后 5000 字符 / Last 5000 chars
|
|
361
|
+
|
|
362
|
+
if log_type in ("novnc", "all"):
|
|
363
|
+
novnc_log = self.config.log_dir / "websockify.log"
|
|
364
|
+
if novnc_log.exists():
|
|
365
|
+
print("\n=== noVNC 日志 / noVNC Logs ===")
|
|
366
|
+
print(novnc_log.read_text()[-5000:])
|
|
367
|
+
|
|
368
|
+
def run_command(self, command: List[str]) -> int:
|
|
369
|
+
"""在 VNC 环境中运行命令 / Run command in VNC environment"""
|
|
370
|
+
if not self.is_running():
|
|
371
|
+
print("❌ 服务未运行,请先执行: devvnc start / Service not running, run: devvnc start")
|
|
372
|
+
return 1
|
|
373
|
+
|
|
374
|
+
env = os.environ.copy()
|
|
375
|
+
env["DISPLAY"] = self.config.display
|
|
376
|
+
|
|
377
|
+
result = subprocess.run(command, env=env)
|
|
378
|
+
return result.returncode
|