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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ devvnc = devvnc.cli:main
@@ -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