Repository: vprix/vncproxy Branch: main Commit: 78d96284a539 Files: 149 Total size: 292.6 KB Directory structure: gitextract_0eg_p_n1/ ├── .gitignore ├── LICENSE ├── README.md ├── bin/ │ └── .keep ├── build ├── canvas/ │ ├── canvas.go │ └── rgb_image.go ├── cmd/ │ ├── player/ │ │ ├── main.go │ │ ├── tcpServer.go │ │ └── wsServer.go │ ├── proxy/ │ │ ├── main.go │ │ ├── tcpServer.go │ │ └── wsServer.go │ ├── recorder/ │ │ ├── main.go │ │ └── recorder.go │ ├── screenshot/ │ │ └── main.go │ └── video/ │ └── main.go ├── docs/ │ ├── .keep │ ├── .nojekyll │ ├── README.md │ ├── changelog.md │ ├── index.html │ ├── overview.md │ ├── player/ │ │ ├── .keep │ │ └── README.md │ ├── proxy/ │ │ ├── .keep │ │ └── README.md │ ├── questions.md │ ├── recorder/ │ │ ├── .keep │ │ └── README.md │ ├── rfc6143/ │ │ ├── GLOSSORY.md │ │ ├── README.md │ │ ├── handshake/ │ │ │ ├── README.md │ │ │ ├── initial.md │ │ │ ├── protocol-version.md │ │ │ └── security-type.md │ │ └── transfer/ │ │ ├── README.md │ │ ├── display.md │ │ ├── encoding/ │ │ │ ├── README.md │ │ │ ├── copy-rect.md │ │ │ ├── raw.md │ │ │ ├── rise-and-run-length.md │ │ │ ├── set-encoding.md │ │ │ ├── tight-png.md │ │ │ ├── tiled-run-length.md │ │ │ └── zlib-run-length.md │ │ ├── input/ │ │ │ ├── README.md │ │ │ ├── clipboard.md │ │ │ ├── keyboard.md │ │ │ └── mouse.md │ │ ├── pixel-format.md │ │ └── set-color-map.md │ ├── screenshot/ │ │ ├── .keep │ │ └── README.md │ ├── summary.md │ └── video/ │ ├── .keep │ └── README.md ├── encodings/ │ ├── default_encoding.go │ ├── encoding.go │ ├── encoding_copyrect.go │ ├── encoding_corre.go │ ├── encoding_h264.go │ ├── encoding_hextile.go │ ├── encoding_jpeg.go │ ├── encoding_jrle.go │ ├── encoding_raw.go │ ├── encoding_rre.go │ ├── encoding_tight.go │ ├── encoding_tightpng.go │ ├── encoding_trle.go │ ├── encoding_zlib.go │ ├── encoding_zrle.go │ ├── pseudo_cursor.go │ ├── pseudo_cursor_with_alpha.go │ ├── pseudo_desktop_name.go │ ├── pseudo_desktop_size.go │ ├── pseudo_extended_desktop_size.go │ ├── pseudo_fence.go │ ├── pseudo_last_rect.go │ ├── pseudo_led_state.go │ ├── pseudo_pointer_pos.go │ └── pseudo_x_cursor.go ├── go.mod ├── handler/ │ ├── ClientClientInitHandler.go │ ├── ClientMessageHandler.go │ ├── ClientSecurityHandler.go │ ├── ClientServerInitHandler.go │ ├── ClientVersionHandler.go │ ├── ServerClientInitHandler.go │ ├── ServerMessageHandler.go │ ├── ServerSecurityHandler.go │ ├── ServerServerInitHandler.go │ └── ServerVersionHandler.go ├── internal/ │ ├── dbuffer/ │ │ ├── buffer.go │ │ └── pool.go │ └── syncPool/ │ └── sync_pool.go ├── messages/ │ ├── clientClientCutText.go │ ├── clientClientFence.go │ ├── clientEnableContinuousUpdates.go │ ├── clientFramebufferUpdateRequest.go │ ├── clientKeyEvent.go │ ├── clientPointerEvent.go │ ├── clientQEMUExtKeyEvent.go │ ├── clientSetDesktopSize.go │ ├── clientSetEncodings.go │ ├── clientSetPixelFormat.go │ ├── default_message.go │ ├── serverBell.go │ ├── serverEndOfContinuousUpdates.go │ ├── serverFramebufferUpdate.go │ ├── serverInit.go │ ├── serverServerCutText.go │ ├── serverServerFence.go │ └── serverSetColorMapEntries.go ├── rfb/ │ ├── color_map.go │ ├── desktop.go │ ├── encoding.go │ ├── encodingtype.go │ ├── encodingtype_string.go │ ├── handler.go │ ├── keys.go │ ├── keys_string.go │ ├── message.go │ ├── message_type_client_string.go │ ├── message_type_server_string.go │ ├── messagetype.go │ ├── options.go │ ├── pixel_format.go │ ├── rectangle.go │ ├── security.go │ ├── securitysubtype_string.go │ ├── securitytype_string.go │ ├── session.go │ ├── session_string.go │ └── target_config.go ├── security/ │ ├── security_none.go │ ├── security_tight.go │ ├── security_vencryptplain.go │ └── security_vnc.go ├── session/ │ ├── canvas.go │ ├── client.go │ ├── player.go │ ├── recorder.go │ └── server.go └── vnc/ ├── player.go ├── proxy.go ├── recorder.go ├── screenshot.go └── video.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # VncProxy [![GitHub release](https://img.shields.io/github/v/release/vprix/vncproxy.svg?style=flat-square)](https://github.com/vprix/vncproxy/releases) [![report card](https://goreportcard.com/badge/github.com/vprix/vncproxy?style=flat-square)](http://goreportcard.com/report/vprix/vncproxy) [![github issues](https://img.shields.io/github/issues/vprix/vncproxy.svg?style=flat-square)](https://github.com/vprix/vncproxy/issues?q=is%3Aopen+is%3Aissue) [![github closed issues](https://img.shields.io/github/issues-closed-raw/vprix/vncproxy.svg?style=flat-square)](https://github.com/vprix/vncproxy/issues?q=is%3Aissue+is%3Aclosed) [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/vprix/vncproxy) [![view examples](https://img.shields.io/badge/learn%20by-examples-00BCD4.svg?style=flat-square)](https://github.com/vprix/vncproxy/tree/main/examples) ## VncProxy简介 `VncProxy` 是使用`Golang`实现的`Vnc`远程桌面代理组件,完全解析`rfb`协议,支持远程桌面代理,rbs文件录屏,rbs文件回放,截图,录制视频. * 全协议支持的vnc proxy。 * 支持Tcp代理 * 支持Websocket代理 * 屏幕录像,保存为`RBS`文件 * 重播服务器,支持vnc客户端链接,播放`RBS`文件。 * 支持实时录制视频 * 支持通过`RBS`文件录制视频。 * 支持屏幕截图 ## 支持的编码格式 - [x] Raw - [x] CopyRect - [x] CoRRE - [x] rre - [x] Hextile - [x] Tight - [x] TightPng - [x] ZLib - [x] Zrle - [x] CursorPseudo - [x] CursorWithAlphaPseudo - [x] DesktopNamePseudo - [x] DesktopSizePseudo - [x] ExtendedDesktopSizePseudo - [x] LedStatePseudo - [x] CursorPosPseudo - [x] XCursorPseudo - [ ] jpeg - [ ] jrle - [ ] trle ## 组件说明 ### Proxy 1. 启动`server`接受`vnc viewer`的链接. 2. 启动`client`连接到指定的`vnc server`. 3. 为`vnc viewer`和`vnc server`之间建立起消息转发通道。 4. 因为`rfb`协议被完全解析,可以针对通信的消息进行转发处理,产生了后续的功能。 ### Recorder 1. 启动`client`连接到指定的`vnc server`. 2. 发送帧缓冲区更新消息`FramebufferUpdateRequest`到`vnc server`。 3. 处理`vnc server`回复的界面更新消息`FramebufferUpdate`。 4. 把这一过程以`rbs`文件格式记录下来。 ### Player 1. 启动`server`接受`vnc viewer`的链接. 2. 读取`rbs`文件,并按格式生成`FramebufferUpdate`消息发送给`vnc viewer`。 3. `vnc viewer`的界面就会回放动作。 ### Video 1. 支持`Proxy`,`Recorder`和`rbs`文件作为输入源。 2. 把`FramebufferUpdate`消息转换为视频文件。 ### Screenshot 1. 支持`Proxy`,`Recorder`和`rbs`文件作为输入源。 2. 把当前的界面视图转换为图片文件。 ## 使用说明 `vncProxy`项目有多种应用场景。 可以作为单独的应用程序编译,也可以作为库被其他应用程序引用。 接下来,分别介绍各种场景下的使用方式。 ### 编译 ```shell # 使用方式: # build.sh [-s app_name] [-v version] [-g go_bin] # app_name 需要编译的应用名称 # 选项: proxy,player,recorder,video,screenshot. # 默认是所有应用,多个应用可以逗号分割. # version 编译后的文件版本号,默认为当前git的commit id. # go_bin 使用的golang程序 # 编译所有应用 $ ./build # 编译proxy $ ./build -s proxy -v v0.1.0 # 编译player,recorder $ ./build -s player,recorder -v v0.1.0 ``` 编译后的二进制文件在`./bin/`目录 ### Proxy 代码路径在`./cmd/proxy`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 #### 获取帮助信息 ```shell # 查看帮助信息 $ ./proxy --help # 查看版本信息 $ ./proxy version ``` #### 启动tcp服务 ```shell # 启动tcp server接受vnc viewer的连接 # vncHost vnc服务器host # vncPort vnc服务器port # vncPassword vnc服务器密码 # tcpHost 本地监听的地址 # tcpPort 本地监听的端口 # proxyPassword vnc连接的密码 # debug 使用debug模式启动服务 $ ./proxy start tcpServer --vncHost=192.168.1.2 \ --vncPort=5901 \ --vncPassword=vprix \ --tcpHost=0.0.0.0 \ --tcpPort=8989 \ --proxyPassword=12345612 \ --debug ``` #### 启动WebSocket服务 ```shell # 启动ws server接受novnc的连接 # vncHost vnc服务器host # vncPort vnc服务器port # vncPassword vnc服务器密码 # wsHost 本地监听的地址 # wsPort 本地监听的端口 # wsPath websocket连接的地址 # proxyPassword vnc连接的密码 # debug 使用debug模式启动服务 $ ./proxy start wsServer --vncHost=192.168.1.2 \ --vncPort=5901 \ --vncPassword=vprix \ --wsHost=0.0.0.0 \ --wsPort=8988 \ --wsPath=/websockify \ --proxyPassword=12345612 \ --debug ``` ### Recorder 代码路径在`./cmd/recorder`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 #### 获取帮助信息 ```shell # 查看帮助信息 $ ./recorder --help # 查看版本信息 $ ./recorder version ``` #### 启动Recorder服务 ```shell # rbsFile 要保存的rbs文件路径(必填) # vncHost vnc服务器host # vncPort vnc服务器port # vncPassword vnc服务器密码 # debug 使用debug模式启动服务 $ ./recorder start --rbsFile=/path/to/foo.rbs --vncHost=192.168.1.2 --vncPort=5901 --vncPassword=vprix --debug ``` ### Player 代码路径在`./cmd/player`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 #### 获取帮助信息 ```shell # 查看帮助信息 $ ./player --help # 查看版本信息 $ ./player version ``` #### 启动Player Tcp服务 ```shell # rbsFile 要保存的rbs文件路径(必填) # tcpHost 本地监听的tcp协议地址 默认0.0.0.0 # tcpPort 本地监听的tcp协议端口 默认8989 # proxyPassword 连接到proxy的密码 不传入密码则使用auth none # debug 使用debug模式启动服务 $ ./player start tcpServer --rbsFile=/path/to/foo.rbs --tcpHost=0.0.0.0 --tcpPort=8989 --proxyPassword=12345612 --debug ``` #### 启动Player WS服务 ```shell # rbsFile 要保存的rbs文件路径(必填) # wsHost 启动websocket服务的本地地址 默认 0.0.0.0 # wsPort 启动websocket服务的本地端口 默认8988 # wsPath 启动websocket服务的url path 默认'/' # proxyPassword 连接到proxy的密码 不传入密码则使用auth none # debug 使用debug模式启动服务 $ ./player start wsServer --rbsFile=/path/to/foo.rbs --wsHost=0.0.0.0 --wsPort=8989 --wsPath=/ --proxyPassword=12345612 --debug ``` ### Screenshot 代码路径在`./cmd/screenshot`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 #### 获取帮助信息 ```shell # 查看帮助信息 $ ./screenshot --help # 查看版本信息 $ ./screenshot version ``` #### 启动Screenshot 获取vnc服务器的屏幕截图 ```shell # imageFile 要生成的截图地址,暂时只支持jpeg格式(必填) # vncHost 要连接的vnc服务端地址(必填) # vncPort 要连接的vnc服务端端口(必填) # vncPassword 要连接的vnc服务端密码,不传则使用auth none $ ./screenshot --imageFile=./screen.jpeg --vncHost=127.0.0.1 --vncPort=5900 --vncPassword=12345612 ``` ## 项目参考 本项目参考了以下项目完成。 * [vncproxy](https://github.com/amitbet/vncproxy) * [vnc2video](https://github.com/amitbet/vnc2video) * [rfbproto](https://github.com/rfbproto/rfbproto) ## 交流 我在做这个项目的过程中碰到了很多问题,查遍了互联网,缺少中文资料,大部分信息都是雷同的。 所以我萌生了开源的想法,帮助更多有需要的人。 我建立了一个可供交流的微信群,以便大家在使用的过程中碰到疑问,能有解答的地方。 当然,如果你对vnc有兴趣,也可以加我微信,多多交流。 欢迎各位贡献代码。 ![微信二维码](/docs/images/5bb8dbe702ce04b0bdde8c26583b152.jpg) ================================================ FILE: bin/.keep ================================================ ================================================ FILE: build ================================================ #!/usr/bin/env bash set -e export GOPROXY=https://proxy.golang.com.cn,direct help() { echo "使用方式:" echo " build.sh [-s app_name] [-v version] [-g go_bin]" echo "参数详解:" echo " app_name 需要编译的应用名称,选项: proxy,player,recorder,video,screenshot.默认是所有应用,多个应用可以逗号分割" echo " version 编译后的文件版本号,默认为当前git的commit id" echo " go_bin 使用的golang程序" exit } getOutFile(){ build_name=$1 output_dir=$2 output_file="${output_dir}"/${build_name} } while getopts 's:v:g:h' OPT; do case $OPT in s) app_names="$OPTARG";; v) build_version="$OPTARG";; g) goBin="$OPTARG";; h) help;; ?) help;; esac done ## 获取当前环境 ## shellcheck disable=SC2046 cd $(dirname "$0")/ || exit 1; # 如果go bin 不存在,则去环境变量中查找 if [ ! -x "$goBin" ]; then goBin=$(which go) fi if [ ! -x "$goBin" ]; then echo "No goBin found." exit 2 fi # 编译时间 build_date=$(date +"%Y-%m-%d %H:%M:%S") # 编译时候当前git的commit id build_git=$(git rev-parse --short HEAD) # 编译的golang版本 go_version=$(${goBin} version) #编译版本 if [ -z "$build_version" ]; then build_version="$build_git" fi if [ -z "$app_names" ]; then app_names="proxy,player,recorder,video,screenshot" fi echo "start to build project $app_names" "$build_date" # shellcheck disable=SC2154 echo "$go_version" pwd root_dir="$(pwd)" ldflags=() # 链接时设置变量值 ldflags+=("-X" "\"main.BuildVersion=${build_version}\"") ldflags+=("-X" "\"github.com/osgochina/dmicro/easyservice.BuildVersion=${build_version}\"") ldflags+=("-X" "\"github.com/osgochina/dmicro/easyservice.BuildGoVersion=${go_version}\"") ldflags+=("-X" "\"github.com/osgochina/dmicro/easyservice.BuildGitCommitId=${build_git}\"") ldflags+=("-X" "\"github.com/osgochina/dmicro/easyservice.BuildTime=${build_date}\"") for app_name in $(echo $app_names | sed "s/,/ /g") do getOutFile $app_name "$root_dir/bin" cd "$root_dir/cmd/$app_name/" echo "进入[$(pwd)]目录" ${goBin} build -v -ldflags "${ldflags[*]}" -o "${output_file}" || exit 1 echo "build $app_name done." done ================================================ FILE: canvas/canvas.go ================================================ package canvas import ( "encoding/binary" "errors" "fmt" "github.com/vprix/vncproxy/rfb" "image" "image/color" "image/draw" "io" ) const ( BlockWidth = 16 BlockHeight = 16 ) type VncCanvas struct { draw.Image imageBuffs [2]draw.Image Cursor draw.Image CursorMask [][]bool CursorBackup draw.Image CursorOffset *image.Point CursorLocation *image.Point DrawCursor bool Changed map[string]bool } func NewVncCanvas(width, height int) *VncCanvas { writeImg := NewRGBImage(image.Rect(0, 0, width, height)) canvas := &VncCanvas{ Image: writeImg, } return canvas } // Read 从链接中读取数据 func (that *VncCanvas) Read(buf []byte) (int, error) { return 0, nil } // Write 写入数据到链接 func (that *VncCanvas) Write(buf []byte) (int, error) { return 0, nil } // Close 关闭会话 func (that *VncCanvas) Close() error { return nil } func (that *VncCanvas) SetChanged(rect *rfb.Rectangle) { if that.Changed == nil { that.Changed = make(map[string]bool) } for x := int(rect.X) / BlockWidth; x*BlockWidth < int(rect.X+rect.Width); x++ { for y := int(rect.Y) / BlockHeight; y*BlockHeight < int(rect.Y+rect.Height); y++ { key := fmt.Sprintf("%d,%d", x, y) //fmt.Println("setting block: ", key) that.Changed[key] = true } } } func (that *VncCanvas) Reset(rect *rfb.Rectangle) { that.Changed = nil } func (that *VncCanvas) RemoveCursor() image.Image { if that.Cursor == nil || that.CursorLocation == nil { return that.Image } if !that.DrawCursor { return that.Image } rect := that.Cursor.Bounds() loc := that.CursorLocation img := that.Image for y := rect.Min.Y; y < int(rect.Max.Y); y++ { for x := rect.Min.X; x < int(rect.Max.X); x++ { // offset := y*int(rect.Width) + x // if bitmask[y*int(scanLine)+x/8]&(1< 0 { col := that.CursorBackup.At(x, y) //mask := c.CursorMask.At(x, y).(color.RGBA) mask := that.CursorMask[x][y] //logger.Info("Drawing Cursor: ", x, y, col, mask) if mask { //logger.Info("Drawing Cursor for real: ", x, y, col) img.Set(x+loc.X-that.CursorOffset.X, y+loc.Y-that.CursorOffset.Y, col) } // //logger.Tracef("CursorPseudoEncoding.Read: setting pixel: (%d,%d) %v", x+int(rect.X), y+int(rect.Y), colors[offset]) // } } } return img } func (that *VncCanvas) PaintCursor() image.Image { if that.Cursor == nil || that.CursorLocation == nil { return that.Image } if !that.DrawCursor { return that.Image } rect := that.Cursor.Bounds() if that.CursorBackup == nil { that.CursorBackup = image.NewRGBA(that.Cursor.Bounds()) } loc := that.CursorLocation img := that.Image for y := rect.Min.Y; y < int(rect.Max.Y); y++ { for x := rect.Min.X; x < int(rect.Max.X); x++ { // offset := y*int(rect.Width) + x // if bitmask[y*int(scanLine)+x/8]&(1< 0 { col := that.Cursor.At(x, y) //mask := c.CursorMask.At(x, y).(RGBColor) mask := that.CursorMask[x][y] backup := that.Image.At(x+loc.X-that.CursorOffset.X, y+loc.Y-that.CursorOffset.Y) //c.CursorBackup.Set(x, y, backup) //backup the previous data at this point //logger.Info("Drawing Cursor: ", x, y, col, mask) if mask { that.CursorBackup.Set(x, y, backup) //logger.Info("Drawing Cursor for real: ", x, y, col) img.Set(x+loc.X-that.CursorOffset.X, y+loc.Y-that.CursorOffset.Y, col) } // //logger.Tracef("CursorPseudoEncoding.Read: setting pixel: (%d,%d) %v", x+int(rect.X), y+int(rect.Y), colors[offset]) // } } } return img } // FillRect 为指定的矩形区域填充颜色 func (that *VncCanvas) FillRect(rect *image.Rectangle, c color.Color) { for x := rect.Min.X; x < rect.Max.X; x++ { for y := rect.Min.Y; y < rect.Max.Y; y++ { that.Set(x, y, c) } } } // ReadColor Read unmarshal color from conn func (that *VncCanvas) ReadColor(c io.Reader, pf *rfb.PixelFormat) (*color.RGBA, error) { if pf.TrueColor == 0 { return nil, errors.New("support for non true color formats was not implemented") } order := pf.Order() var pixel uint32 switch pf.BPP { case 8: var px uint8 if err := binary.Read(c, order, &px); err != nil { return nil, err } pixel = uint32(px) case 16: var px uint16 if err := binary.Read(c, order, &px); err != nil { return nil, err } pixel = uint32(px) case 32: var px uint32 if err := binary.Read(c, order, &px); err != nil { return nil, err } pixel = uint32(px) } rgb := color.RGBA{ R: uint8((pixel >> pf.RedShift) & uint32(pf.RedMax)), G: uint8((pixel >> pf.GreenShift) & uint32(pf.GreenMax)), B: uint8((pixel >> pf.BlueShift) & uint32(pf.BlueMax)), A: 1, } return &rgb, nil } func (that *VncCanvas) DecodeRaw(reader io.Reader, pf *rfb.PixelFormat, rect *rfb.Rectangle) error { for y := 0; y < int(rect.Height); y++ { for x := 0; x < int(rect.Width); x++ { col, err := that.ReadColor(reader, pf) if err != nil { return err } that.Set(int(rect.X)+x, int(rect.Y)+y, col) } } return nil } func MakeRect(x, y, width, height int) image.Rectangle { return image.Rectangle{Min: image.Point{X: x, Y: y}, Max: image.Point{X: x + width, Y: y + height}} } func MakeRectFromVncRect(rect *rfb.Rectangle) image.Rectangle { return MakeRect(int(rect.X), int(rect.Y), int(rect.Width), int(rect.Height)) } ================================================ FILE: canvas/rgb_image.go ================================================ package canvas import ( "image" "image/color" ) type RGBColor struct { R, G, B uint8 } func (that RGBColor) RGBA() (r, g, b, a uint32) { return uint32(that.R), uint32(that.G), uint32(that.B), 1 } type RGBImage struct { // Pix holds the image's pixels, in R, G, B, A order. The pixel at // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3]. Pix []uint8 // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect image.Rectangle } func (that RGBImage) ColorModel() color.Model { return nil } func (that RGBImage) Bounds() image.Rectangle { return that.Rect } func (that RGBImage) At(x, y int) color.Color { col := that.RGBAt(x, y) return color.RGBA{R: col.R, G: col.G, B: col.B, A: 1} } func (that *RGBImage) RGBAt(x, y int) *RGBColor { if !(image.Point{X: x, Y: y}.In(that.Rect)) { return &RGBColor{} } i := that.PixOffset(x, y) return &RGBColor{that.Pix[i+0], that.Pix[i+1], that.Pix[i+2]} } func (that *RGBImage) PixOffset(x, y int) int { return (y-that.Rect.Min.Y)*that.Stride + (x-that.Rect.Min.X)*3 } func (that RGBImage) Set(x, y int, c color.Color) { if !(image.Point{X: x, Y: y}.In(that.Rect)) { return } i := that.PixOffset(x, y) c1 := color.RGBAModel.Convert(c).(color.RGBA) that.Pix[i+0] = c1.R that.Pix[i+1] = c1.G that.Pix[i+2] = c1.B } func (that *RGBImage) SetRGB(x, y int, c color.RGBA) { if !(image.Point{X: x, Y: y}.In(that.Rect)) { return } i := that.PixOffset(x, y) that.Pix[i+0] = c.R that.Pix[i+1] = c.G that.Pix[i+2] = c.B } func NewRGBImage(r image.Rectangle) *RGBImage { w, h := r.Dx(), r.Dy() buf := make([]uint8, 3*w*h) return &RGBImage{buf, 3 * w, r} } ================================================ FILE: cmd/player/main.go ================================================ package main import ( "fmt" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/text/gstr" "github.com/osgochina/dmicro/easyservice" "github.com/osgochina/dmicro/logger" "golang.org/x/net/context" "os" ) var ( helpContent = gstr.TrimLeft(` USAGE ./server [start|stop|quit] [tcpServer|wsServer] [OPTION] OPTION --rbsFile 使用的rbs文件地址 必传 --tcpHost 本地监听的tcp协议地址 默认0.0.0.0 --tcpPort 本地监听的tcp协议端口 默认8989 --proxyPassword 连接到proxy的密码 不传入密码则使用auth none --wsHost 启动websocket服务的本地地址 默认 0.0.0.0 --wsPort 启动websocket服务的本地端口 默认8988 --wsPath 启动websocket服务的url path 默认'/' --debug 是否开启debug 默认debug=false -d,--daemon 使用守护进程模式启动 --pid 设置pid文件的地址,默认是/tmp/[server].pid -h,--help 获取帮助信息 -v,--version 获取编译版本信息 EXAMPLES /path/to/server /path/to/server start --env=dev --debug=true --pid=/tmp/server.pid /path/to/server start -c=config.product.toml /path/to/server start tcpServer,wsServer --config=config.product.toml /path/to/server start wsServer --rbsFile=/path/to/foo.rbs --wsHost=0.0.0.0 --wsPort=8988 --proxyPassword=12345612 --debug /path/to/server start tcpServer --rbsFile=/path/to/foo.rbs --tcpHost=0.0.0.0 --tcpPort=8989 --proxyPassword=12345612 --debug /path/to/server stop /path/to/server quit /path/to/server reload /path/to/server version /path/to/server help `) ) func main() { easyservice.Authors = "ClownFish" easyservice.SetHelpContent(helpContent) easyservice.SetOptions( map[string]bool{ "tcpHost": true, //本地监听的tcp协议地址 默认0.0.0.0 "tcpPort": true, //本地监听的tcp协议端口 默认8989 "proxyPassword": true, //连接到proxy的密码 不传入密码则使用auth none "wsHost": true, //启动websocket服务的本地地址 默认 0.0.0.0 "wsPort": true, //启动websocket服务的本地端口 默认8988 "wsPath": true, //启动websocket服务的url path 默认'/' "rbsFile": true, // 使用的rbs文件地址 必传 }) easyservice.Setup(func(svr *easyservice.EasyService) { //注册服务停止时要执行法方法 svr.BeforeStop(func(service *easyservice.EasyService) bool { fmt.Println("Vnc player server stop") return true }) cfg := svr.Config() rbsFile := svr.CmdParser().GetOpt("rbsFile", "") if len(rbsFile.String()) <= 0 || !gfile.Exists(rbsFile.String()) { svr.Help() os.Exit(0) } _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("rbsFile", rbsFile.String()) logger.SetDebug(cfg.MustGet(context.TODO(), "Debug").Bool()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("tcpHost", svr.CmdParser().GetOpt("tcpHost", "0.0.0.0").String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("tcpPort", svr.CmdParser().GetOpt("tcpPort", 8989).Int()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("proxyPassword", svr.CmdParser().GetOpt("proxyPassword", "").String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("wsHost", svr.CmdParser().GetOpt("wsHost", "0.0.0.0").String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("wsPort", svr.CmdParser().GetOpt("wsPort", 8988).Int()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("wsPath", svr.CmdParser().GetOpt("wsPath", "/").String()) if svr.SandboxNames().ContainsI("tcpserver") { svr.AddSandBox(NewTcpSandBox(cfg)) return } if svr.SandboxNames().ContainsI("wsserver") { svr.AddSandBox(NewWSSandBox(cfg)) return } svr.AddSandBox(NewTcpSandBox(cfg)) svr.AddSandBox(NewWSSandBox(cfg)) }) } ================================================ FILE: cmd/player/tcpServer.go ================================================ package main import ( "fmt" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/os/glog" "github.com/osgochina/dmicro/drpc" "github.com/osgochina/dmicro/drpc/status" "github.com/osgochina/dmicro/easyservice" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/security" "github.com/vprix/vncproxy/session" "github.com/vprix/vncproxy/vnc" "golang.org/x/net/context" "io" "net" ) // TcpSandBox Tcp的服务 type TcpSandBox struct { id int name string cfg *gcfg.Config service *easyservice.EasyService lis net.Listener closed chan struct{} } // NewTcpSandBox 创建一个默认的服务沙盒 func NewTcpSandBox(cfg *gcfg.Config) *TcpSandBox { id := easyservice.GetNextSandBoxId() sBox := &TcpSandBox{ id: id, name: fmt.Sprintf("tcp_%d", id), cfg: cfg, closed: make(chan struct{}), } return sBox } func (that *TcpSandBox) ID() int { return that.id } func (that *TcpSandBox) Name() string { return that.name } func (that *TcpSandBox) Setup() error { var err error addr := fmt.Sprintf("%s:%d", that.cfg.MustGet(context.TODO(), "tcpHost"), that.cfg.MustGet(context.TODO(), "tcpPort")) that.lis, err = net.Listen("tcp", addr) if err != nil { glog.Fatalf(context.TODO(), "Error listen. %v", err) } fmt.Printf("Tcp proxy started! listening %s . vnc server %s:%d\n", that.lis.Addr().String(), that.cfg.MustGet(context.TODO(), "vncHost"), that.cfg.MustGet(context.TODO(), "vncPort")) securityHandlers := []rfb.ISecurityHandler{&security.ServerAuthNone{}} if len(that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()) > 0 { securityHandlers = append(securityHandlers, &security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()}) } for { conn, err := that.lis.Accept() if err != nil { select { case <-that.closed: return drpc.ErrListenClosed default: } return err } go func(c net.Conn) { defer func() { //捕获错误,并且继续执行 if p := recover(); p != nil { err = fmt.Errorf("panic:%v\n%s", p, status.PanicStackTrace()) } }() svrSession := session.NewServerSession( rfb.OptSecurityHandlers(securityHandlers...), rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { return c, nil }), ) play := vnc.NewPlayer(that.cfg.MustGet(context.TODO(), "rbsFile").String(), svrSession) err = play.Start() if err != nil { glog.Warning(context.TODO(), err) return } glog.Info(context.TODO(), "play finished") }(conn) } } func (that *TcpSandBox) Shutdown() error { close(that.closed) return that.lis.Close() } func (that *TcpSandBox) Service() *easyservice.EasyService { return that.service } ================================================ FILE: cmd/player/wsServer.go ================================================ package main import ( "fmt" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/os/glog" "github.com/osgochina/dmicro/easyservice" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/security" "github.com/vprix/vncproxy/session" "github.com/vprix/vncproxy/vnc" "golang.org/x/net/context" "golang.org/x/net/websocket" "io" ) // WSSandBox Tcp的服务 type WSSandBox struct { id int name string cfg *gcfg.Config service *easyservice.EasyService svr *ghttp.Server } // NewWSSandBox 创建一个默认的服务沙盒 func NewWSSandBox(cfg *gcfg.Config) *WSSandBox { id := easyservice.GetNextSandBoxId() sBox := &WSSandBox{ id: id, name: fmt.Sprintf("ws_%d", id), cfg: cfg, } return sBox } func (that *WSSandBox) ID() int { return that.id } func (that *WSSandBox) Name() string { return that.name } func (that *WSSandBox) Setup() error { that.svr = g.Server() that.svr.BindHandler(that.cfg.MustGet(context.TODO(), "wsPath", "/").String(), func(r *ghttp.Request) { h := websocket.Handler(func(conn *websocket.Conn) { conn.PayloadType = websocket.BinaryFrame securityHandlers := []rfb.ISecurityHandler{&security.ServerAuthNone{}} if len(that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()) > 0 { securityHandlers = []rfb.ISecurityHandler{&security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()}} } svrSession := session.NewServerSession( rfb.OptSecurityHandlers(securityHandlers...), rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { return conn, nil }), ) play := vnc.NewPlayer(that.cfg.MustGet(context.TODO(), "rfbFile").String(), svrSession) err := play.Start() if err != nil { glog.Warning(context.TODO(), err) return } glog.Info(context.TODO(), "play session end") }) h.ServeHTTP(r.Response.Writer, r.Request) }) that.svr.SetAddr(fmt.Sprintf("%s:%d", that.cfg.MustGet(context.TODO(), "wsHost").String(), that.cfg.MustGet(context.TODO(), "wsPort").Int())) return that.svr.Start() } func (that *WSSandBox) Shutdown() error { return that.svr.Shutdown() } func (that *WSSandBox) Service() *easyservice.EasyService { return that.service } ================================================ FILE: cmd/proxy/main.go ================================================ package main import ( "fmt" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/text/gstr" "github.com/osgochina/dmicro/easyservice" "github.com/osgochina/dmicro/logger" "golang.org/x/net/context" "os" ) var ( helpContent = gstr.TrimLeft(` USAGE ./proxy [start|stop|quit] [tcpServer|wsServer] [OPTION] OPTION --vncHost 要连接的vnc服务端地址 必传 --vncPort 要连接的vnc服务端端口 必传 --vncPassword 要连接的vnc服务端密码 不传则使用auth none --tcpHost 本地监听的tcp协议地址 默认0.0.0.0 --tcpPort 本地监听的tcp协议端口 默认8989 --proxyPassword 连接到proxy的密码 不传入密码则使用auth none --wsHost 启动websocket服务的本地地址 默认 0.0.0.0 --wsPort 启动websocket服务的本地端口 默认8988 --wsPath 启动websocket服务的url path 默认'/' --debug 是否开启debug 默认debug=false -d,--daemon 使用守护进程模式启动 --pid 设置pid文件的地址,默认是/tmp/[server].pid -h,--help 获取帮助信息 -v,--version 获取编译版本信息 EXAMPLES /path/to/proxy /path/to/proxy start --env=dev --debug=true --pid=/tmp/server.pid /path/to/proxy start -c=config.product.toml /path/to/proxy start tcpServer,wsServer --config=config.product.toml /path/to/proxy start wsServer --vncHost=192.168.1.2 --vncPort=5901 --vncPassword=vprix --wsHost=0.0.0.0 --wsPort=8988 --wsPath=/ --proxyPassword=12345612 --debug /path/to/proxy start tcpServer --vncHost=192.168.1.2 --vncPort=5901 --vncPassword=vprix --tcpHost=0.0.0.0 --tcpPort=8989 --proxyPassword=12345612 --debug /path/to/proxy stop /path/to/proxy quit /path/to/proxy reload /path/to/proxy version /path/to/proxy help `) ) func main() { easyservice.Authors = "ClownFish" easyservice.SetHelpContent(helpContent) easyservice.SetOptions( map[string]bool{ "tcpHost": true, //本地监听的tcp协议地址 默认0.0.0.0 "tcpPort": true, //本地监听的tcp协议端口 默认8989 "proxyPassword": true, //连接到proxy的密码 不传入密码则使用auth none "wsHost": true, //启动websocket服务的本地地址 默认 0.0.0.0 "wsPort": true, //启动websocket服务的本地端口 默认8988 "wsPath": true, //启动websocket服务的url path 默认'/' "vncHost": true, // 要连接的vnc服务端地址 必传 "vncPort": true, // 要连接的vnc服务端端口 必传 "vncPassword": true, // 要连接的vnc服务端密码 不传则使用auth none }) easyservice.Setup(func(svr *easyservice.EasyService) { //注册服务停止时要执行法方法 svr.BeforeStop(func(service *easyservice.EasyService) bool { fmt.Println("Vnc proxy server stop") return true }) cfg := svr.Config() vncHost := svr.CmdParser().GetOpt("vncHost", "") if len(vncHost.String()) <= 0 { svr.Help() os.Exit(0) } vncPort := svr.CmdParser().GetOpt("vncPort", 0) if vncPort.Int() <= 0 { svr.Help() os.Exit(0) } _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncHost", vncHost.String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPort", vncPort.Int()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPassword", svr.CmdParser().GetOpt("vncPassword", "").String()) logger.SetDebug(cfg.MustGet(context.TODO(), "Debug").Bool()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("tcpHost", svr.CmdParser().GetOpt("tcpHost", "0.0.0.0").String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("tcpPort", svr.CmdParser().GetOpt("tcpPort", 8989).Int()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("proxyPassword", svr.CmdParser().GetOpt("proxyPassword", "").String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("wsHost", svr.CmdParser().GetOpt("wsHost", "0.0.0.0").String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("wsPort", svr.CmdParser().GetOpt("wsPort", 8988).Int()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("wsPath", svr.CmdParser().GetOpt("wsPath", "/").String()) if svr.SandboxNames().ContainsI("tcpserver") { svr.AddSandBox(NewTcpSandBox(cfg)) return } if svr.SandboxNames().ContainsI("wsserver") { svr.AddSandBox(NewWSSandBox(cfg)) return } svr.AddSandBox(NewTcpSandBox(cfg)) svr.AddSandBox(NewWSSandBox(cfg)) }) } ================================================ FILE: cmd/proxy/tcpServer.go ================================================ package main import ( "fmt" "github.com/gogf/gf/v2/container/gmap" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/os/glog" "github.com/osgochina/dmicro/drpc" "github.com/osgochina/dmicro/drpc/status" "github.com/osgochina/dmicro/easyservice" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/security" "github.com/vprix/vncproxy/session" "github.com/vprix/vncproxy/vnc" "golang.org/x/net/context" "io" "net" "time" ) // TcpSandBox Tcp的服务 type TcpSandBox struct { id int name string cfg *gcfg.Config service *easyservice.EasyService lis net.Listener closed chan struct{} proxyHub *gmap.StrAnyMap } // NewTcpSandBox 创建一个默认的服务沙盒 func NewTcpSandBox(cfg *gcfg.Config) *TcpSandBox { id := easyservice.GetNextSandBoxId() sBox := &TcpSandBox{ id: id, name: fmt.Sprintf("tcp_%d", id), cfg: cfg, closed: make(chan struct{}), proxyHub: gmap.NewStrAnyMap(true), } return sBox } func (that *TcpSandBox) ID() int { return that.id } func (that *TcpSandBox) Name() string { return that.name } func (that *TcpSandBox) Setup() error { var err error addr := fmt.Sprintf("%s:%d", that.cfg.MustGet(context.TODO(), "tcpHost").String(), that.cfg.MustGet(context.TODO(), "tcpPort").Int()) that.lis, err = net.Listen("tcp", addr) if err != nil { glog.Fatalf(context.TODO(), "Error listen. %v", err) } fmt.Printf("Tcp proxy started! listening %s . vnc server %s:%d\n", that.lis.Addr().String(), that.cfg.MustGet(context.TODO(), "vncHost").String(), that.cfg.MustGet(context.TODO(), "vncPort")) securityHandlers := []rfb.ISecurityHandler{ &security.ServerAuthNone{}, } if len(that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()) > 0 { securityHandlers = append(securityHandlers, &security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()}) } targetCfg := rfb.TargetConfig{ Host: that.cfg.MustGet(context.TODO(), "vncHost").String(), Port: that.cfg.MustGet(context.TODO(), "vncPort").Int(), Password: that.cfg.MustGet(context.TODO(), "vncPassword").Bytes(), } for { conn, err := that.lis.Accept() if err != nil { select { case <-that.closed: return drpc.ErrListenClosed default: } return err } go func(c net.Conn) { defer func() { //捕获错误,并且继续执行 if p := recover(); p != nil { err = fmt.Errorf("panic:%v\n%s", p, status.PanicStackTrace()) } }() svrSess := session.NewServerSession( rfb.OptDesktopName([]byte("Vprix VNC Proxy")), rfb.OptHeight(768), rfb.OptWidth(1024), rfb.OptSecurityHandlers(securityHandlers...), rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { return c, nil }), ) timeout := 10 * time.Second network := "tcp" cliSess := session.NewClient( rfb.OptSecurityHandlers([]rfb.ISecurityHandler{&security.ClientAuthVNC{Password: targetCfg.Password}}...), rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { return net.DialTimeout(network, targetCfg.Addr(), timeout) }), ) p := vnc.NewVncProxy(cliSess, svrSess) remoteKey := c.RemoteAddr().String() that.proxyHub.Set(remoteKey, p) err = p.Start() if err != nil { glog.Warning(context.TODO(), err) return } glog.Info(context.TODO(), "proxy session closed") }(conn) } } func (that *TcpSandBox) Shutdown() error { close(that.closed) return that.lis.Close() } func (that *TcpSandBox) Service() *easyservice.EasyService { return that.service } ================================================ FILE: cmd/proxy/wsServer.go ================================================ package main import ( "context" "fmt" "github.com/gogf/gf/v2/container/gmap" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/os/glog" "github.com/osgochina/dmicro/easyservice" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/security" "github.com/vprix/vncproxy/session" "github.com/vprix/vncproxy/vnc" "golang.org/x/net/websocket" "io" "net" ) // WSSandBox Tcp的服务 type WSSandBox struct { id int name string cfg *gcfg.Config service *easyservice.EasyService svr *ghttp.Server proxyHub *gmap.StrAnyMap } // NewWSSandBox 创建一个默认的服务沙盒 func NewWSSandBox(cfg *gcfg.Config) *WSSandBox { id := easyservice.GetNextSandBoxId() sBox := &WSSandBox{ id: id, name: fmt.Sprintf("ws_%d", id), cfg: cfg, proxyHub: gmap.NewStrAnyMap(true), } return sBox } func (that *WSSandBox) ID() int { return that.id } func (that *WSSandBox) Name() string { return that.name } func (that *WSSandBox) Setup() error { that.svr = g.Server() that.svr.BindHandler(that.cfg.MustGet(context.TODO(), "wsPath", "/").String(), func(r *ghttp.Request) { h := websocket.Handler(func(conn *websocket.Conn) { conn.PayloadType = websocket.BinaryFrame securityHandlers := []rfb.ISecurityHandler{ &security.ServerAuthNone{}, } if len(that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()) > 0 { securityHandlers = append(securityHandlers, &security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()}) } targetCfg := rfb.TargetConfig{ Host: that.cfg.MustGet(context.TODO(), "vncHost").String(), Port: that.cfg.MustGet(context.TODO(), "vncPort").Int(), Password: that.cfg.MustGet(context.TODO(), "vncPassword").Bytes(), } var err error svrSess := session.NewServerSession( rfb.OptDesktopName([]byte("Vprix VNC Proxy")), rfb.OptHeight(768), rfb.OptWidth(1024), rfb.OptSecurityHandlers(securityHandlers...), rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { return conn, nil }), ) cliSess := session.NewClient( rfb.OptSecurityHandlers([]rfb.ISecurityHandler{&security.ClientAuthVNC{Password: targetCfg.Password}}...), rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { return net.DialTimeout(targetCfg.GetNetwork(), targetCfg.Addr(), targetCfg.GetTimeout()) }), ) p := vnc.NewVncProxy(cliSess, svrSess) remoteKey := conn.RemoteAddr().String() that.proxyHub.Set(remoteKey, p) err = p.Start() if err != nil { glog.Warning(context.TODO(), err) return } glog.Info(context.TODO(), "proxy session end") }) h.ServeHTTP(r.Response.Writer, r.Request) }) that.svr.SetAddr(fmt.Sprintf("%s:%d", that.cfg.MustGet(context.TODO(), "wsHost").String(), that.cfg.MustGet(context.TODO(), "wsPort").Int())) return that.svr.Start() } func (that *WSSandBox) Shutdown() error { return that.svr.Shutdown() } func (that *WSSandBox) Service() *easyservice.EasyService { return that.service } ================================================ FILE: cmd/recorder/main.go ================================================ package main import ( "fmt" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/text/gstr" "github.com/osgochina/dmicro/easyservice" "github.com/osgochina/dmicro/logger" "golang.org/x/net/context" "os" ) var ( helpContent = gstr.TrimLeft(` USAGE ./recorder [start|stop|quit] [OPTION] OPTION --rbsFile 使用的rbs文件地址 必传 --vncHost 要连接的vnc服务端地址 必传 --vncPort 要连接的vnc服务端端口 必传 --vncPassword 要连接的vnc服务端密码 不传则使用auth none --debug 是否开启debug 默认debug=false -d,--daemon 使用守护进程模式启动 --pid 设置pid文件的地址,默认是/tmp/[server].pid -h,--help 获取帮助信息 -v,--version 获取编译版本信息 EXAMPLES /path/to/recorder /path/to/recorder start --env=dev --debug=true --pid=/tmp/server.pid /path/to/recorder start -c=config.product.toml /path/to/recorder start --config=config.product.toml /path/to/recorder start --rbsFile=/path/to/foo.rbs --vncHost=192.168.1.2 --vncPort=5901 --vncPassword=vprix --debug /path/to/server stop /path/to/server quit /path/to/server reload /path/to/server version /path/to/server help `) ) func main() { easyservice.Authors = "ClownFish" easyservice.SetHelpContent(helpContent) easyservice.SetOptions( map[string]bool{ "rbsFile": true, // 使用的rbs文件地址 必传 "vncHost": true, // 要连接的vnc服务端地址 必传 "vncPort": true, // 要连接的vnc服务端端口 必传 "vncPassword": true, // 要连接的vnc服务端密码 不传则使用auth none }) easyservice.Setup(func(svr *easyservice.EasyService) { //注册服务停止时要执行法方法 svr.BeforeStop(func(service *easyservice.EasyService) bool { fmt.Println("Vnc player server stop") return true }) cfg := svr.Config() rbsFile := svr.CmdParser().GetOpt("rbsFile", "") if len(rbsFile.String()) <= 0 { svr.Help() os.Exit(0) } vncHost := svr.CmdParser().GetOpt("vncHost", "") if len(vncHost.String()) <= 0 { svr.Help() os.Exit(0) } vncPort := svr.CmdParser().GetOpt("vncPort", 0) if vncPort.Int() <= 0 { svr.Help() os.Exit(0) } _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("rbsFile", rbsFile.String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncHost", vncHost.String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPort", vncPort.Int()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPassword", svr.CmdParser().GetOpt("vncPassword", "")) logger.SetDebug(cfg.MustGet(context.TODO(), "Debug").Bool()) svr.AddSandBox(NewRecorderSandBox(cfg)) }) } ================================================ FILE: cmd/recorder/recorder.go ================================================ package main import ( "fmt" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/gtime" "github.com/osgochina/dmicro/easyservice" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/encodings" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/security" "github.com/vprix/vncproxy/session" "github.com/vprix/vncproxy/vnc" "golang.org/x/net/context" "io" "net" "os" "time" ) // RecorderSandBox 记录服务 type RecorderSandBox struct { id int name string cfg *gcfg.Config service *easyservice.EasyService recorder *vnc.Recorder closed chan struct{} } // NewRecorderSandBox 创建一个默认的服务沙盒 func NewRecorderSandBox(cfg *gcfg.Config) *RecorderSandBox { id := easyservice.GetNextSandBoxId() sBox := &RecorderSandBox{ id: id, name: fmt.Sprintf("tcp_%d", id), cfg: cfg, closed: make(chan struct{}), } return sBox } func (that *RecorderSandBox) ID() int { return that.id } func (that *RecorderSandBox) Name() string { return that.name } func (that *RecorderSandBox) Setup() error { saveFilePath := that.cfg.MustGet(context.TODO(), "rbsFile").String() targetCfg := rfb.TargetConfig{ Network: "tcp", Host: that.cfg.MustGet(context.TODO(), "vncHost").String(), Port: that.cfg.MustGet(context.TODO(), "vncPort").Int(), Password: that.cfg.MustGet(context.TODO(), "vncPassword").Bytes(), Timeout: 10 * time.Second, } var securityHandlers = []rfb.ISecurityHandler{ &security.ClientAuthNone{}, } if len(targetCfg.Password) > 0 { securityHandlers = []rfb.ISecurityHandler{ &security.ClientAuthVNC{Password: targetCfg.Password}, } } // 创建会话 recorderSess := session.NewRecorder( rfb.OptEncodings(encodings.DefaultEncodings...), rfb.OptMessages(messages.DefaultServerMessages...), rfb.OptPixelFormat(rfb.PixelFormat32bit), rfb.OptGetConn(func(iSession rfb.ISession) (io.ReadWriteCloser, error) { if gfile.Exists(saveFilePath) { saveFilePath = fmt.Sprintf("%s%s%s_%d%s", gfile.Dir(saveFilePath), gfile.Separator, gfile.Name(gfile.Basename(saveFilePath)), gtime.Now().Unix(), gfile.Ext(gfile.Basename(saveFilePath)), ) } return gfile.OpenFile(saveFilePath, os.O_RDWR|os.O_CREATE, 0644) }), ) cliSession := session.NewClient( rfb.OptEncodings(encodings.DefaultEncodings...), rfb.OptMessages(messages.DefaultServerMessages...), rfb.OptPixelFormat(rfb.PixelFormat32bit), rfb.OptGetConn(func(iSession rfb.ISession) (io.ReadWriteCloser, error) { return net.DialTimeout(targetCfg.Network, targetCfg.Addr(), targetCfg.Timeout) }), rfb.OptSecurityHandlers(securityHandlers...), ) that.recorder = vnc.NewRecorder(recorderSess, cliSession) err := that.recorder.Start() if err != nil { logger.Fatal(context.TODO(), err) } return err } func (that *RecorderSandBox) Shutdown() error { close(that.closed) that.recorder.Close() return nil } func (that *RecorderSandBox) Service() *easyservice.EasyService { return that.service } ================================================ FILE: cmd/screenshot/main.go ================================================ package main import ( "bytes" "context" "fmt" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/text/gstr" "github.com/osgochina/dmicro/easyservice" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/vnc" "image/draw" "image/jpeg" "os" "time" ) var ( helpContent = gstr.TrimLeft(` USAGE ./server [start|stop|quit] [OPTION] OPTION --imageFile 要生成的截图地址,暂时只支持jpeg格式 必传 --vncHost 要连接的vnc服务端地址 必传 --vncPort 要连接的vnc服务端端口 必传 --vncPassword 要连接的vnc服务端密码 不传则使用auth none --debug 是否开启debug 默认debug=false -h,--help 获取帮助信息 -v,--version 获取编译版本信息 EXAMPLES /path/to/server /path/to/server start --env=dev --debug=true /path/to/server start -c=config.product.toml /path/to/server start --config=config.product.toml /path/to/server start --imageFile=/path/to/foo.jpeg --vncHost=192.168.1.2 --vncPort=5901 --vncPassword=vprix --debug /path/to/server version /path/to/server help `) ) func main() { easyservice.Authors = "ClownFish" easyservice.SetHelpContent(helpContent) easyservice.SetOptions( map[string]bool{ "imageFile": true, // 要生成的截图地址,暂时只支持jpeg格式 必传 "vncHost": true, // 要连接的vnc服务端地址 必传 "vncPort": true, // 要连接的vnc服务端端口 必传 "vncPassword": true, // 要连接的vnc服务端密码 不传则使用auth none }) easyservice.Setup(func(svr *easyservice.EasyService) { //注册服务停止时要执行法方法 svr.BeforeStop(func(service *easyservice.EasyService) bool { fmt.Println("Vnc player server stop") return true }) cfg := svr.Config() rbsFile := svr.CmdParser().GetOpt("imageFile", "") if len(rbsFile.String()) <= 0 { svr.Help() os.Exit(0) } vncHost := svr.CmdParser().GetOpt("vncHost", "") if len(vncHost.String()) <= 0 { svr.Help() os.Exit(0) } vncPort := svr.CmdParser().GetOpt("vncPort", 0) if vncPort.Int() <= 0 { svr.Help() os.Exit(0) } _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("rbsFile", rbsFile.String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncHost", vncHost.String()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPort", vncPort.Int()) _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPassword", svr.CmdParser().GetOpt("vncPassword", "").String()) logger.SetDebug(cfg.MustGet(context.TODO(), "Debug").Bool()) v := vnc.NewScreenshot( rfb.TargetConfig{ Network: "tcp", Host: vncHost.String(), Port: vncPort.Int(), Password: svr.CmdParser().GetOpt("vncPassword", "").Bytes(), Timeout: 5 * time.Second, }, ) img, err := v.GetImage() if err != nil { logger.Fatal(context.TODO(), err) } j := &bytes.Buffer{} err = jpeg.Encode(j, img.(draw.Image), &jpeg.Options{Quality: 100}) if err != nil { fmt.Println(err) } err = gfile.PutBytes(rbsFile.String(), j.Bytes()) if err != nil { fmt.Println(err) } os.Exit(0) }) } ================================================ FILE: cmd/video/main.go ================================================ package main func main() { // 暂未实现 //v := vnc.NewVideo(nil, // rfb.TargetConfig{ // Network: "tcp", // Host: "127.0.0.1", // Port: 5901, // Password: []byte("@abc1234"), // Timeout: 10 * time.Second, // }, //) //go func() { // err := v.Start() // if err != nil { // logger.Fatal(err) // } //}() //for { // err := <-v.Error() // logger.Error(err) //} } ================================================ FILE: docs/.keep ================================================ ================================================ FILE: docs/.nojekyll ================================================ ================================================ FILE: docs/README.md ================================================ # VncProxy [![GitHub release](https://img.shields.io/github/v/release/vprix/vncproxy.svg?style=flat-square)](https://github.com/vprix/vncproxy/releases) [![report card](https://goreportcard.com/badge/github.com/vprix/vncproxy?style=flat-square)](http://goreportcard.com/report/vprix/vncproxy) [![github issues](https://img.shields.io/github/issues/vprix/vncproxy.svg?style=flat-square)](https://github.com/vprix/vncproxy/issues?q=is%3Aopen+is%3Aissue) [![github closed issues](https://img.shields.io/github/issues-closed-raw/vprix/vncproxy.svg?style=flat-square)](https://github.com/vprix/vncproxy/issues?q=is%3Aissue+is%3Aclosed) [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/vprix/vncproxy) [![view examples](https://img.shields.io/badge/learn%20by-examples-00BCD4.svg?style=flat-square)](https://github.com/vprix/vncproxy/tree/main/examples) ## VncProxy简介 `VncProxy` 是使用`golang`实现的rfb协议解析库,支持rfb协议解析,在其上实现了很多好用的功能。 * 全协议支持的vnc proxy。 * 支持Tcp代理 * 支持Websocket代理 * 屏幕录像,保存为`RBS`文件 * 重播服务器,支持vnc客户端链接,播放`RBS`文件。 * 支持实时录制视频 * 支持通过`RBS`文件录制视频。 * 支持屏幕截图 ## 支持的编码格式 - [x] Raw - [x] CopyRect - [x] CoRRE - [x] rre - [x] Hextile - [x] Tight - [x] TightPng - [x] ZLib - [x] Zrle - [x] CursorPseudo - [x] CursorWithAlphaPseudo - [x] DesktopNamePseudo - [x] DesktopSizePseudo - [x] ExtendedDesktopSizePseudo - [x] LedStatePseudo - [x] CursorPosPseudo - [x] XCursorPseudo - [ ] jpeg - [ ] jrle - [ ] trle ## 组件说明 ### Proxy 1. 启动`server`接受`vnc viewer`的链接. 2. 启动`client`连接到指定的`vnc server`. 3. 为`vnc viewer`和`vnc server`之间建立起消息转发通道。 4. 因为`rfb`协议被完全解析,可以针对通信的消息进行转发处理,产生了后续的功能。 ### Recorder 1. 启动`client`连接到指定的`vnc server`. 2. 发送帧缓冲区更新消息`FramebufferUpdateRequest`到`vnc server`。 3. 处理`vnc server`回复的界面更新消息`FramebufferUpdate`。 4. 把这一过程以`rbs`文件格式记录下来。 ### Player 1. 启动`server`接受`vnc viewer`的链接. 2. 读取`rbs`文件,并按格式生成`FramebufferUpdate`消息发送给`vnc viewer`。 3. `vnc viewer`的界面就会回放动作。 ### Video 1. 支持`Proxy`,`Recorder`和`rbs`文件作为输入源。 2. 把`FramebufferUpdate`消息转换为视频文件。 ### Screenshot 1. 支持`Proxy`,`Recorder`和`rbs`文件作为输入源。 2. 把当前的界面视图转换为图片文件。 ## 使用说明 `vncProxy`项目有多种应用场景。 可以作为单独的应用程序编译,也可以作为库被其他应用程序引用。 接下来,分别介绍各种场景下的使用方式。 ### 编译 ```shell # 使用方式: # build.sh [-s app_name] [-v version] [-g go_bin] # app_name 需要编译的应用名称 # 选项: proxy,player,recorder,video,screenshot. # 默认是所有应用,多个应用可以逗号分割. # version 编译后的文件版本号,默认为当前git的commit id. # go_bin 使用的golang程序 # 编译所有应用 $ ./build # 编译proxy $ ./build -s proxy -v v0.1.0 # 编译player,recorder $ ./build -s player,recorder -v v0.1.0 ``` 编译后的二进制文件在`./bin/`目录 ### Proxy 代码路径在`./cmd/proxy`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 #### 获取帮助信息 ```shell # 查看帮助信息 $ ./proxy --help # 查看版本信息 $ ./proxy version ``` #### 启动tcp服务 ```shell # 启动tcp server接受vnc viewer的连接 # vncHost vnc服务器host # vncPort vnc服务器port # vncPassword vnc服务器密码 # tcpHost 本地监听的地址 # tcpPort 本地监听的端口 # proxyPassword vnc连接的密码 # debug 使用debug模式启动服务 $ ./proxy start tcpServer --vncHost=192.168.1.2 \ --vncPort=5901 \ --vncPassword=vprix \ --tcpHost=0.0.0.0 \ --tcpPort=8989 \ --proxyPassword=12345612 \ --debug ``` #### 启动WebSocket服务 ```shell # 启动ws server接受novnc的连接 # vncHost vnc服务器host # vncPort vnc服务器port # vncPassword vnc服务器密码 # wsHost 本地监听的地址 # wsPort 本地监听的端口 # wsPath websocket连接的地址 # proxyPassword vnc连接的密码 # debug 使用debug模式启动服务 $ ./proxy start wsServer --vncHost=192.168.1.2 \ --vncPort=5901 \ --vncPassword=vprix \ --wsHost=0.0.0.0 \ --wsPort=8988 \ --wsPath=/websockify \ --proxyPassword=12345612 \ --debug ``` ### Recorder 代码路径在`./cmd/recorder`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 #### 获取帮助信息 ```shell # 查看帮助信息 $ ./recorder --help # 查看版本信息 $ ./recorder version ``` #### 启动Recorder服务 ```shell # rbsFile 要保存的rbs文件路径(必填) # vncHost vnc服务器host # vncPort vnc服务器port # vncPassword vnc服务器密码 # debug 使用debug模式启动服务 $ ./recorder start --rbsFile=/path/to/foo.rbs --vncHost=192.168.1.2 --vncPort=5901 --vncPassword=vprix --debug ``` ### Player 代码路径在`./cmd/player`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 #### 获取帮助信息 ```shell # 查看帮助信息 $ ./player --help # 查看版本信息 $ ./player version ``` #### 启动Player Tcp服务 ```shell # rbsFile 要保存的rbs文件路径(必填) # tcpHost 本地监听的tcp协议地址 默认0.0.0.0 # tcpPort 本地监听的tcp协议端口 默认8989 # proxyPassword 连接到proxy的密码 不传入密码则使用auth none # debug 使用debug模式启动服务 $ ./player start tcpServer --rbsFile=/path/to/foo.rbs --tcpHost=0.0.0.0 --tcpPort=8989 --proxyPassword=12345612 --debug ``` #### 启动Player WS服务 ```shell # rbsFile 要保存的rbs文件路径(必填) # wsHost 启动websocket服务的本地地址 默认 0.0.0.0 # wsPort 启动websocket服务的本地端口 默认8988 # wsPath 启动websocket服务的url path 默认'/' # proxyPassword 连接到proxy的密码 不传入密码则使用auth none # debug 使用debug模式启动服务 $ ./player start wsServer --rbsFile=/path/to/foo.rbs --wsHost=0.0.0.0 --wsPort=8989 --wsPath=/ --proxyPassword=12345612 --debug ``` ### Screenshot 代码路径在`./cmd/screenshot`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 #### 获取帮助信息 ```shell # 查看帮助信息 $ ./screenshot --help # 查看版本信息 $ ./screenshot version ``` #### 启动Screenshot 获取vnc服务器的屏幕截图 ```shell # imageFile 要生成的截图地址,暂时只支持jpeg格式(必填) # vncHost 要连接的vnc服务端地址(必填) # vncPort 要连接的vnc服务端端口(必填) # vncPassword 要连接的vnc服务端密码,不传则使用auth none $ ./screenshot --imageFile=./screen.jpeg --vncHost=127.0.0.1 --vncPort=5900 --vncPassword=12345612 ``` ## 项目参考 本项目参考了以下项目完成。 * [vncproxy](https://github.com/amitbet/vncproxy) * [vnc2video](https://github.com/amitbet/vnc2video) * [rfbproto](https://github.com/rfbproto/rfbproto) ## 交流 我在做这个项目的过程中碰到了很多问题,查遍了互联网,缺少中文资料,大部分信息都是雷同的。 所以我萌生了开源的想法,帮助更多有需要的人。 我建立了一个可供交流的微信群,以便大家在使用的过程中碰到疑问,能有解答的地方。 当然,如果你对vnc有兴趣,也可以加我微信,多多交流。 欢迎各位贡献代码。 ![微信二维码](/docs/images/5bb8dbe702ce04b0bdde8c26583b152.jpg) ================================================ FILE: docs/changelog.md ================================================ ================================================ FILE: docs/index.html ================================================ VncProxy - RFB协议 - 使用文档
================================================ FILE: docs/overview.md ================================================ ================================================ FILE: docs/player/.keep ================================================ ================================================ FILE: docs/player/README.md ================================================ ## Player 代码路径在`./cmd/player`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 ### 获取帮助信息 ```shell # 查看帮助信息 $ ./player --help # 查看版本信息 $ ./player version ``` ### 启动Player Tcp服务 ```shell # rbsFile 要保存的rbs文件路径(必填) # tcpHost 本地监听的tcp协议地址 默认0.0.0.0 # tcpPort 本地监听的tcp协议端口 默认8989 # proxyPassword 连接到proxy的密码 不传入密码则使用auth none # debug 使用debug模式启动服务 $ ./player start tcpServer --rbsFile=/path/to/foo.rbs --tcpHost=0.0.0.0 --tcpPort=8989 --proxyPassword=12345612 --debug ``` ### 启动Player WS服务 ```shell # rbsFile 要保存的rbs文件路径(必填) # wsHost 启动websocket服务的本地地址 默认 0.0.0.0 # wsPort 启动websocket服务的本地端口 默认8988 # wsPath 启动websocket服务的url path 默认'/' # proxyPassword 连接到proxy的密码 不传入密码则使用auth none # debug 使用debug模式启动服务 $ ./player start wsServer --rbsFile=/path/to/foo.rbs --wsHost=0.0.0.0 --wsPort=8989 --wsPath=/ --proxyPassword=12345612 --debug ``` ================================================ FILE: docs/proxy/.keep ================================================ ================================================ FILE: docs/proxy/README.md ================================================ ## Proxy 代码路径在`./cmd/proxy`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 ### 获取帮助信息 ```shell # 查看帮助信息 $ ./proxy --help # 查看版本信息 $ ./proxy version ``` ### 启动tcp服务 ```shell # 启动tcp server接受vnc viewer的连接 # vncHost vnc服务器host # vncPort vnc服务器port # vncPassword vnc服务器密码 # tcpHost 本地监听的地址 # tcpPort 本地监听的端口 # proxyPassword vnc连接的密码 # debug 使用debug模式启动服务 $ ./proxy start tcpServer --vncHost=192.168.1.2 \ --vncPort=5901 \ --vncPassword=vprix \ --tcpHost=0.0.0.0 \ --tcpPort=8989 \ --proxyPassword=12345612 \ --debug ``` ### 启动WebSocket服务 ```shell # 启动ws server接受novnc的连接 # vncHost vnc服务器host # vncPort vnc服务器port # vncPassword vnc服务器密码 # wsHost 本地监听的地址 # wsPort 本地监听的端口 # wsPath websocket连接的地址 # proxyPassword vnc连接的密码 # debug 使用debug模式启动服务 $ ./proxy start wsServer --vncHost=192.168.1.2 \ --vncPort=5901 \ --vncPassword=vprix \ --wsHost=0.0.0.0 \ --wsPort=8988 \ --wsPath=/websockify \ --proxyPassword=12345612 \ --debug ``` ================================================ FILE: docs/questions.md ================================================ ================================================ FILE: docs/recorder/.keep ================================================ ================================================ FILE: docs/recorder/README.md ================================================ ## Recorder 代码路径在`./cmd/recorder`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 ### 获取帮助信息 ```shell # 查看帮助信息 $ ./recorder --help # 查看版本信息 $ ./recorder version ``` ### 启动Recorder服务 ```shell # rbsFile 要保存的rbs文件路径(必填) # vncHost vnc服务器host # vncPort vnc服务器port # vncPassword vnc服务器密码 # debug 使用debug模式启动服务 $ ./recorder start --rbsFile=/path/to/foo.rbs --vncHost=192.168.1.2 --vncPort=5901 --vncPassword=vprix --debug ``` ================================================ FILE: docs/rfc6143/GLOSSORY.md ================================================ # 帧缓冲 帧缓冲器也称为帧缓冲或者显存,是用来存储渲染数据的地方。帧缓冲的每一个存储单位对应一个像素,它是屏幕显示画面的直接映象,又称为位映射图(Bit Map)。 ![帧缓冲器](../images/rfc6143-glassory-1.png) # 调色板 传统的帧缓冲器支持的色彩模式很广泛。受限于昂贵的内存,大多数早期的帧缓冲器使用的是1位、2位、4位或 8位的色深。小的色深导致不能产生完整的色彩范围。其解决方法是为帧缓冲器增加一个查找表(lookup table,LUT),把帧缓冲器中存储的每个“颜色”作为一个索引。这就是所谓的索引色(indexed color)模式。 ![调色板](../images/rfc6143-glassory-2.png) # 游程编码 游程编码(run-length encoding,RLE)是一种比较简单的压缩算法,其基本思想是将重复且连续出现多次的字符使用(连续出现次数,某个字符)来描述。 举例来说,字符串"AAAABBBCCDEEEE",由4个A、3个B、2个C、1个D、4个E组成,游程编码将其压缩为4A3B2C1D4E,由14个字符转成10个字符,压缩比 71.4%。 游程编码的优点是将重复性高的数据压缩成小单位;若数据出现频率不高,压缩结果可能比原始数据大。例如:"ABCDE",压缩结果为"1A1B1C1D1E",由5个字符转成10个字符,压缩比 200%。 ![游程编码](../images/rfc6143-glassory-3.png) ================================================ FILE: docs/rfc6143/README.md ================================================ # 远程帧缓冲协议 该文档原著为:https://github.com/vincenthcui/rfc6143 ## RFB 协议 RFB (Remote Framebuffer Protocol) 远程帧缓冲协议,是一种允许用户通过网络连接控制远端计算机的七层网络协议。 在 RFB 协议中,用户通过本地鼠标、键盘输入,经由远端计算机计算后,将图形用户界面(GUI)回传本地进行输出。 ![RFB 协议通信](../images/rfc6143-readme-1.png) ### 协议特点 协议设计有以下几个特点: - 瘦客户端。客户端职责简单清晰,无状态 - 运行在弱网络环境下 - 跨操作系统兼容性 ## 协议版本 RFB 协议有三个公开版本,分别是 3.3、3.7和3.8,3.8 是稳定版本。 | 版本 | 发布时间 | 协议差异 | |:-------------------:|:----------:|:---------:| | Version 3.3 | 1998-01 | 服务器单向认证 | | Version 3.7 | 2003-8-12 | 关闭连接时返回原因 | | Version 3.8 (Final) | 2010-11-26 | - | 三个版本只在协议的握手阶段和初始化阶段存在差异,在数据流交换阶段保持一致。 ### 协议的拓展 第三方 VNC 服务端和客户端拓展了 3.8 版本协议,提供更多的认证方式,优化传输效率。 - Tight - RealVNC - Ultra - VMWare ## RFB 的发展历史 - 2002 年,AT&T 关闭其位于英国剑桥的 Olivetti 研究实验室。 - 2002 年,VNC 技术发明者 Tristan Richardson 合伙成立 RealVNC 公司,向商业公司提供企业级远程访问软件。 - 2003年8月12日,Richardson 公开 RFB 协议的 3.7 版本。 - 2010年11月26日,发布稳定协议版本 v3.8。 > RFB 是 IETF 公开的开源通信协议 > > RFB® 和 VNC® 是 RealVNC 公司的注册商标。 ## 公开协议版本及资料 - [RFC 6143: The Remote Framebuffer Protocol (describes Version 3.8)](https://tools.ietf.org/html/rfc6143) - [The RFB Protocol - Community Version](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst) - [The RFB Protocol - Version 3.8 (2010-11-26)](https://web.archive.org/web/20160410055332/http://www.realvnc.com/docs/rfbproto.pdf) - [The RFB Protocol - Version 3.7 (2003-08-12)](https://web.archive.org/web/20040325204925/http://www.realvnc.com/docs/rfbproto.pdf) ================================================ FILE: docs/rfc6143/handshake/README.md ================================================ # 握手 RFB 协议有四个阶段: ```mermaid graph LR protocol["协议握手"] auth["认证"] initial["初始化"] transfer("数据交互") subgraph 握手 protocol --> auth auth --> initial end initial --> transfer transfer --> transfer ``` - [协议握手](/rfc6143/handshake/protocol-version.md):对协议版本达成共识 - [认证](/rfc6143/handshake/security-type.md):认证客户端身份 - [初始化](/rfc6143/handshake/initial.md):交换像素格式等背景数据 - [数据交互](/rfc6143/transfer/README.md):传输交互事件,更新图像帧 ================================================ FILE: docs/rfc6143/handshake/initial.md ================================================ # 初始化 收 SecurityResult 后,客户端应当发送 [ClientInit](#客户端初始化) 数据包,收到后,服务端发送 [ServerInit](#服务端初始化) 包。 ```mermaid sequenceDiagram participant Client participant Server Server->>Client: SecurityResult(success) Client->>Server: ClientInit Server->>Client: ServerInit ``` ## 客户端初始化 客户端初始化需要声明是否的共享屏幕。 ``` +--------------+--------------+-------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+-------------+ | 1 | U8 | shared-flag | +--------------+--------------+-------------+ ``` - shared-flag: 是否与其他客户端共享连接。如果是 1,允许服务端保持/加入其他客户端的连接。如果是0,服务端应该主动断开与其他客户端的连接。 ## 服务端初始化 收到 ClientInit 消息后,服务端发送 ServerInit 消息,声明帧缓冲区大小、像素格式以及桌面的名称。 ``` +--------------+--------------+------------------------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+------------------------------+ | 2 | U16 | framebuffer-width in pixels | | 2 | U16 | framebuffer-height in pixels | | 16 | PIXEL_FORMAT | server-pixel-format | | 4 | U32 | name-length | | name-length | U8 array | name-string | +--------------+--------------+------------------------------+ ``` - framebuffer-width in pixels: 屏幕宽度 - framebuffer-height in pixels: 屏幕高度 - server-pixel-format: 服务器默认像素格式 - name-length/name-string: 桌面的名字 如果客户端无法响应服务端指定的 pixel-format,可以主动发起 [SetPixelFormat](/display/pixel-format.md#SetPixelFormat) 重新设置。 ================================================ FILE: docs/rfc6143/handshake/protocol-version.md ================================================ # 协议握手 协议握手对客户端和服务端通信过程使用的协议版本达成共识,有可能的情况下,对不同协议版本实现向前兼容。 ## 握手过程 建立 TCP 连接后,服务器首先向客户端发送版本 X,收到 X 后,客户端向服务器发送不高于 X 的版本 Y。 RFB 有三个公开可选版本 3.3/3.7/3.8。 部分客户端或浏览器变种可能会发送其他的协议版本,统一将非标协议认定为 3.3(协议认为未公开协议版本没有实现3.7/3.8 中引入的特殊握手流程)。 ```mermaid sequenceDiagram participant Client participant Server Server->>Client: ProtocolVersion Client->>Server: ProtocolVersion ``` ## 协议报文 协议消息由标识符、主次版本组成,其结构体如下: ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 3 | U8 array | protocol | | 1 | U8 [32] | blank | | 3 | U8 array | major version| | 1 | U8 [42] | pot | | 3 | U8 array | minor version| +--------------+--------------+--------------+ ``` 对于 3.8 版本协议,其发送协议头部如下: ``` RFB 003.008\n (hex 52 46 42 20 30 30 33 2e 30 30 38 0a) ``` ================================================ FILE: docs/rfc6143/handshake/security-type.md ================================================ # 安全握手 协商好协议版本后,客户端和服务端进行安全握手,就认证方式达成共识,并完成认证过程。 ## 握手过程 认证的流程图如下: ```mermaid sequenceDiagram participant Client participant Server alt incompatible Server->>Client: SecurityTypes(Empty) Server->>Client: FailReason Server-XClient: Close end Server->>Client: SecurityTypes Client->>Server: SecurityType alt None else VNC Auth Server->>Client: Challenge Client->>Server: ChallengeResponse end Server->>Client: SecurityResult alt failed Server->>Client: FailReason Server-XClient: Close end ``` - 服务器向客户端列举支持的加密方式,客户端挑选支持的认证方式,告知服务端。 - 根据认证方式,完成认证 - 服务端返回认证结果,完成安全握手 ## 协议报文 ### security-types ``` +--------------------------+-------------+--------------------------+ | No. of bytes | Type | Description | | | [Value] | | +--------------------------+-------------+--------------------------+ | 1 | U8 | number-of-security-types | | number-of-security-types | U8 array | security-types | +--------------------------+-------------+--------------------------+ ``` - number-of-security-types: 认证方式数量 - security-types: 认证方式标识符 协议定义的标识符有三种,剩下的由协议厂家进行拓展: ``` +--------+--------------------+ | Number | Name | +--------+--------------------+ | 0 | Invalid | | 1 | None | | 2 | VNC Authentication | +--------+--------------------+ ``` ### security-type 客户端以单字节报文告知选择的认证方式 ``` +--------------+--------------+---------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+---------------+ | 1 | U8 | security-type | +--------------+--------------+---------------+ ``` - security-type: 达成共识的协议标识符 ### fail-reason 当错误发送时,例如,服务端不兼容客户端版本,服务端会发送空的 SecurityTypes,发送消息说明错误原因后,关闭连接。 FailReason 报文如下: ``` +---------------+--------------+---------------+ | No. of bytes | Type [Value] | Description | +---------------+--------------+---------------+ | 4 | U32 | reason-length | | reason-length | U8 array | reason-string | +---------------+--------------+---------------+ ``` - reason-length: 原因长度 - reason-string: 错误原因 ### VNC Auth VNC Auth 过程,服务器首先发送 16 字节的随机字符串,作为 `challenge`。客户端用密码通过 `DES` 算法对 `challenge` 进行加密,将加密后的 16 字节结果告知服务端。 > TODO: 加密算法详解 有几点需要注意: - 为格式化密钥,密码会被删节/补齐为 8 字符 - VNC Auth 是弱加密,不能用于不可信网络 - 为了更安全访问,可以通过 IPsec/SSH 加密链路 #### Challenge ``` +--------------+--------------+-------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+-------------+ | 16 | U8 | challenge | +--------------+--------------+-------------+ ``` #### ChallengeResponse ``` +--------------+--------------+-------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+-------------+ | 16 | U8 | response | +--------------+--------------+-------------+ ``` ### SecurityResult 客户端选择认证方式后,服务端返回 SecurityResult 告知认证结果,哪怕使用 `None` 认证。 如果认证失败,服务器发送 `failed` 和 fail-reason 报文,主动关闭连接。 ``` +--------------+--------------+-------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+-------------+ | 4 | U32 | status: | | | 0 | OK | | | 1 | failed | +--------------+--------------+-------------+ ``` ================================================ FILE: docs/rfc6143/transfer/README.md ================================================ # 数据交互 数据交互阶段,客户端向服务端发送鼠标、键盘事件,或请求更新图像。 ```mermaid graph LR A("鼠标事件") B("键盘事件") C("更新图像") ``` ================================================ FILE: docs/rfc6143/transfer/display.md ================================================ # 显示协议 客户端可能处于弱网络环境,或只有较低性能的渲染设备。如果服务端不加限制的向客户端发送像素画面,很容易造成客户端卡死或网络堵塞。 在 RFB 协议中,当且仅当客户端主动请求显示数据时,服务端才会将 [FramebufferUpdate](#FramebufferUpdate) 发往客户端。响应 [FramebufferUpdateRequest](#FramebufferUpdateRequest) 往往需要返回多条 FramebufferUpdate。 ```mermaid sequenceDiagram participant Client participant Server Client->>Server: FramebufferUpdateRequest loop no change Client->>Server: FramebufferUpdateRequest end Server->>Client: FramebufferUpdate Server->>Client: FramebufferUpdate Server->>Client: FramebufferUpdate ``` ## FramebufferUpdateRequest FramebufferUpdateRequest 告知服务端,客户端希望得到指定区域的内容。 ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 1 | U8 [3] | message-type | | 1 | U8 | incremental | | 2 | U16 | x-position | | 2 | U16 | y-position | | 2 | U16 | width | | 2 | U16 | height | +--------------+--------------+--------------+ ``` - message-type: 消息类型,固定 `3` - incremental: 是否是增量请求。 - x-position/y-position: 区域的起始坐标 - width/height: 区域的长度和宽度 incremental 通常为非 0 值,服务器只需要发有变化的图像信息。当客户端丢失了缓存的帧缓冲信息,或者刚建立连接,需要完整的图像信息时,将 incremental 置为 0,获取全量信息。 ## FramebufferUpdate FramebufferUpdate 由一组矩形图像(rectangles of pixel)组成,客户端收到 FramebufferUpdate 消息后,将消息内的矩形填充到帧缓冲对应区域,完成图像展示。 ``` +--------------+--------------+----------------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+----------------------+ | 1 | U8 [0] | message-type | | 1 | | padding | | 2 | U16 | number-of-rectangles | +--------------+--------------+----------------------+ ``` - message-type: 消息类型,固定 0 - number-of-rectangles: 矩形的数量 ### FramebufferUpdateRectangle FramebufferUpdate 携带 `number-of-rectangles` 数量的矩形信息,每个矩形都有头部信息 ``` +--------------+--------------+---------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+---------------+ | 2 | U16 | x-position | | 2 | U16 | y-position | | 2 | U16 | width | | 2 | U16 | height | | 4 | S32 | encoding-type | +--------------+--------------+---------------+ ``` - x-position/y-position: 矩形起始坐标 - width/height: 矩形宽度和高度 - encoding-type: 编码类型 ================================================ FILE: docs/rfc6143/transfer/encoding/README.md ================================================ # 编码 ”编码“ 是像素数据的表达方式。在发往客户端前,服务器经常将发送的图形数据进行编码、压缩,提高图像传输效率。 ``` +--------+-----------------------------+ | Number | Name | +--------+-----------------------------+ | 0 | Raw | | 1 | CopyRect | | 2 | RRE | | 5 | Hextile | | 15 | TRLE | | 16 | ZRLE | | -239 | Cursor pseudo-encoding | | -223 | DesktopSize pseudo-encoding | +--------+-----------------------------+ ``` - [Raw](/rfc6143/transfer/encoding/raw.md): 原始位图编码,即不编码 - [CopyRect](/rfc6143/transfer/encoding/copy-rect.md): 从帧缓冲复制 - [RRE](/rfc6143/transfer/encoding/rise-and-run-length.md): rise-and-run-length 二维游程编码 - Hextile: RRE 的变种,图块游程编码 - [TRLE](/rfc6143/transfer/encoding/tiled-run-length.md): 图块游程编码 - [ZRLE](/rfc6143/transfer/encoding/zlib-run-length.md): Zlib Run-Length Encoding,zlib 压缩的游程编码 - Cursor pseudo-encoding: 鼠标指针伪编码 - DesktopSize pseudo-encoding: 桌面分辨率伪编码 ================================================ FILE: docs/rfc6143/transfer/encoding/copy-rect.md ================================================ # 镜像编码 Copy Rect CopyRect 指示客户端,从已有帧缓冲区域复制到新区域。这种编码常用于窗口拖动、页面滚动等场景。 报文只说明起始坐标,区域的长度和宽度由 [FramebufferUpdateRectangle](/rfc6143/transfer/display.md#FramebufferUpdateRectangle) 指定。 ``` +--------------+--------------+----------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+----------------+ | 2 | U16 | src-x-position | | 2 | U16 | src-y-position | +--------------+--------------+----------------+ ``` - src-x-position/src-y-position: 源图像的起点坐标 ================================================ FILE: docs/rfc6143/transfer/encoding/raw.md ================================================ # Raw 原始编码 Raw 是最简单也是最原始的编码,直接向客户端传递位图信息,不进行编码优化。 在 Raw 格式中,像素按从左到右,从上到下的顺序排列成一维数组。 > 协议要求客户端必须支持 Raw 类型编码。 > > 协议要求服务器必须传输 Raw 类型编码,除非客户端另有要求。 ``` +----------------------------+--------------+-------------+ | No. of bytes | Type [Value] | Description | +----------------------------+--------------+-------------+ | width*height*bytesPerPixel | PIXEL array | pixels | +----------------------------+--------------+-------------+ ``` - pixels: 像素数组 ================================================ FILE: docs/rfc6143/transfer/encoding/rise-and-run-length.md ================================================ # 上升和游程编码 Rise-and-run-length RRE 是游程编码的二维变种。基本思想是将大的矩形拆分为子矩形,每个子矩形有单值的像素组成,所有小矩形的并集构成原始矩形区域。 编码由背景像素值 Vb 、计数 N ,以及 N 个子矩形列表组成。子矩形由元组 表示,其中 v 是像素值(v != Vb),x/y/w/h 表示子矩形相对主矩形的坐标,和大小。 绘制时,客户端先以背景像素值填充矩形,再绘制每个子矩形,叠加出原始图像。 ``` +---------------+--------------+-------------------------+ | No. of bytes | Type [Value] | Description | +---------------+--------------+-------------------------+ | 4 | U32 | number-of-subrectangles | | bytesPerPixel | PIXEL | background-pixel-value | +---------------+--------------+-------------------------+ ``` - number-of-subrectangles: 子矩形数量 - background-pixel-value: 矩形背景色 对于子矩形 ``` +---------------+--------------+---------------------+ | No. of bytes | Type [Value] | Description | +---------------+--------------+---------------------+ | bytesPerPixel | PIXEL | subrect-pixel-value | | 2 | U16 | x-position | | 2 | U16 | y-position | | 2 | U16 | width | | 2 | U16 | height | +---------------+--------------+---------------------+ ``` - subrect-pixel-value: 子矩形色值 - x-position/y-position: 与背景矩行的**相对位置** - width/height: 子矩形宽度和高度 ================================================ FILE: docs/rfc6143/transfer/encoding/set-encoding.md ================================================ # 设置编码 客户端用 SetEncoding 消息告知服务端,接受哪些像素[编码](/rfc6143/transfer/encoding/README.md)。 除了用于解析像素的编码外,客户端可以发送伪编码,向服务端请求拓展功能。如果服务端不识别此编码,可以直接忽略。客户端在未收到服务端明确的”支持“回复前,应当默认服务端不支持伪编码。 数据结构如下: ``` +-----------------------+--------------+---------------------+ | No. of bytes | Type [Value] | Description | +-----------------------+--------------+---------------------+ | 1 | U8 [2] | message-type | | 1 | | padding | | 2 | U16 | number-of-encodings | | 4*number-of-encodings | S32 array | encoding-types | +-----------------------+--------------+---------------------+ ``` - message-type: 消息类型,固定为 `2` - number-of-encodings: 编码数量 - encoding-types: [编码标识符](/rfc6143/transfer/encoding/README.md) ================================================ FILE: docs/rfc6143/transfer/encoding/tight-png.md ================================================ # 严格Png编码 Tight Encoding 严格编码为像素数据提供了有效的压缩。为了减少执行的复杂性,任何Tight编码的矩形的宽度不能超过2048像素。 如果需要一个更宽的矩形,必须将其分成几个矩形,每个矩形都要单独编码。 每个Tight-encoded矩形的第一个字节是一个压缩控制字节。 ``` +--------------+--------------+----------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+----------------+ | 1 | U8 | 压缩控制字节 | +--------------+--------------+----------------+ ``` 压缩控制字节中最低有效位四位标识了客户端在解码矩形之前应该重置哪些zlib压缩流。 每个位都是独立的,对应于一个应该被重置的单独的zlib流。 ``` +-----+----------------+ | Bit | Description | +-----+----------------+ | 0 | Reset stream 0 | | 1 | Reset stream 1 | | 2 | Reset stream 2 | | 3 | Reset stream 3 | +-----+----------------+ ``` 在Tight编码中,支持三种可能的压缩方法之一。它们是`BasicCompression`, `FillCompression`和`JpegCompression`。 如果`压缩控制字节`的第7位(最高位)是0,那么压缩类型是BasicCompression。 在这种情况下,压缩控制的第7-4位(最重要的4位)应该被解释为如下: ``` +--------------+--------------+----------------+ | Bits | Binary value | Description | +--------------+--------------+----------------+ | 5-4 | 00 | Use stream 0 | | | 01 | Use stream 1 | | | 10 | Use stream 2 | | | 11 | Use stream 3 | | 6 | 0 | --- | | | 1 | read-filter-id | | 7 | 0 | BasicCompression| +--------------+--------------+----------------+ ``` 否则,如果`压缩控制字节`的第7位设置为1,则压缩方法是`FillCompression`或`JpegCompression`,取决于同一字节的其他位: ``` +--------------+--------------+----------------+ | Bits | Binary value | Description | +--------------+--------------+----------------+ | 7-4 | 1000 | FillCompression| | | 1001 | JpegCompression| | | any other | Invalid | +--------------+--------------+----------------+ ``` > 注意:`JpegCompression` 只能在像素编码为 16 或 32 并且客户端已使用 ` JPEG Quality Level Pseudo-encoding`。 Tight 编码使用一种新型 `TPIXEL`(Tight 像素)。 这与商定的像素格式的 PIXEL 相同,除了真彩色标志非零、每像素位数为 32、深度为 24 以及构成红色、绿色和蓝色强度的所有位正好是 8 位宽。 在这种情况下,`TPIXEL` 只有 3 个字节长,其中第一个字节是红色分量,第二个字节是绿色分量,第三个字节是像素颜色值的蓝色分量。 > 压缩控制字节之后的数据取决于压缩方法。 - FillCompression 如果压缩类型为 `FillCompression`,则后面是唯一的像素值,采用 `TPIXEL` 格式。此值适用于矩形的所有像素。 - JpegCompression 如果压缩类型为 `JpegCompression`,则以下数据流如下所示: ``` +--------------+--------------+----------------+ | No. of bytes | Type | Description | +--------------+-----------+-------------------+ | 1-3 | | jpeg数据的长度 | | length | U8 array | jpeg-data | +--------------+-----------+-------------------+ ``` 根据以下方案,长度动态压缩表示为一个、两个或三个字节: ``` +----------------------------+---------------------------+ | Value | Description | +----------------------------+---------------------------+ | 0xxxxxxx | for values 0..127 | | 1xxxxxxx 0yyyyyyy | for values 128..16383 | | 1xxxxxxx 1yyyyyyy zzzzzzzz | for values 16384..4194303 | +----------------------------+---------------------------+ ``` 这里每个字符表示一位,`xxxxxxx` 是值的最低 7 位(位 0-6),`yyyyyyy` 是 7-13 位,`zzzzzzzz` 是最高有效 8 位(14-21 位)。例如,十进制值 10000 应表示为两个字节:二进制 10010000 01001110,或十六进制 90 4E。 > jpeg-data 是 JFIF 流 - BasicCompression 如果压缩类型是 `BasicCompression` 并且压缩控制字节的第 6 位(read-filter-id 位)设置为 1, 则下一个(第二个)字节指定 `filter-id`,它告诉解码器使用什么过滤器类型编码器在压缩之前对像素数据进行预处理。 `filter-id` 字节可以是以下之一: ``` +--------------+-------+-----------+-------------------------+ | No. of bytes | Type | [Value] | Description | +--------------+-------+-----------+-------------------------+ | 1 | U8 | | filter-id | | | | 0 | CopyFilter (no filter) | | | | 1 | PaletteFilter | | | | 2 | GradientFilter | +--------------+-------+-----------+-------------------------+ ``` 如果压缩控制字节的第 6 位设置为 0(无过滤器 ID 字节),则使用 `CopyFilter`。 - `CopyFilter` 当 `CopyFilter` 处于活动状态时,将压缩 `TPIXEL` 格式的原始像素值。有关压缩的详细信息,请参见下文。 - `PaletteFilter` `PaletteFilter` 将真彩色像素数据转换为索引颜色和可由 2..256 种颜色组成的调色板。 如果颜色数为 2,则每个像素用 1 位编码,否则 8 位用于编码一个像素。 1位编码的执行方式是最高有效位对应于最左边的像素,并且每行像素都与字节边界对齐。 使用 `PaletteFilter` 时,调色板在像素数据之前发送。调色板以一个无符号字节开始,其值是调色板中的颜色数减 1(即 1 表示 2 种颜色,255 表示调色板中的 256 种颜色)。 然后是调色板本身,它由 `TPIXEL` 格式的像素值组成。 - `GradientFilter` GradientFilter 使用一种简单的算法对像素数据进行预处理,该算法将每个颜色分量转换为“预测”强度和实际强度之间的差异。这种技术不会影响未压缩的数据大小,但有助于更好地压缩类似照片的图像。将强度转换为差异的伪代码如下: ``` P[i,j] := V[i-1,j] + V[i,j-1] - V[i-1,j-1]; if (P[i,j] < 0) then P[i,j] := 0; if (P[i,j] > MAX) then P[i,j] := MAX; D[i,j] := V[i,j] - P[i,j]; ``` 这里 `V[i,j]` 是坐标 `(i,j)` 处像素的颜色分量的强度。对于当前矩形之外的像素,假设 `V[i,j]` 为零(这与 `P[i,0]` 和 `P[0,j]` 相关)。 MAX 是颜色分量的最大强度值。 > 注意:`GradientFilter` 只能在 bits-per-pixel 为 16 或 32 时使用。 使用上述三个过滤器之一过滤像素数据后,使用 `zlib` 库对其进行压缩。 但是,如果在应用过滤器之后但在压缩之前的数据大小小于 12,则数据按原样发送,未压缩。 可以使用四个单独的 zlib 流(0..3),解码器应该从压缩控制字节中读取实际的流 id。 如果不使用压缩,则按原样发送像素数据,否则数据流如下所示: ``` +--------------+--------------+----------------+ | No. of bytes | Type | Description | +--------------+-----------+-------------------+ | 1-3 | | zlib数据的长度 | | length | U8 array | zlibData | +--------------+-----------+-------------------+ ``` 长度紧凑地表示为一个、两个或三个字节,就像在 JpegCompression 方法中一样(见上文)。 如果压缩控制字节中的某些位 0、1、2 和 3 设置为 1,则解码器必须在解码矩形之前重置 `zlib` 流。请注意,即使压缩类型为解码器,解码器也必须重置指示的 zlib 流是 `FillCompression` 或 `JpegCompression`。 ================================================ FILE: docs/rfc6143/transfer/encoding/tiled-run-length.md ================================================ # 图块游程编码 TRLE TRLE(Tiled Run-Length Encoding),使用图块压缩技术、调色板技术以及游程编码,对传输的桌面图像进行压缩。 在图块压缩技术中,传输图像被分为 N 个 16x16 的小图块,图块按从左到右,从上到下的顺序排列成一位数组。如果传输图像的尺寸不是 16 的整数倍,最后一列/最后一行图块的宽度/高度应为实际宽度/实际高度(小于16)。 TRLE 使用 CPIXEL(compressed pixel) 表示像素。CPIXEL 同样由 [PIXEL_FORMAT](/rfc6143/transfer/pixel-format.md) 定义。 在特殊情况下,CPIXEL 使用更紧凑的结构表示像素。 假如 PIXEL_FORMAT 中 true-color-flag 是非零值,bits-per-pixel 是 32,depth 是 24 或更小,只会用低 3 bit 或者高 3 bit 来表达红、绿、蓝。 ## 子编码 图块使用头部单字节表示图块的子编码类型。 子编码类型的最高比特表示当前图块是否使用游程编码。 低 7-bit 指示调色板的大小。 ``` +---------------+--------------+------------------+ | No. of bits | Type [Value] | Description | +------------------------------+------------------+ | 1 | bit | run-length-flag | | 2-8 | bit | palette-length | +------------------------------+------------------+ ``` TRLE 对自编码的值有单独定义 | 十六进制 | 十进制 | 含义 | |-------------|-----------|-------------------------------| | 0x00 | 0 | [Raw 模式](#raw-模式),不使用游程和调色板 | | 0x01 | 1 | [纯色模式](#纯色模式),图块使用同一种颜色 | | 0x02 - 0x10 | 2 - 16 | [打包像素调色板模式](#打包像素调色板模式) | | 0x11 - 0x7E | 17 - 126 | 保留 | | 0x7F | 127 | [打包像素调色板模式](#打包像素调色板模式),复用调色板 | | 0x80 | 128 | [RLE 模式](#rle-模式) | | 0x81 | 129 | [调色板游程编码](#调色板游程编码),复用调色板 | | 0x82 - 0xFF | 130 - 255 | [调色板游程编码](#调色板游程编码) | ### Raw 模式 Raw 模式直接传输像素值,像素在图块中按从左到右、从上到下排列。不使用游程编码,不使用调色板。 ``` +-----------------------------+----------------+--------------+ | No. of bytes | Type [Value] | Description | +-----------------------------+----------------+--------------+ | 1 | SubEncoding[0] | sub-encoding | | width*height*BytesPerCPixel | CPIXEL array | pixels | +-----------------------------+----------------+--------------+ ``` ### 纯色模式 图块使用同一种颜色。 ``` +----------------+----------------+--------------+ | No. of bytes | Type [Value] | Description | +----------------+----------------+--------------+ | 1 | SubEncoding[1] | sub-encoding | | bytesPerCPixel | CPIXEL | pixelValue | +----------------+----------------+--------------+ ``` ### 打包像素调色板模式 打包像素调色板模式。使用调色板定义颜色,使用颜色在调色板中的偏移量表达颜色。由于调色板较小,可以将多个像素的色值打包到一个字节中存储,进一步压缩体积。 每个像素使用的 bit 数由调色板大小确定,N=log2 M。 如果像素的数量不是 2/4/8 的倍数,需要添加填充位,对齐字节。 | 调色板大小 | 像素比特数 | 打包后字节数 | |:-----:|:-----:|-----------------------------| | =2 | 1 | m = (width+7) // 8 *height | | <=4 | 2 | m = (width+3) // 4 * height | | <=16 | 4 | m = (width+1) // 2 * height | ``` +----------------------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +----------------------------+--------------+--------------+ | 1 | SubEncoding | sub-encoding | | paletteSize*bytesPerCPixel | CPIXEL array | palette | | m | U8 array | packedPixels | +----------------------------+--------------+--------------+ ``` ### RLE 模式 RLE 模式,不使用调色板,使用游程编码。在计算游程时,允许跨行计算。 长度由一个或多个字节表示,到第一个不是 255(0xFF)停止。 ``` +-------------------------+------------------+-----------------------+ | No. of bytes | Type [Value] | Description | +-------------------------+------------------+-----------------------+ | 1 | SubEncoding[128] | sub-encoding | | bytesPerCPixel | CPIXEL | pixelValue | | div(runLength - 1, 255) | U8 array | 255 | | 1 | U8 | (runLength-1) mod 255 | +-------------------------+------------------+-----------------------+ ``` 例如: | 游程长度 | 字节表示 | |:----:|:--------:| | 1 | 0x00 | | 255 | 0xFE | | 256 | 0xFF00 | | 257 | 0xFF01 | | 510 | 0xFFFE | | 511 | 0xFFFF00 | ### 调色板游程编码 调色板游程编码。使用调色板,游程部分类似[RLE 模式](#rle-模式),将像素放入数组后,使用像素加游程的方式表示整个色块,像素用调色板偏移量表示。 ``` +----------------------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +----------------------------+--------------+--------------+ | 1 | SubEncoding | sub-encoding | | paletteSize*bytesPerCPixel | CPIXEL array | palette | +----------------------------+--------------+--------------+ ``` - 长度为1的游程,仅由调色板索引表示 ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 1 | U8 | paletteIndex | +--------------+--------------+--------------+ ``` - 长度大于1的游程,由调色板索引+128和游程长度表示 ``` +-------------------------+--------------+-----------------------+ | No. of bytes | Type [Value] | Description | +-------------------------+--------------+-----------------------+ | 1 | U8 | paletteIndex + 128 | | div(runLength - 1, 255) | U8 array | 255 | | 1 | U8 | (runLength-1) mod 255 | +-------------------------+--------------+-----------------------+ ``` ================================================ FILE: docs/rfc6143/transfer/encoding/zlib-run-length.md ================================================ # zlib 压缩游程编码 ZRLE 融合游程编码、Zlib 压缩算法、图块压缩算法对传输图像进行压缩。 ZRLE 使用 Zlib 进行编码和解码,要求流数据严格有序。 ``` +--------------+--------------+-------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+-------------+ | 4 | U32 | length | | length | U8 array | zlibData | +--------------+--------------+-------------+ ``` - length: 流(stream)长度 - zlibData: 流数据 ZlibData 解压后,是从左到右,从上到下排列的图块数据。图块大小是 64x64,其他跟 TRLE 一致。对于长度和宽度不足的图形,其最后一排和最后一列的宽/高是实际尺寸,不做补齐。 ================================================ FILE: docs/rfc6143/transfer/input/README.md ================================================ # 输入协议 RFB 协议支持鼠标和键盘两种输入设备,同时支持剪贴板进行远程复制粘贴。 ================================================ FILE: docs/rfc6143/transfer/input/clipboard.md ================================================ # 剪贴板 复制粘贴是双向事件,可以由客户端向服务端复制,也可以由服务端向客户端复制。 ## 协议报文 复制剪贴板的报文如下: ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 1 | U8 [3/6] | message-type | | 3 | | padding | | 4 | U32 | length | | length | U8 array | text | +--------------+--------------+--------------+ ``` - message-type: 消息类型,客户端是 `0x6`,服务端是 `0x3` - length: 文本长度 - text: 复制粘贴的文本,长度由 length 限制 协议有几点限制 - 只支持 ISO 8859-1 (Latin-1) 字符集 - 使用单独换行符 `0x0a`,不应该使用回车符 `0x0d` ## 拓展剪贴板伪协议 > RFB 3.8 协议限制,剪贴板只能传输 Latin-1 字符集。 > 2016年,Cendio Ossman 将 [Extended Clipboard Pseudo-Encoding](https://github.com/rfbproto/rfbproto/commit/08018f655acd52970680b34021159924357efb5d) 合入协议主分支,支持在剪贴板消息中传输 unicode 字符集。 > UltraVNC/TigerVNC/RealVNC 服务端都支持此拓展协议,x11vnc 尚未提供支持(2021/8/11)。 拓展剪贴板伪协议需要客户端和服务端软件同时支持。报文拓展了 `ServerCutText` 和 `ClientCutText`, 如下: ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 1 | U8 [3/6] | message-type | | 3 | | padding | | 4 | S32 | length | | 4 | U32 | text-type | | length-4 | U8 array | text | +--------------+--------------+--------------+ ``` - length: 数据由 U32 改为 S32。首bit是标志位,0 表示传递原始 Latin-1 消息,1 表示传递拓展信息。abs(length) 是实际的消息长度。 - text-type: 消息头部,指示消息类型 - text: 消息内容 ### 消息类型 消息用 4 字节的 text-type 作为头部。 ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 1 | U32 | message-type | +--------------+--------------+--------------+ ``` text-type 分为指令(`action`)和格式(`formats`)两类。指令传输操作命令,格式传输剪贴板内容。 text-type 标记的含义如下: | Bit | Name | Description | |-|-|---| | 0 | [text](#文本内容) | 文本内容 | | 1| rtf | 微软富文本格式 | | 2 | html | 微软 HTML 格式 | | 3 | dib | Microsoft Device Independent Bitmap | | 4 | files | 文件,暂未实现 | | 5-15| fotmats 保留位 | | 16-23 | 保留 | | 24 | [caps](#能力声明) | 指示支持的 text-type 和最大长度 | | 25 | request | 强制对端传递剪贴板内容 | | 26 | peek | 强制对端提供支持的 text-type | | 27 | notify | peek 回包,返回支持的 text-type | | 28 | [provide](#粘贴板内容) | request 回包,返回粘贴板内容 | | 29-31 | actions 保留位 | | ### 文本内容 text 纯文本,unicode 编码的无格式文本。以 `\r\n` 作为行的结尾,原始协议的换行符是 `\n`。 文本应该以 `\0` 结尾,即使在 ClientCutText/ServerCutText 中都声明了文本长度。 ### 能力声明 caps Caps 指示期望收到的文本类型,发送的结构是长度数组。数组大小跟格式数量相等(0-15),数组的每个条目,指示格式支持的最大长度。 #### 数据结构 ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | formats*4 | U32 array | sizes | +--------------+--------------+--------------+ ``` 例如: ``` [1024,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] ``` 表示只接受 1024 byte 以内的纯文本信息。 #### 行为约束 服务端收到支持拓展剪贴板协议的 SetEncodings 报文时,必须主动发送类型为 caps 的 ServerCutText 消息。 客户端收到 caps 消息时,应该发送类型为 caps 的 ClientCutText 消息作为回应。 否则,客户端默认会接受 text/rtf/html/request/notify/provide 消息,其中 text 默认长度为 20 Mib,其他为 0 字节。 当最大长度限制为 0 时,认为长度没有限制,如果内容长度大于声明的长度限制,则剪贴板变动的消息不会被发送。建议将所有的 caps 设置为 0,以便接受所有的剪贴板消息变动。 > 某些实现的默认行为与协议描述不一致,例如: > - dib 也是默认支持的格式 > - text 的默认限制是 10Mid > - rft/html 默认限制为 2Mib > - dib 默认限制为 0 字节 > - 客户端忽略 caps 消息建议的格式和长度限制 在发送 caps 之前,只能发送指令,不能发送格式和内容。 ### 粘贴板内容 粘贴板内容的 text-type 是 provide。在剪贴板变化,或对端发送 request 后发送。 在 text-type 后面,是 Zlib 压缩的字节流。对于每种支持的 text-type,会发送 size + data 数据对。 ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 4 | U32 | size | | size | U8 array | data | +--------------+--------------+--------------+ ``` ================================================ FILE: docs/rfc6143/transfer/input/keyboard.md ================================================ # 键盘事件 在正常理解中,键盘事件的处理应该是简单明了的。参考以下协议报文 ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 1 | U8 [4] | message-type | | 1 | U8 | down-flag | | 2 | | padding | | 4 | U32 | key | +--------------+--------------+--------------+ ``` - message-type: 固定为 `0x4` - down-flag: `1` 表示键位按下,`0` 表示弹起 - pending: 对齐字节,方便解析 - key: 表示具体的键位 其中 key 的值在 X 系统中有[明确定义](https://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.html#keysym_encoding) ``` +-----------------+--------------------+ | Key name | Keysym value (hex) | +-----------------+--------------------+ | BackSpace | 0xff08 | | Tab | 0xff09 | | Return or Enter | 0xff0d | | Escape | 0xff1b | | Insert | 0xff63 | | Delete | 0xffff | | Home | 0xff50 | | End | 0xff57 | | Page Up | 0xff55 | | Page Down | 0xff56 | | Left | 0xff51 | | Up | 0xff52 | | Right | 0xff53 | | Down | 0xff54 | | F1 | 0xffbe | | F2 | 0xffbf | | F3 | 0xffc0 | | F4 | 0xffc1 | | ... | ... | | F12 | 0xffc9 | | Shift (left) | 0xffe1 | | Shift (right) | 0xffe2 | | Control (left) | 0xffe3 | | Control (right) | 0xffe4 | | Meta (left) | 0xffe7 | | Meta (right) | 0xffe8 | | Alt (left) | 0xffe9 | | Alt (right) | 0xffea | +-----------------+--------------------+ ``` ## 组合键 组合键指 `Ctrl + Alt + Del` 或 `Shift + 3` 等组合按键。受不同的操作系统、键盘布局影响,组合键是按键事件中容易发生歧义的一环。 RFB 基本遵循以下规则: - 如果客户端 key 在 `keysym` 中存在,服务端应该遵循 `keysym` 的指示,尽可能的忽略客户端传递 `Shift`、`CpasLock` 等键位,在需要时,应该主动补充/忽略 `Shift` 等键位。例如,在 US 键盘布局中,`#` 需要按下 `Shift + 3`,但是在 UK 布局中不需要。这就意味着用户在输入 `#` 的时候不会输入 `Shift`。这种情况下,服务端应该主动模拟一个 `Shift` 状态,防止输入的键位是 `3`。同理,如果 key 输入的键位是 `A`,服务端统一要模拟一个 `Shift`,保证输入的是 `A` 而不是 `a`。 - 如果客户端 key 在 `keysym` 中不存在(例如 `Ctrl + A`),服务端应该遵循客户端指示,客户端应该主动在 `A` 前发送 `Ctrl` 的按键。 - 如果客户端通过 `Ctrl + Alt + Q` 来输入 `@`,客户端应该在发送 `Ctrl`/`Alt`/`@`后,主动发送`Ctrl`/`Alt`的弹起事件。 - 对于 `BackTab`,常见的有三种实现,`ISO_Left_Tab` `BackTab` 和 `Shift + Tab`。RFB 协议优先使用 `Shift + Tab`,但对于其他的键位,服务端和客户端应当尽量提供兼容。 - 优先使用 `ASCII` 而不是 `unicode` - 对于 `Ctrl + Alt + Del` 等无法被客户端操作系统拦截的按键(系统拦截有更高优先级),客户端应该提供操作按钮 ================================================ FILE: docs/rfc6143/transfer/input/mouse.md ================================================ # 鼠标事件 鼠标指针即鼠标操作事件,分移动事件和点击事件两种。在 RFB 协议中,使用 PointerEvent 表示客户端触发一次鼠标事件。 ## 协议报文 鼠标事件的报文如下 ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 1 | U8 [5] | message-type | | 1 | U8 | button-mask | | 2 | U16 | x-position | | 2 | U16 | y-position | +--------------+--------------+--------------+ ``` - message-type: 无符号整形,固定 `0x5` - button-mask: 8 位掩码,表示键位状态,`1`为按下,`0`为弹起 - x-position: 当前 X 坐标 - y-position: 当前 Y 坐标 button-mask 的 Bit0/1/2 位置分别代表鼠标的左键、中建、右键。使用滚轮的鼠标,每向上滑动一次,会发送一个 Bit3 的按下和弹起事件;每向下滑动一次,会发送一个 Bit4 的按下和弹起事件。 ## USB/PS/2 鼠标协议 在 PS/2 或 USB 鼠标协议中,有类似 RFB 协议的表达方式。 ``` ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │ │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │ ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤ │Byte 1│Y Over│X Over│X Sign│Y Sign│ 0x1 │ Mid │Right │ Left │ ├──────┼──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┤ │Byte 2│ X Movement │ ├──────┼───────────────────────────────────────────────────────┤ │Byte 3│ Y Movement │ ├──────┼───────────────────────────────────────────────────────┤ │Byte 4│ Z Movement │ └──────┴───────────────────────────────────────────────────────┘ ``` 鼠标协议和 RFB 协议有两点不同: - 鼠标协议的 Bit0/1/2 和 RFB 协议的 Bit0/2/1 对应 - 鼠标协议的中轮转动,需要转换为 RFB 协议的按下/弹起事件 > 根据 [wiki](https://en.wikipedia.org/wiki/RFB_protocol#Limitations) 描述,RFB 协议只支持8个鼠标键位,左中右和滑轮占用5个,只有3个键位给特殊按键(例如:多功能游戏鼠标) ================================================ FILE: docs/rfc6143/transfer/pixel-format.md ================================================ # 像素格式 [帧缓冲](/rfc6143/GLOSSORY.md#帧缓冲)由像素构成。 ![像素组成帧缓冲](../../images/rfc6143-transfer-pixel-format-1.png) ## PixelFormat RFB 协议中,使用 16 字节结构体 PIXEL_FORMAT 描述像素的格式。 ``` +--------------+--------------+-----------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+-----------------+ | 1 | U8 | bits-per-pixel | | 1 | U8 | depth | | 1 | U8 | big-endian-flag | | 1 | U8 | true-color-flag | | 2 | U16 | red-max | | 2 | U16 | green-max | | 2 | U16 | blue-max | | 1 | U8 | red-shift | | 1 | U8 | green-shift | | 1 | U8 | blue-shift | | 3 | | padding | +--------------+--------------+-----------------+ ``` - bits-per-pixel: 像素的位数,位数越大,色彩越丰富。只支持[8|16|32] - depth: 色深,像素中表示色彩的位数 - big-endian-flag: 多字节像素的字节序,非零即大端序 - true-color-flag: 1 表示真彩色,pixel 的值表示 RGB 颜色;0 表示调色板,pexel 的值表示颜色在调色板的偏移量 - -max/-shift: 获取红蓝绿三色的位移量和长度,max=2^N-1,N是颜色的位数 ``` BigEndian: Blue Shift Green Shift Red Shift │ │ │ ▼ ▼ ▼ ┌──────────────────┬─────────────────┬─────────────────┐ │ BLUE MAX │ GREEN MAX │ RED MAX │ └──────────────────┴─────────────────┴─────────────────┘ ``` > bits-per-pixel 必须大于或等于 depth ## SetPixelFormat 客户端发送 SetPixelFormat,声明需要的的像素格式(画面质量)。此消息覆盖 [ServerInit](/rfc6143/handshake/initial.md#服务端初始化) 消息中服务端声明的初始化像素格式。 当 true-color-flag 为 0 时,服务端必须发送 SetColorMapEntries,声明使用的颜色表。客户端发送 SetPixelFormat 后,需清空本地缓存的颜色表,无论颜色表中是否有内容。 ``` +--------------+--------------+--------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+--------------+ | 1 | U8 [0] | message-type | | 3 | | padding | | 16 | PIXEL_FORMAT | pixel-format | +--------------+--------------+--------------+ ``` - message-type: 消息类型,固定为 0 - pixel-format: [PixelFormat](#pixelformat) 结构 ================================================ FILE: docs/rfc6143/transfer/set-color-map.md ================================================ # 设置颜色表 当 PIXEL_FORMAT 的 true-color-flag 字段被设置为 0 时,服务端使用颜色表表示像素的颜色。 `SetColorMapEntries` 用于设置颜色表的内容。 ``` +--------------+--------------+------------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+------------------+ | 1 | U8 [1] | message-type | | 1 | | padding | | 2 | U16 | first-color | | 2 | U16 | number-of-colors | +--------------+--------------+------------------+ ``` - message-type: 消息类型,固定是 `1` - first-color: [未知](https://github.com/rfbproto/rfbproto/issues/42) - number-of-colors: 颜色的数量 ## 色值 颜色表的值总是 3 个 16 bits,代表红、绿、蓝三种颜色,每个颜色的范围是 0-65535。 例如,白色的色值是 65535,65535,65535。 ``` +--------------+--------------+-------------+ | No. of bytes | Type [Value] | Description | +--------------+--------------+-------------+ | 2 | U16 | red | | 2 | U16 | green | | 2 | U16 | blue | +--------------+--------------+-------------+ ``` ================================================ FILE: docs/screenshot/.keep ================================================ ================================================ FILE: docs/screenshot/README.md ================================================ ## Screenshot 代码路径在`./cmd/screenshot`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译 ### 获取帮助信息 ```shell # 查看帮助信息 $ ./screenshot --help # 查看版本信息 $ ./screenshot version ``` ### 启动Screenshot 获取vnc服务器的屏幕截图 ```shell # imageFile 要生成的截图地址,暂时只支持jpeg格式(必填) # vncHost 要连接的vnc服务端地址(必填) # vncPort 要连接的vnc服务端端口(必填) # vncPassword 要连接的vnc服务端密码,不传则使用auth none $ ./screenshot --imageFile=./screen.jpeg --vncHost=127.0.0.1 --vncPort=5900 --vncPassword=12345612 ``` ================================================ FILE: docs/summary.md ================================================ * 快速入门 * [项目介绍](README.md) * [快速开始](overview.md) * [常见问题](questions.md) * [版本更新记录](changelog.md) * 代理(Proxy) - [使用方式](proxy/README.md) * 屏幕录像(Recorder) - [使用方式](recorder/README.md) * 屏幕录像播放(Player) - [使用方式](player/README.md) * 截屏(Screenshot) - [使用方式](screenshot/README.md) * 屏幕录像转视频(Video) * RFB协议详解 - [远程帧缓冲协议](rfc6143/README.md) - [术语表](rfc6143/GLOSSORY.md) - [握手](rfc6143/handshake/README.md) - [协议版本握手](rfc6143/handshake/protocol-version.md) - [安全握手](rfc6143/handshake/security-type.md) - [初始化](rfc6143/handshake/initial.md) - [数据交互](rfc6143/transfer/README.md) - [显示协议](rfc6143/transfer/display.md) - [像素格式](rfc6143/transfer/pixel-format.md) - [输入协议](rfc6143/transfer/input/README.md) - [鼠标事件](rfc6143/transfer/input/mouse.md) - [键盘事件](rfc6143/transfer/input/keyboard.md) - [剪贴板](rfc6143/transfer/input/clipboard.md) - [编码](rfc6143/transfer/encoding/README.md) - [设置编码](rfc6143/transfer/encoding/set-encoding.md) - [Raw 原始编码](rfc6143/transfer/encoding/raw.md) - [CopyRect 镜像编码](rfc6143/transfer/encoding/copy-rect.md) - [RRE 上升游程编码](rfc6143/transfer/encoding/rise-and-run-length.md) - [TRLE 图块游程编码](rfc6143/transfer/encoding/tiled-run-length.md) - [ZRLE Zlib游程编码](rfc6143/transfer/encoding/zlib-run-length.md) ================================================ FILE: docs/video/.keep ================================================ ================================================ FILE: docs/video/README.md ================================================ ================================================ FILE: encodings/default_encoding.go ================================================ package encodings import "github.com/vprix/vncproxy/rfb" var ( DefaultEncodings = []rfb.IEncoding{ &ZRLEEncoding{}, &TightEncoding{}, &HexTileEncoding{}, &TightPngEncoding{}, &RREEncoding{}, &ZLibEncoding{}, &CopyRectEncoding{}, &CoRREEncoding{}, &RawEncoding{}, &CursorPseudoEncoding{}, &DesktopNamePseudoEncoding{}, &DesktopSizePseudoEncoding{}, &CursorPosPseudoEncoding{}, &ExtendedDesktopSizePseudo{}, &CursorWithAlphaPseudoEncoding{}, &LedStatePseudo{}, &LastRectPseudo{}, &FencePseudo{}, &XCursorPseudoEncoding{}, } ) ================================================ FILE: encodings/encoding.go ================================================ package encodings import ( "encoding/binary" "errors" "github.com/vprix/vncproxy/internal/dbuffer" "github.com/vprix/vncproxy/rfb" "io" ) func ReadUint8(r io.Reader) (uint8, error) { var myUint uint8 if err := binary.Read(r, binary.BigEndian, &myUint); err != nil { return 0, err } return myUint, nil } func ReadUint16(r io.Reader) (uint16, error) { var myUint uint16 if err := binary.Read(r, binary.BigEndian, &myUint); err != nil { return 0, err } return myUint, nil } func ReadUint32(r io.Reader) (uint32, error) { var myUint uint32 if err := binary.Read(r, binary.BigEndian, &myUint); err != nil { return 0, err } return myUint, nil } func ReadBytes(count int, r io.Reader) ([]byte, error) { buff := dbuffer.GetByteBuffer() defer dbuffer.ReleaseByteBuffer(buff) buff.ChangeLen(count) lengthRead, err := io.ReadFull(r, buff.B) if lengthRead != count { return nil, errors.New("ReadBytes unable to read bytes") } if err != nil { return nil, err } return buff.Bytes(), nil } func ReadPixel(c io.Reader, pf *rfb.PixelFormat) ([]byte, error) { px := make([]byte, int(pf.BPP/8)) if err := binary.Read(c, pf.Order(), &px); err != nil { return nil, err } return px, nil } ================================================ FILE: encodings/encoding_copyrect.go ================================================ package encodings import ( "encoding/binary" "github.com/vprix/vncproxy/rfb" ) // CopyRectEncoding 该编码方式对于客户端在某些已经有了相同的象素数据的时候是非常简单和有效的。 // 这种编码方式在网络中表现为x,y 坐标。让客户端知道去拷贝那一个矩形的象素数据。 // 它可以应用于很多种情况。最明显的就是当用户在屏幕上移动某一个窗口的时候,还有在窗口内容滚动的时候。 // 在优化画的时候不是很明显,一个比较智能的服务器可能只会发送一次,因为它知道在客户端的帧缓存里已经存在了。 // 复制矩形编码并不是完全独立地发送所有的数据矩形,而是对于像素值完全相同的一组矩形, // 只发送第一个矩形全部数据,随后的矩形则只需要发送左上角X、Y坐标。 // 实际上,复制矩形编码主要指的就是随后的这一系列X、Y坐标,而对于第一个矩形具体采用何种编码类型并没有限制, // 仅仅需要知道第一个矩形在帧缓冲区中的位置,以便于完成复制操作。 // 因此,往往是把复制矩形编码和其它针对某一个矩形的编码类型结合使用。 type CopyRectEncoding struct { SX, SY uint16 } func (that *CopyRectEncoding) Type() rfb.EncodingType { return rfb.EncCopyRect } func (that *CopyRectEncoding) Supported(session rfb.ISession) bool { return true } func (that *CopyRectEncoding) Clone(data ...bool) rfb.IEncoding { obj := &CopyRectEncoding{} if len(data) > 0 && data[0] { obj.SX = that.SX obj.SY = that.SY } return obj } func (that *CopyRectEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if err := binary.Read(session, binary.BigEndian, &that.SX); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &that.SY); err != nil { return err } return nil } func (that *CopyRectEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error { if err := binary.Write(session, binary.BigEndian, that.SX); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.SY); err != nil { return err } return nil } ================================================ FILE: encodings/encoding_corre.go ================================================ package encodings import ( "bytes" "encoding/binary" "errors" "github.com/vprix/vncproxy/rfb" ) // CoRREEncoding CoRRE是RRE的变体,它把发送的最大矩形限制在255×255个像素以内,用一个字节就能表示子矩形的维度。 // 如果服务器想要发送一个超出限制的矩形,则只要把它划分成几个更小的RFB矩形即可。 // “对于通常的桌面,这样的方式具有比RRE更好的压缩度”。 // 实际上,如果进一步限制矩形的大小,就能够获得最好的压缩度。“矩形的最大值越小,决策的尺度就越好”。 // 但是,如果把矩形的最大值限制得太小,就增加了矩形的数量,而由于每个RFB矩形都会有一定的开销,结果反而会使压缩度变差。 // 所以应该选择一个比较恰当的数字。在目前的实现中,采用的最大值为48×48。 type CoRREEncoding struct { buff *bytes.Buffer } func (that *CoRREEncoding) Type() rfb.EncodingType { return rfb.EncCoRRE } func (that *CoRREEncoding) Supported(session rfb.ISession) bool { return true } func (that *CoRREEncoding) Clone(data ...bool) rfb.IEncoding { obj := &CoRREEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *CoRREEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } pf := session.Options().PixelFormat // 子矩形的数量 var numOfSubRectangles uint32 if err := binary.Read(session, binary.BigEndian, &numOfSubRectangles); err != nil { return err } if err := binary.Write(that.buff, binary.BigEndian, numOfSubRectangles); err != nil { return err } // (backgroundColor + (color=BPP + x=8b + y=8b + w=8b + h=8b)) size := uint32(pf.BPP/8) + (uint32((pf.BPP/8)+4) * numOfSubRectangles) b, err := ReadBytes(int(size), session) if err != nil { return err } _, err = that.buff.Write(b) if err != nil { return err } return nil } func (that *CoRREEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) (err error) { if that.buff == nil { return errors.New("ByteBuffer is nil") } _, err = that.buff.WriteTo(session) that.buff.Reset() return err } ================================================ FILE: encodings/encoding_h264.go ================================================ package encodings import ( "bytes" "encoding/binary" "errors" "github.com/vprix/vncproxy/rfb" ) type H264Encoding struct { buff *bytes.Buffer } var _ rfb.IEncoding = new(H264Encoding) func (that *H264Encoding) Type() rfb.EncodingType { return rfb.EncH264 } func (that *H264Encoding) Supported(session rfb.ISession) bool { return true } func (that *H264Encoding) Clone(data ...bool) rfb.IEncoding { obj := &ZLibEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *H264Encoding) Read(session rfb.ISession, rectangle *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } size, err := ReadUint32(session) if err != nil { return err } err = binary.Write(that.buff, binary.BigEndian, size) if err != nil { return err } flags, err := ReadUint32(session) if err != nil { return err } err = binary.Write(that.buff, binary.BigEndian, flags) if err != nil { return err } b, err := ReadBytes(int(size), session) if err != nil { return err } _, err = that.buff.Write(b) if err != nil { return err } return nil } func (that *H264Encoding) Write(session rfb.ISession, rectangle *rfb.Rectangle) error { if that.buff == nil { return errors.New("ByteBuffer is nil") } _, err := that.buff.WriteTo(session) that.buff.Reset() return err } ================================================ FILE: encodings/encoding_hextile.go ================================================ package encodings import ( "bytes" "encoding/binary" "fmt" "github.com/gogf/gf/v2/errors/gerror" "github.com/vprix/vncproxy/canvas" "github.com/vprix/vncproxy/rfb" "image" "image/color" ) const ( HexTileRaw = 1 << 0 // Raw数据:不压缩,直接传送,一般此位置1时其它位都置0 HexTileBackgroundSpecified = 1 << 1 // 包含背景色数据:标志位之后需要接收背景色数据 HexTileForegroundSpecified = 1 << 2 // 包含前景色数据:背景色之后需要接收前景色数据 HexTileAnySubRects = 1 << 3 // 是否含有子块:只要该块中含有两种及两种以上颜色,则此位置1 HexTileSubRectsColoured = 1 << 4 // 子块的颜色:如果含有两种颜色,此位置0,子块颜色用前景色;若该块中含有两种以上的颜色,此位置1,子块颜色需要单独指明 ) // HexTileEncoding 是RRE编码的变种,把屏幕分成16x16象素的小块,每块用Raw或RRE方式转送. // 通过解释HexTile算法,说明了简单而常用的屏幕传送和压缩算法,希望对屏幕监测、传送相关的工作有所启发 type HexTileEncoding struct { buff *bytes.Buffer } var _ rfb.IEncoding = new(HexTileEncoding) func (that *HexTileEncoding) Supported(_ rfb.ISession) bool { return true } func (that *HexTileEncoding) Clone(data ...bool) rfb.IEncoding { obj := &HexTileEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *HexTileEncoding) Type() rfb.EncodingType { return rfb.EncHexTile } // 1. 分割 // a. 传送的图像区域被分为若干个大小为16×16象素的块,如果整个矩形不是16的倍数,则最后1行(或列)的块宽度(或高度)变小 // b. 这些块按从左到右,从上到下的顺序排列,屏幕中变化的块被传送,不变的不被传送. // c. 由于块大小是16x16,所以块内坐标XY可用一字节表示,WH可用一个字节表示 // 2. 块内部的编码 // a. 计算块内部的颜色数:一种、两种、多种,并记录出现频率最多的颜色为背景色,如果仅有两种颜色,则另一颜色记为前景色 // b. 判断块内颜色数是否为一种,如果是,则先修改传送标志位,然后传送整块大小(0,0,w,h)和背景色,此块传送即完成. // c. 如果不是一种,则把块拆分成颜色不同的小矩形,方法如下: // 1. 先把块复制到一块内存区中,以免破坏原始数据,暂称tmpBuf // 2. 从第一个象素开始判断,该点颜色是否与背景色相同. // 3. 如果不同,则分别向右和向下求得与该点颜色连续的色块. // 4. 对比右色块和下色块,取出其中较大的一个,做为一个矩形色块 // 5. 在tmpBuf中把此矩形填成背景色,以避免重复判断 // 6. 继续判断下一象素点…… // d. 记录各个矩形色块的位置(x,y,w,h),如果块内含两种以上颜色,还要记录矩形色块的颜色值 // e. 一边取得矩形色块,一边判断矩形色块描述数据的总长是否大于原始数据,如果大于原始数据,则放弃取色块,标志字节Raw(HexTileRaw)位置 // 1. 以Raw方式直接传送原始数据,此块传送完成 // f. 如果块含两种以上颜色,则将标志位的子块位(HexTileSubRectsColoured)置1,否则置0 // g. 传送标志位,传送矩形色块个数,然后传送各矩形块数据,块中颜色数为2时不需要传送每个矩形块的颜色数据,只传位置即可. // h. 注意:如果背景色或前景色与前一块(16x16块)相同,则可以不传送背景色或前景色,客户端会默认延用前一块的 func (that *HexTileEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } bytesPerPixel := int(session.Options().PixelFormat.BPP) / 8 // 从上到下 for ty := rect.Y; ty < rect.Y+rect.Height; ty += 16 { th := 16 // 如果整个矩形不是16的倍数,则最后1列的块高度为实际高度 if rect.Y+rect.Height-ty < 16 { th = int(rect.Y) + int(rect.Height) - int(ty) } // 从左到右 for tx := rect.X; tx < rect.X+rect.Width; tx += 16 { tw := 16 // 如果整个矩形不是16的倍数,则最后1行的块宽度为实际宽度 if rect.X+rect.Width-tx < 16 { tw = int(rect.X) + int(rect.Width) - int(tx) } var bt []byte // 读取标志位 subEncoding, err := ReadUint8(session) if err != nil { return gerror.Newf("HextileEncoding.Read: error in hextile reader: %v", err) } _ = binary.Write(that.buff, session.Options().PixelFormat.Order(), subEncoding) // 如果是原始编码 if (subEncoding & HexTileRaw) != 0 { bt, err = ReadBytes(tw*th*bytesPerPixel, session) if err != nil { return gerror.Newf("HextileEncoding.Read: error in hextile reader: %v", err) } _, _ = that.buff.Write(bt) continue } // 包含背景色数据 if (subEncoding & HexTileBackgroundSpecified) != 0 { bt, err = ReadBytes(bytesPerPixel, session) if err != nil { return gerror.Newf("HextileEncoding.Read: error in hextile reader: %v", err) } _, _ = that.buff.Write(bt) } // 包含前景色数据 if (subEncoding & HexTileForegroundSpecified) != 0 { bt, err = ReadBytes(bytesPerPixel, session) if err != nil { return gerror.Newf("HextileEncoding.Read: error in hextile reader: %v", err) } _, _ = that.buff.Write(bt) } // 不包含子块则跳过 if (subEncoding & HexTileAnySubRects) != 0 { nSubRects, err := ReadUint8(session) if err != nil { return gerror.Newf("HextileEncoding.Read: error in hextile reader: %v", err) } _ = binary.Write(that.buff, session.Options().PixelFormat.Order(), nSubRects) for i := 0; i < int(nSubRects); i++ { if (subEncoding & HexTileSubRectsColoured) != 0 { bt, err = ReadBytes(bytesPerPixel, session) if err != nil { return gerror.Newf("HextileEncoding.Read: error in hextile reader: %v", err) } _, _ = that.buff.Write(bt) } xy, err := ReadUint8(session) if err != nil { return gerror.Newf("HextileEncoding.Read: error in hextile reader: %v", err) } _ = binary.Write(that.buff, session.Options().PixelFormat.Order(), xy) wh, err := ReadUint8(session) if err != nil { return gerror.Newf("HextileEncoding.Read: error in hextile reader: %v", err) } _ = binary.Write(that.buff, session.Options().PixelFormat.Order(), wh) } } } } return nil } func (that *HexTileEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error { if sess.Type() == rfb.CanvasSessionType { return that.draw(sess.Conn().(*canvas.VncCanvas), sess.Options().PixelFormat, rect) } var err error _, err = that.buff.WriteTo(sess) that.buff.Reset() return err } func (that *HexTileEncoding) draw(cv *canvas.VncCanvas, pf rfb.PixelFormat, rect *rfb.Rectangle) error { var bgCol *color.RGBA var fgCol *color.RGBA var err error var subEncoding byte var dimensions byte var nSubRects uint8 // 从上到下 for ty := rect.Y; ty < rect.Y+rect.Height; ty += 16 { th := 16 // 如果整个矩形不是16的倍数,则最后1列的块高度为实际高度 if rect.Y+rect.Height-ty < 16 { th = int(rect.Y) + int(rect.Height) - int(ty) } // 从左到右 for tx := rect.X; tx < rect.X+rect.Width; tx += 16 { tw := 16 // 如果整个矩形不是16的倍数,则最后1行的块宽度为实际宽度 if rect.X+rect.Width-tx < 16 { tw = int(rect.X) + int(rect.Width) - int(tx) } subEncoding, err = ReadUint8(that.buff) if err != nil { return fmt.Errorf("HextileEncoding.Read: error in hextile reader: %v", err) } // 如果是原始编码 if (subEncoding & HexTileRaw) != 0 { err = cv.DecodeRaw(that.buff, &pf, &rfb.Rectangle{X: tx, Y: ty, Width: uint16(tw), Height: uint16(th), EncType: rfb.EncRaw}) if err != nil { return err } continue } // 读取单个背景颜色 if (subEncoding & HexTileBackgroundSpecified) != 0 { bgCol, err = cv.ReadColor(that.buff, &pf) if err != nil { return fmt.Errorf("HexTileEncoding.Read: error in hexTile bg color reader: %v", err) } } // 绘制一个矩形 rBounds := image.Rectangle{ Min: image.Point{X: int(tx), Y: int(ty)}, Max: image.Point{X: int(tx) + tw, Y: int(ty) + th}, } // 填充背景色 cv.FillRect(&rBounds, bgCol) // 读取前景色 if (subEncoding & HexTileForegroundSpecified) != 0 { fgCol, err = cv.ReadColor(that.buff, &pf) if err != nil { return fmt.Errorf("HexTileEncoding.Read: error in hexTile fg color reader: %v", err) } } if (subEncoding & HexTileAnySubRects) == 0 { continue } // 读取子块的个数 nSubRects, err = ReadUint8(that.buff) if err != nil { return err } // 是否指定子块的填充颜色,如果未指定,则使用前景色 colorSpecified := (subEncoding & HexTileSubRectsColoured) != 0 for i := 0; i < int(nSubRects); i++ { var co *color.RGBA if colorSpecified { co, err = cv.ReadColor(that.buff, &pf) if err != nil { return fmt.Errorf("HexTileEncoding.Read: problem reading color from connection: %v", err) } } else { co = fgCol } fgCol = co dimensions, err = ReadUint8(that.buff) // bits 7-4 for x, bits 3-0 for y if err != nil { return fmt.Errorf("HexTileEncoding.Read: problem reading dimensions from connection: %v", err) } subTileX := dimensions >> 4 & 0x0f subTileY := dimensions & 0x0f dimensions, err = ReadUint8(that.buff) // bits 7-4 for x, bits 3-0 for y if err != nil { return fmt.Errorf("HexTileEncoding.Read: problem reading dimensions from connection: %v", err) } subTileWidth := 1 + (dimensions >> 4 & 0x0f) subTileHeight := 1 + (dimensions & 0x0f) subRectBounds := image.Rectangle{ Min: image.Point{X: int(tx) + int(subTileX), Y: int(ty) + int(subTileY)}, Max: image.Point{X: int(tx) + int(subTileX) + int(subTileWidth), Y: int(ty) + int(subTileY) + int(subTileHeight)}, } cv.FillRect(&subRectBounds, fgCol) } } } return nil } ================================================ FILE: encodings/encoding_jpeg.go ================================================ package encodings ================================================ FILE: encodings/encoding_jrle.go ================================================ package encodings ================================================ FILE: encodings/encoding_raw.go ================================================ package encodings import ( "bytes" "errors" "github.com/vprix/vncproxy/canvas" "github.com/vprix/vncproxy/rfb" ) // RawEncoding 采用原始地像素数据,而不进行任何的加工处理。 // 在这种情况下,对于一个宽度乘以高度(即面积)为N的矩形,数据就由N个像素值组成,这些值表示按照扫描线顺序从左到右排列的每个像素。 // 很明显,这种编码方式是最简单的,也是效率最低的。 // RFB要求所有的客户都必须能够处理这种原始编码的数据,并且在客户没有特别指定需要某种编码方式的时候,RFB服务器就默认生成原始编码。 type RawEncoding struct { buff *bytes.Buffer } var _ rfb.IEncoding = new(RawEncoding) func (that *RawEncoding) Supported(rfb.ISession) bool { return true } func (that *RawEncoding) Type() rfb.EncodingType { return rfb.EncRaw } func (that *RawEncoding) Clone(data ...bool) rfb.IEncoding { obj := &RawEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *RawEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error { if sess.Type() == rfb.CanvasSessionType { cv, ok := sess.Conn().(*canvas.VncCanvas) if !ok { return errors.New("canvas error") } pf := sess.Options().PixelFormat return cv.DecodeRaw(that.buff, &pf, rect) } var err error _, err = that.buff.WriteTo(sess) that.buff.Reset() return err } // Read 读取原始色彩表示 func (that *RawEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } pf := session.Options().PixelFormat // 表示单个像素是使用多少字节表示,分别为 8,16,32,对应的是1,2,4字节 // 知道表示像素的字节长度,则根据宽高就能算出此次传输的总长度 size := int(rect.Height) * int(rect.Width) * int(pf.BPP/8) b, err := ReadBytes(size, session) if err != nil { return err } _, err = that.buff.Write(b) if err != nil { return err } return nil } ================================================ FILE: encodings/encoding_rre.go ================================================ package encodings import ( "bytes" "encoding/binary" "errors" "github.com/vprix/vncproxy/rfb" ) // RREEncoding RRE表示提升和运行长度,正如它名字暗示的那样,它实质上表示二维向量的运行长度编码。 // RRE把矩形编码成可以被客户机的图形引擎翻译的格式。RRE不适合复杂的桌面,但在一些情况下比较有用。 // RRE的思想就是把像素矩形的数据分成一些子区域,和一些压缩原始区域的单元。最近最佳的分区方式一般是比较容易计算的。 // 编码是由像素值组成的,Vb(基本上是在矩形中最常用的像素值)和一个计数N,紧接着是N的子矩形列表,这些里面由数组组成,(x,y)是对应子矩形的坐标, // 表示子矩形上-左的坐标值,(w,h) 则表示子矩形的宽高。客户端可以通过绘制使用背景像素数据值,然后再根据子矩形来绘制原始矩形。 // 二维行程编码本质上是对行程编码的一个二维模拟,而其压缩度可以保证与行程编码相同甚至更好。 // 而且更重要的是,采用RRE编码的矩形被传送到客户端以后,可以立即有效地被最简单的图形引擎所还原。 type RREEncoding struct { buff *bytes.Buffer } func (that *RREEncoding) Type() rfb.EncodingType { return rfb.EncRRE } func (that *RREEncoding) Supported(session rfb.ISession) bool { return true } func (that *RREEncoding) Clone(data ...bool) rfb.IEncoding { obj := &RREEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *RREEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } pf := session.Options().PixelFormat // 子矩形的数量 var numOfSubRectangles uint32 if err := binary.Read(session, binary.BigEndian, &numOfSubRectangles); err != nil { return err } if err := binary.Write(that.buff, binary.BigEndian, numOfSubRectangles); err != nil { return err } // (backgroundColor + (color=BPP + x=16b + y=16b + w=16b + h=16b)) size := uint32(pf.BPP/8) + (uint32((pf.BPP/8)+8) * numOfSubRectangles) b, err := ReadBytes(int(size), session) if err != nil { return err } _, err = that.buff.Write(b) if err != nil { return err } return nil } func (that *RREEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { return errors.New("ByteBuffer is nil") } _, err := that.buff.WriteTo(session) that.buff.Reset() return err } ================================================ FILE: encodings/encoding_tight.go ================================================ package encodings import ( "bytes" "encoding/binary" "errors" "fmt" "github.com/vprix/vncproxy/rfb" ) var TightMinToCompress int = 12 const ( tightCompressionBasic = 0 tightCompressionFill = 0x08 tightCompressionJPEG = 0x09 tightCompressionPNG = 0x0A ) const ( TightFilterCopy = 0 TightFilterPalette = 1 TightFilterGradient = 2 ) type TightEncoding struct { buff *bytes.Buffer } func (that *TightEncoding) Supported(session rfb.ISession) bool { return true } func (that *TightEncoding) Clone(data ...bool) rfb.IEncoding { obj := &TightEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *TightEncoding) Type() rfb.EncodingType { return rfb.EncTight } func calcTightBytePerPixel(pf *rfb.PixelFormat) int { bytesPerPixel := int(pf.BPP / 8) var bytesPerPixelTight int if 24 == pf.Depth && 32 == pf.BPP { bytesPerPixelTight = 3 } else { bytesPerPixelTight = bytesPerPixel } return bytesPerPixelTight } func (that *TightEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { return errors.New("ByteBuffer is nil") } _, err := that.buff.WriteTo(session) that.buff.Reset() return err } func (that *TightEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } pf := session.Options().PixelFormat bytesPixel := calcTightBytePerPixel(&pf) compressionControl, err := ReadUint8(session) if err != nil { return nil } _ = binary.Write(that.buff, binary.BigEndian, compressionControl) compType := compressionControl >> 4 & 0x0F switch compType { case tightCompressionFill: // 全部为紧凑压缩 bt, err := ReadBytes(bytesPixel, session) if err != nil { return err } _, _ = that.buff.Write(bt) //jpeg紧凑压缩 case tightCompressionJPEG: if pf.BPP == 8 { return errors.New("Tight encoding: JPEG is not supported in 8 bpp mode. ") } // 获取jpeg流的长度 size, err := that.ReadCompactLen(session) //读取jpeg流 jpegBytes, err := ReadBytes(size, session) if err != nil { return err } _, _ = that.buff.Write(jpegBytes) default: // 默认使用基础的压缩方式 if compType > tightCompressionJPEG { return errors.New("Compression control byte is incorrect! ") } err = that.handleTightFilters(session, rect, &pf, compressionControl) return err } return nil } // ReadCompactLen 获取动态长度 func (that *TightEncoding) ReadCompactLen(session rfb.ISession) (int, error) { var err error part, err := ReadUint8(session) if err := binary.Write(that.buff, binary.BigEndian, part); err != nil { return 0, err } size := uint32(part & 0x7F) if (part & 0x80) == 0 { return int(size), nil } part, err = ReadUint8(session) if err := binary.Write(that.buff, binary.BigEndian, part); err != nil { return 0, err } size |= uint32(int(part)&0x7F) << 7 if (part & 0x80) == 0 { return int(size), nil } part, err = ReadUint8(session) if err := binary.Write(that.buff, binary.BigEndian, part); err != nil { return 0, err } size |= uint32(int(part)&0xFF) << 14 return int(size), err } // 基础压缩格式 func (that *TightEncoding) handleTightFilters(session rfb.ISession, rect *rfb.Rectangle, pf *rfb.PixelFormat, compCtl uint8) error { var FilterIdMask uint8 = 0x40 var filterId uint8 var err error if (compCtl & FilterIdMask) > 0 { filterId, err = ReadUint8(session) if err != nil { return fmt.Errorf("error in handling tight encoding, reading filterid: %s", err.Error()) } _ = binary.Write(that.buff, binary.BigEndian, filterId) } bytesPixel := calcTightBytePerPixel(pf) lengthCurrentBPP := bytesPixel * int(rect.Width) * int(rect.Height) switch filterId { case TightFilterPalette: palette, err := that.readTightPalette(session, bytesPixel) if err != nil { return err } var dataLength int if palette == 2 { dataLength = int(rect.Height) * ((int(rect.Width) + 7) / 8) } else { dataLength = int(rect.Width) * int(rect.Height) } err = that.ReadTightData(dataLength, session) if err != nil { return err } case TightFilterGradient: err = that.ReadTightData(lengthCurrentBPP, session) if err != nil { return fmt.Errorf("handleTightFilters: error in handling tight encoding, Reading GRADIENT_FILTER: %v", err) } case TightFilterCopy: err = that.ReadTightData(lengthCurrentBPP, session) if err != nil { return fmt.Errorf("handleTightFilters: error in handling tight encoding, Reading BASIC_FILTER: %v", err) } default: return fmt.Errorf("handleTightFilters: Bad tight filter id: %d", filterId) } return nil } // 获取调色板数据 func (that *TightEncoding) readTightPalette(session rfb.ISession, bytesPixel int) (int, error) { colorCount, err := ReadUint8(session) if err != nil { return 0, fmt.Errorf("handleTightFilters: error in handling tight encoding, reading TightFilterPalette: %v", err) } _ = binary.Write(that.buff, binary.BigEndian, colorCount) // 注意这个地方,必须先转换为int类型,不然如果colorCount为255,+1的情况下会溢出,变成0,造成bug numColors := int(colorCount) + 1 paletteSize := numColors * bytesPixel paletteColorBytes, err := ReadBytes(paletteSize, session) if err != nil { return numColors, err } _, _ = that.buff.Write(paletteColorBytes) return numColors, nil } func (that *TightEncoding) ReadTightData(dataSize int, session rfb.ISession) error { if dataSize < TightMinToCompress { b, err := ReadBytes(dataSize, session) if err == nil { _, _ = that.buff.Write(b) } return err } zlibDataLen, err := that.ReadCompactLen(session) if err != nil { return err } zippedBytes, err := ReadBytes(zlibDataLen, session) if err != nil { return err } _, _ = that.buff.Write(zippedBytes) return nil } ================================================ FILE: encodings/encoding_tightpng.go ================================================ package encodings import ( "bytes" "encoding/binary" "errors" "fmt" "github.com/vprix/vncproxy/rfb" ) type TightPngEncoding struct { buff *bytes.Buffer } func (that *TightPngEncoding) Supported(session rfb.ISession) bool { return true } func (that *TightPngEncoding) Clone(data ...bool) rfb.IEncoding { obj := &TightPngEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *TightPngEncoding) Type() rfb.EncodingType { return rfb.EncTightPng } func (that *TightPngEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { return errors.New("ByteBuffer is nil") } _, err := that.buff.WriteTo(session) that.buff.Reset() return err } func (that *TightPngEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } pf := session.Options().PixelFormat bytesPixel := calcTightBytePerPixel(&pf) compressionControl, err := ReadUint8(session) if err != nil { return nil } _ = binary.Write(that.buff, binary.BigEndian, compressionControl) compType := compressionControl >> 4 & 0x0F switch compType { case tightCompressionPNG: size, err := that.ReadCompactLen(session) if err != nil { return err } bt, err := ReadBytes(size, session) if err != nil { return err } _, _ = that.buff.Write(bt) case tightCompressionFill: bt, err := ReadBytes(bytesPixel, session) if err != nil { return err } _, _ = that.buff.Write(bt) default: return fmt.Errorf("unknown tight compression %d", compType) } return nil } // ReadCompactLen 获取动态长度 func (that *TightPngEncoding) ReadCompactLen(session rfb.ISession) (int, error) { var err error part, err := ReadUint8(session) if err := binary.Write(that.buff, binary.BigEndian, part); err != nil { return 0, err } size := uint32(part & 0x7F) if (part & 0x80) == 0 { return int(size), nil } part, err = ReadUint8(session) if err := binary.Write(that.buff, binary.BigEndian, part); err != nil { return 0, err } size |= uint32(int(part)&0x7F) << 7 if (part & 0x80) == 0 { return int(size), nil } part, err = ReadUint8(session) if err := binary.Write(that.buff, binary.BigEndian, part); err != nil { return 0, err } size |= uint32(int(part)&0xFF) << 14 return int(size), err } ================================================ FILE: encodings/encoding_trle.go ================================================ package encodings ================================================ FILE: encodings/encoding_zlib.go ================================================ package encodings import ( "bytes" "encoding/binary" "errors" "github.com/vprix/vncproxy/rfb" ) type ZLibEncoding struct { buff *bytes.Buffer } func (that *ZLibEncoding) Supported(c rfb.ISession) bool { return true } func (that *ZLibEncoding) Type() rfb.EncodingType { return rfb.EncZlib } func (that *ZLibEncoding) Clone(data ...bool) rfb.IEncoding { obj := &ZLibEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *ZLibEncoding) Read(sess rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } size, err := ReadUint32(sess) if err != nil { return err } err = binary.Write(that.buff, binary.BigEndian, size) if err != nil { return err } b, err := ReadBytes(int(size), sess) if err != nil { return err } _, err = that.buff.Write(b) if err != nil { return err } return nil } func (that *ZLibEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { return errors.New("ByteBuffer is nil") } _, err := that.buff.WriteTo(sess) that.buff.Reset() return err } ================================================ FILE: encodings/encoding_zrle.go ================================================ package encodings import ( "bytes" "compress/zlib" "encoding/binary" "errors" "fmt" "github.com/vprix/vncproxy/canvas" "github.com/vprix/vncproxy/rfb" "image/color" "io" ) const ( ZRLERawPixelData = 0 ZRLESingleColour = 1 ) // ZRLEEncoding ZRLE(Zlib Run - Length Encoding),它结合了zlib 压缩,片技术、调色板和运行长度编码。 // 在传输中,矩形以4 字节长度区域开始,紧接着是zlib 压缩的数据,一个单一的 zlib“流”对象被用在RFB协议的连接上, // 因此ZRLE矩形必须严格的按照顺序进行编码和译码。 type ZRLEEncoding struct { buff *bytes.Buffer } func (that *ZRLEEncoding) Type() rfb.EncodingType { return rfb.EncZRLE } func (that *ZRLEEncoding) Supported(session rfb.ISession) bool { return true } func (that *ZRLEEncoding) Clone(data ...bool) rfb.IEncoding { obj := &ZRLEEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *ZRLEEncoding) Read(sess rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } size, err := ReadUint32(sess) if err != nil { return err } err = binary.Write(that.buff, binary.BigEndian, size) if err != nil { return err } b, err := ReadBytes(int(size), sess) if err != nil { return err } _, err = that.buff.Write(b) if err != nil { return err } return nil } func (that *ZRLEEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { return errors.New("ByteBuffer is nil") } if sess.Type() == rfb.CanvasSessionType { return that.draw(sess.Conn().(*canvas.VncCanvas), sess.Options().PixelFormat, rect) } _, err := that.buff.WriteTo(sess) that.buff.Reset() return err } // 绘制画布 func (that *ZRLEEncoding) draw(cv *canvas.VncCanvas, pf rfb.PixelFormat, rect *rfb.Rectangle) error { var size uint32 err := binary.Read(that.buff, binary.BigEndian, &size) if err != nil { return err } b, err := ReadBytes(int(size), that.buff) if err != nil { return err } bytesBuff := bytes.NewBuffer(b) unZipper, err := zlib.NewReader(bytesBuff) if err != nil { return err } for tileOffsetY := 0; tileOffsetY < int(rect.Height); tileOffsetY += 64 { tileHeight := min(64, int(rect.Height)-tileOffsetY) for tileOffsetX := 0; tileOffsetX < int(rect.Width); tileOffsetX += 64 { tileWidth := min(64, int(rect.Width)-tileOffsetX) // 获取二级编码格式 subEnc, err := ReadUint8(unZipper) if err != nil { return fmt.Errorf("renderZRLE: error while reading subencoding: %v", err) } switch { case subEnc == ZRLERawPixelData: // 原始编码格式 err = that.readZRLERaw(cv, unZipper, &pf, int(rect.X)+tileOffsetX, int(rect.Y)+tileOffsetY, tileWidth, tileHeight) if err != nil { return fmt.Errorf("renderZRLE: error while reading Raw tile: %v", err) } case subEnc == ZRLESingleColour: // 获取一个颜色,填充指定区域 co, err := readCPixel(cv, unZipper, &pf) if err != nil { return fmt.Errorf("renderZRLE: error while reading CPixel for bgColor tile: %v", err) } myRect := canvas.MakeRect(int(rect.X)+tileOffsetX, int(rect.Y)+tileOffsetY, tileWidth, tileHeight) cv.FillRect(&myRect, co) case subEnc >= 2 && subEnc <= 16: // 调色版编码 err = that.handlePaletteTile(cv, unZipper, tileOffsetX, tileOffsetY, tileWidth, tileHeight, subEnc, &pf, rect) if err != nil { return err } case subEnc == 128: // err = that.handlePlainRLETile(cv, unZipper, tileOffsetX, tileOffsetY, tileWidth, tileHeight, &pf, rect) if err != nil { return err } case subEnc >= 130: err = that.handlePaletteRLETile(cv, unZipper, tileOffsetX, tileOffsetY, tileWidth, tileHeight, subEnc, &pf, rect) if err != nil { return err } default: return fmt.Errorf("Unknown ZRLE subencoding: %v ", subEnc) } } } return nil } func min(a, b int) int { if a < b { return a } return b } func (that *ZRLEEncoding) readZRLERaw(cv *canvas.VncCanvas, reader io.Reader, pf *rfb.PixelFormat, tx, ty, tw, th int) error { for y := 0; y < th; y++ { for x := 0; x < tw; x++ { col, err := readCPixel(cv, reader, pf) if err != nil { return err } cv.Set(tx+x, ty+y, col) } } return nil } // 获取像素格式 func readCPixel(cv *canvas.VncCanvas, c io.Reader, pf *rfb.PixelFormat) (*color.RGBA, error) { if pf.TrueColor == 0 { return nil, errors.New("support for non true color formats was not implemented") } isZRLEFormat := IsCPixelSpecific(pf) var col *color.RGBA if isZRLEFormat { tBytes, err := ReadBytes(3, c) if err != nil { return nil, err } if pf.BigEndian != 1 { col = &color.RGBA{ B: tBytes[0], G: tBytes[1], R: tBytes[2], A: uint8(1), } } else { col = &color.RGBA{ R: tBytes[0], G: tBytes[1], B: tBytes[2], A: uint8(1), } } return col, nil } col, err := cv.ReadColor(c, pf) if err != nil { return nil, fmt.Errorf("readCPixel: Error while reading zrle: %v", err) } return col, nil } func IsCPixelSpecific(pf *rfb.PixelFormat) bool { significant := int(pf.RedMax<> (8 - indexBits) buffer <<= indexBits bitsAvailable -= indexBits // Write pixel to image cv.Set(tileOffsetX+int(rect.X)+x, tileOffsetY+int(rect.Y)+y, palette[index]) } } return err } // 普通rle编码 func (that *ZRLEEncoding) handlePlainRLETile(cv *canvas.VncCanvas, unZipper io.Reader, tileOffsetX int, tileOffsetY int, tileWidth int, tileHeight int, pf *rfb.PixelFormat, rect *rfb.Rectangle) error { var col *color.RGBA var err error runLen := 0 for y := 0; y < tileHeight; y++ { for x := 0; x < tileWidth; x++ { if runLen == 0 { // Read length and color col, err = readCPixel(cv, unZipper, pf) if err != nil { return fmt.Errorf("handlePlainRLETile: error while reading CPixel in plain RLE subencoding: %v", err) } runLen, err = readRunLength(unZipper) if err != nil { return fmt.Errorf("handlePlainRLETile: error while reading runlength in plain RLE subencoding: %v", err) } } // Write pixel to image cv.Set(tileOffsetX+int(rect.X)+x, tileOffsetY+int(rect.Y)+y, col) runLen-- } } return err } func readRunLength(r io.Reader) (int, error) { runLen := 1 addition, err := ReadUint8(r) if err != nil { return 0, fmt.Errorf("renderZRLE: error while reading addition to runLen in plain RLE subencoding: %v", err) } runLen += int(addition) for addition == 255 { addition, err = ReadUint8(r) if err != nil { return 0, fmt.Errorf("renderZRLE: error while reading addition to runLen in-loop plain RLE subencoding: %v", err) } runLen += int(addition) } return runLen, nil } // 调色板rle编码 func (that *ZRLEEncoding) handlePaletteRLETile(cv *canvas.VncCanvas, unZipper io.Reader, tileOffsetX, tileOffsetY, tileWidth, tileHeight int, subEnc uint8, pf *rfb.PixelFormat, rect *rfb.Rectangle) error { // Palette RLE paletteSize := subEnc - 128 palette := make([]*color.RGBA, paletteSize) var err error // Read RLE palette for j := 0; j < int(paletteSize); j++ { palette[j], err = readCPixel(cv, unZipper, pf) if err != nil { return fmt.Errorf("renderZRLE: error while reading color in palette RLE subencoding: %v", err) } } var index uint8 runLen := 0 for y := 0; y < tileHeight; y++ { for x := 0; x < tileWidth; x++ { if runLen == 0 { // Read length and index index, err = ReadUint8(unZipper) if err != nil { return fmt.Errorf("renderZRLE: error while reading length and index in palette RLE subencoding: %v", err) } runLen = 1 // Run is represented by index | 0x80 // Otherwise, single pixel if (index & 0x80) != 0 { index -= 128 runLen, err = readRunLength(unZipper) if err != nil { return fmt.Errorf("handlePlainRLETile: error while reading runlength in plain RLE subencoding: %v", err) } } } // Write pixel to image cv.Set(tileOffsetX+int(rect.X)+x, tileOffsetY+int(rect.Y)+y, palette[index]) runLen-- } } return nil } ================================================ FILE: encodings/pseudo_cursor.go ================================================ package encodings import ( "bytes" "encoding/binary" "github.com/vprix/vncproxy/canvas" "github.com/vprix/vncproxy/rfb" "image" "image/color" "math" ) // CursorPseudoEncoding 如果客户端请求指针/鼠标伪编码,那么就是说它有能力进行本地绘制鼠标。 // 这样就可以明显改善传输性能。服务器通过发送带有伪鼠标编码的伪矩形来设置鼠标的形状作为更新的一部分。 // 伪矩形的x 和y 表示鼠标的热点,宽和高表示用像素来表示鼠标的宽和高。包含宽X高像素值的数据带有位掩码。 // 位掩码是由从左到右,从上到下的扫描线组成,而每一扫描线被填充为floor((width +7) / 8)。 // 对应每一字节最重要的位表示最左边像素,对应1 位表示相应指针的像素是正确的。 type CursorPseudoEncoding struct { buff *bytes.Buffer } func (that *CursorPseudoEncoding) Supported(session rfb.ISession) bool { return true } // Draw 绘制鼠标指针 func (that *CursorPseudoEncoding) draw(cv *canvas.VncCanvas, pf rfb.PixelFormat, rect *rfb.Rectangle) error { numColors := int(rect.Height) * int(rect.Width) colors := make([]color.Color, numColors) var err error for i := 0; i < numColors; i++ { colors[i], err = cv.ReadColor(that.buff, &pf) if err != nil { return err } } // 获取掩码信息 bitmask := make([]byte, int((rect.Width+7)/8*rect.Height)) if err = binary.Read(that.buff, binary.BigEndian, &bitmask); err != nil { return err } scanLine := (rect.Width + 7) / 8 // 生成鼠标指针的形状 cursorImg := image.NewRGBA(canvas.MakeRect(0, 0, int(rect.Width), int(rect.Height))) var cursorMask [][]bool for i := 0; i < int(rect.Width); i++ { cursorMask = append(cursorMask, make([]bool, rect.Height)) } // 填充鼠标指针的颜色 for y := 0; y < int(rect.Height); y++ { for x := 0; x < int(rect.Width); x++ { offset := y*int(rect.Width) + x if bitmask[y*int(scanLine)+x/8]&(1< 0 { cursorImg.Set(x, y, colors[offset]) cursorMask[x][y] = true } } } // 设置鼠标指针 cv.CursorOffset = &image.Point{X: int(rect.X), Y: int(rect.Y)} cv.Cursor = cursorImg cv.CursorBackup = image.NewRGBA(cursorImg.Bounds()) cv.CursorMask = cursorMask return nil } func (that *CursorPseudoEncoding) Clone(data ...bool) rfb.IEncoding { obj := &CursorPseudoEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *CursorPseudoEncoding) Type() rfb.EncodingType { return rfb.EncCursorPseudo } func (that *CursorPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if rect.Width*rect.Height == 0 { return nil } if that.buff == nil { that.buff = &bytes.Buffer{} } var bt []byte var err error bytesPixel := int(session.Options().PixelFormat.BPP / 8) //calcTightBytePerPixel(pf) bt, err = ReadBytes(int(rect.Width*rect.Height)*bytesPixel, session) if err != nil { return err } _, _ = that.buff.Write(bt) mask := ((rect.Width + 7) / 8) * rect.Height bt, err = ReadBytes(int(math.Floor(float64(mask))), session) if err != nil { return err } _, _ = that.buff.Write(bt) return nil } func (that *CursorPseudoEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { return nil } if sess.Type() == rfb.CanvasSessionType { return that.draw(sess.Conn().(*canvas.VncCanvas), sess.Options().PixelFormat, rect) } var err error _, err = that.buff.WriteTo(sess) that.buff.Reset() return err } ================================================ FILE: encodings/pseudo_cursor_with_alpha.go ================================================ package encodings import ( "bytes" "github.com/vprix/vncproxy/rfb" ) type CursorWithAlphaPseudoEncoding struct { buff *bytes.Buffer } func (that *CursorWithAlphaPseudoEncoding) Supported(session rfb.ISession) bool { return true } func (that *CursorWithAlphaPseudoEncoding) Clone(data ...bool) rfb.IEncoding { obj := &CursorWithAlphaPseudoEncoding{} if len(data) > 0 && data[0] { if that.buff != nil { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } } return obj } func (that *CursorWithAlphaPseudoEncoding) Type() rfb.EncodingType { return rfb.EncCursorWithAlphaPseudo } func (that *CursorWithAlphaPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } var bt []byte var err error bt, err = ReadBytes(4, session) if err != nil { return err } _, _ = that.buff.Write(bt) if rect.Width*rect.Height > 0 { bt2, err := ReadBytes(int(rect.Width*rect.Height)*4, session) if err != nil { return err } _, _ = that.buff.Write(bt2) } return nil } func (that *CursorWithAlphaPseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { return nil } var err error _, err = that.buff.WriteTo(session) that.buff.Reset() return err } ================================================ FILE: encodings/pseudo_desktop_name.go ================================================ package encodings import ( "encoding/binary" "github.com/vprix/vncproxy/rfb" ) // DesktopNamePseudoEncoding 服务端设置桌面名字的消息 type DesktopNamePseudoEncoding struct { Name []byte } func (that *DesktopNamePseudoEncoding) Supported(session rfb.ISession) bool { return true } func (that *DesktopNamePseudoEncoding) Clone(data ...bool) rfb.IEncoding { obj := &DesktopNamePseudoEncoding{} if len(data) > 0 && data[0] { obj.Name = that.Name } return obj } func (that *DesktopNamePseudoEncoding) Type() rfb.EncodingType { return rfb.EncDesktopNamePseudo } // Read 实现了编码接口 func (that *DesktopNamePseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { var length uint32 if err := binary.Read(session, binary.BigEndian, &length); err != nil { return err } name := make([]byte, length) if err := binary.Read(session, binary.BigEndian, &name); err != nil { return err } that.Name = name return nil } func (that *DesktopNamePseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error { if err := binary.Write(session, binary.BigEndian, uint32(len(that.Name))); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.Name); err != nil { return err } return session.Flush() } ================================================ FILE: encodings/pseudo_desktop_size.go ================================================ package encodings import ( "github.com/vprix/vncproxy/rfb" ) // DesktopSizePseudoEncoding 如果客户端请求桌面大小伪编码,那么就是说它能处理帧缓存宽/高的改变。 // 服务器通过发送带有桌面大小伪编码的伪矩形作为上一个矩形来完成一次更新。 // 伪矩形的x 和y 被忽略,而宽和高表示帧缓存新的宽和高。没有其他的数据与伪矩形有关。 type DesktopSizePseudoEncoding struct { } func (that *DesktopSizePseudoEncoding) Supported(session rfb.ISession) bool { return true } func (that *DesktopSizePseudoEncoding) Clone(data ...bool) rfb.IEncoding { obj := &DesktopSizePseudoEncoding{} return obj } func (that *DesktopSizePseudoEncoding) Type() rfb.EncodingType { return rfb.EncDesktopSizePseudo } // Read implements the Encoding interface. func (that *DesktopSizePseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { return nil } func (that *DesktopSizePseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error { return nil } ================================================ FILE: encodings/pseudo_extended_desktop_size.go ================================================ package encodings import ( "bytes" "encoding/binary" "github.com/vprix/vncproxy/rfb" ) // ExtendedDesktopSizePseudo 扩展适应客户端桌面分辨率 type ExtendedDesktopSizePseudo struct { buff *bytes.Buffer } func (that *ExtendedDesktopSizePseudo) Supported(rfb.ISession) bool { return true } func (that *ExtendedDesktopSizePseudo) Clone(data ...bool) rfb.IEncoding { obj := &ExtendedDesktopSizePseudo{} if len(data) > 0 && data[0] { obj.buff = &bytes.Buffer{} _, _ = obj.buff.Write(that.buff.Bytes()) } return obj } func (that *ExtendedDesktopSizePseudo) Type() rfb.EncodingType { return rfb.EncExtendedDesktopSizePseudo } func (that *ExtendedDesktopSizePseudo) Write(session rfb.ISession, rect *rfb.Rectangle) (err error) { _, err = that.buff.WriteTo(session) that.buff.Reset() return err } func (that *ExtendedDesktopSizePseudo) Read(session rfb.ISession, rect *rfb.Rectangle) error { if that.buff == nil { that.buff = &bytes.Buffer{} } //读取屏幕数量 screensNumber, err := ReadUint8(session) if err != nil { return err } err = binary.Write(that.buff, binary.BigEndian, screensNumber) if err != nil { return err } // 填充 pad, err := ReadBytes(3, session) if err != nil { return err } err = binary.Write(that.buff, binary.BigEndian, pad) b2, err := ReadBytes(int(screensNumber)*16, session) if err != nil { return err } _, _ = that.buff.Write(b2) return nil } ================================================ FILE: encodings/pseudo_fence.go ================================================ package encodings import ( "github.com/vprix/vncproxy/rfb" ) type FencePseudo struct { } func (that *FencePseudo) Supported(_ rfb.ISession) bool { return true } func (that *FencePseudo) Clone(_ ...bool) rfb.IEncoding { obj := &FencePseudo{} return obj } func (that *FencePseudo) Type() rfb.EncodingType { return rfb.EncFencePseudo } func (that *FencePseudo) Read(_ rfb.ISession, _ *rfb.Rectangle) error { return nil } func (that *FencePseudo) Write(_ rfb.ISession, _ *rfb.Rectangle) error { return nil } ================================================ FILE: encodings/pseudo_last_rect.go ================================================ package encodings import ( "github.com/vprix/vncproxy/rfb" ) type LastRectPseudo struct { } func (that *LastRectPseudo) Supported(_ rfb.ISession) bool { return true } func (that *LastRectPseudo) Clone(_ ...bool) rfb.IEncoding { obj := &LastRectPseudo{} return obj } func (that *LastRectPseudo) Type() rfb.EncodingType { return rfb.EncLastRectPseudo } func (that *LastRectPseudo) Read(_ rfb.ISession, _ *rfb.Rectangle) error { return nil } func (that *LastRectPseudo) Write(_ rfb.ISession, _ *rfb.Rectangle) error { return nil } ================================================ FILE: encodings/pseudo_led_state.go ================================================ package encodings import ( "encoding/binary" "github.com/vprix/vncproxy/rfb" ) // LedStatePseudo 切换客户端本地小键盘锁定的led灯 // 0 滚动锁 // 1 数字锁定 // 2 大写锁定 type LedStatePseudo struct { LedState uint8 } func (that *LedStatePseudo) Supported(session rfb.ISession) bool { return true } func (that *LedStatePseudo) Clone(data ...bool) rfb.IEncoding { obj := &LedStatePseudo{} return obj } func (that *LedStatePseudo) Type() rfb.EncodingType { return rfb.EncLedStatePseudo } func (that *LedStatePseudo) Read(session rfb.ISession, rect *rfb.Rectangle) error { u8, err := ReadUint8(session) if err != nil { return err } that.LedState = u8 return nil } func (that *LedStatePseudo) Write(session rfb.ISession, rect *rfb.Rectangle) error { if err := binary.Write(session, binary.BigEndian, that.LedState); err != nil { return err } return nil } ================================================ FILE: encodings/pseudo_pointer_pos.go ================================================ package encodings import ( "errors" "github.com/vprix/vncproxy/canvas" "github.com/vprix/vncproxy/rfb" "image" "image/draw" ) type CursorPosPseudoEncoding struct { } func (that *CursorPosPseudoEncoding) Supported(session rfb.ISession) bool { return true } func (that *CursorPosPseudoEncoding) Draw(img draw.Image, rect *rfb.Rectangle) error { cv, ok := img.(*canvas.VncCanvas) if !ok { return errors.New("canvas error") } // 本地鼠标指针的位置 cv.CursorLocation = &image.Point{X: int(rect.X), Y: int(rect.Y)} return nil } func (that *CursorPosPseudoEncoding) Clone(data ...bool) rfb.IEncoding { obj := &CursorPosPseudoEncoding{} return obj } func (that *CursorPosPseudoEncoding) Type() rfb.EncodingType { return rfb.EncPointerPosPseudo } func (that *CursorPosPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { return nil } func (that *CursorPosPseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error { return nil } ================================================ FILE: encodings/pseudo_x_cursor.go ================================================ package encodings import ( "encoding/binary" "github.com/vprix/vncproxy/rfb" "math" ) type XCursorPseudoEncoding struct { PrimaryR, PrimaryG, PrimaryB uint8 // 主颜色 SecondaryR, SecondaryG, SecondaryB uint8 // 次颜色 Bitmap []byte //颜色位图 Bitmask []byte //透明度位掩码 } func (that *XCursorPseudoEncoding) Supported(session rfb.ISession) bool { return true } func (that *XCursorPseudoEncoding) Clone(data ...bool) rfb.IEncoding { obj := &XCursorPseudoEncoding{} if len(data) > 0 && data[0] { obj.PrimaryR = that.PrimaryR obj.PrimaryG = that.PrimaryG obj.PrimaryB = that.PrimaryB obj.SecondaryR = that.SecondaryR obj.SecondaryG = that.SecondaryG obj.SecondaryB = that.SecondaryB Bitmap := make([]byte, len(that.Bitmap)) Bitmask := make([]byte, len(that.Bitmask)) copy(Bitmap, that.Bitmap) copy(Bitmask, that.Bitmask) obj.Bitmap = Bitmap obj.Bitmask = Bitmask } return obj } func (that *XCursorPseudoEncoding) Type() rfb.EncodingType { return rfb.EncXCursorPseudo } func (that *XCursorPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error { if err := binary.Read(session, binary.BigEndian, &that.PrimaryR); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &that.PrimaryG); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &that.PrimaryB); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &that.SecondaryR); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &that.SecondaryG); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &that.SecondaryB); err != nil { return err } bitMapSize := int(math.Floor((float64(rect.Width)+7)/8) * float64(rect.Height)) bitMaskSize := int(math.Floor((float64(rect.Width)+7)/8) * float64(rect.Height)) that.Bitmap = make([]byte, bitMapSize) that.Bitmask = make([]byte, bitMaskSize) if err := binary.Read(session, binary.BigEndian, &that.Bitmap); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &that.Bitmask); err != nil { return err } return nil } func (that *XCursorPseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error { if err := binary.Write(session, binary.BigEndian, that.PrimaryR); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.PrimaryG); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.PrimaryB); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.SecondaryR); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.SecondaryG); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.SecondaryB); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.Bitmap); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.Bitmask); err != nil { return err } return nil } ================================================ FILE: go.mod ================================================ module github.com/vprix/vncproxy require ( github.com/gogf/gf/v2 v2.9.5 github.com/osgochina/dmicro v1.3.1 golang.org/x/net v0.47.0 ) require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grokify/html-strip-tags-go v0.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/reedsolomon v1.12.0 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/olekukonko/errors v1.1.0 // indirect github.com/olekukonko/ll v0.0.9 // indirect github.com/olekukonko/tablewriter v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/quic-go/quic-go v0.56.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/xtaci/kcp-go/v5 v5.6.37 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect golang.org/x/crypto v0.44.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go 1.24.7 ================================================ FILE: handler/ClientClientInitHandler.go ================================================ package handler import ( "context" "encoding/binary" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" ) // ClientClientInitHandler vnc握手步骤第三步 // 1. 根据配置信息判断该vnc会话是否独占, // 2. 发送是否独占标识给vnc服务端 type ClientClientInitHandler struct{} func (that *ClientClientInitHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debug(context.TODO(), "[Proxy客户端->VNC服务端]: 执行vnc握手步骤第三步[ClientInit]") } cfg := session.Options() var shared uint8 if cfg.Exclusive { shared = 0 } else { shared = 1 } if err := binary.Write(session, binary.BigEndian, shared); err != nil { return err } if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: 执行ClientInit步骤,发送shared=%d", shared) } return session.Flush() } ================================================ FILE: handler/ClientMessageHandler.go ================================================ package handler import ( "encoding/binary" "fmt" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" "golang.org/x/net/context" ) // ClientMessageHandler vnc握手已结束,进入消息交互阶段 // 启动两个协程处理后续消息逻辑 // 1. 协程1:通过ClientMessageCh通道获取消息,并把该消息写入到vnc服务端会话中。 // 2. 协程2:从vnc服务端会话中读取消息类型及消息内容,组装该消息,发消息发送到ServerMessageCh通道中,供其他功能消费 // 3. 发送编码格式消息SetEncodings到vnc服务端 // 4. 发送帧数据请求消息FramebufferUpdateRequest到vnc服务端 type ClientMessageHandler struct{} func (*ClientMessageHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debug(context.TODO(), "[Proxy客户端->VNC服务端]: vnc握手已结束,进入消息交互阶段[ClientMessageHandler]") } cfg := session.Options() var err error // proxy客户端支持的消息类型 serverMessages := make(map[rfb.MessageType]rfb.Message) for _, m := range cfg.Messages { serverMessages[m.Type()] = m } // 通过ClientMessageCh通道获取消息,并把该消息写入到vnc服务端会话中。 go func() { for { select { case <-session.Wait(): return case msg := <-cfg.Input: if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端] 消息类型:%s,消息内容:%s", rfb.ClientMessageType(msg.Type()), msg.String()) } if err = msg.Write(session); err != nil { cfg.ErrorCh <- err _ = session.Close() return } } } }() // 从vnc服务端会话中读取消息类型及消息内容,组装该消息,发消息发送到ServerMessageCh通道中,供其他功能消费 go func() { for { select { case <-session.Wait(): return default: // 从会话中读取消息类型 var messageType rfb.MessageType if err = binary.Read(session, binary.BigEndian, &messageType); err != nil { cfg.ErrorCh <- err return } if logger.IsDebug() { logger.Debugf(context.TODO(), "[VNC服务端->Proxy客户端] 消息类型:%s", rfb.ServerMessageType(messageType)) } // 判断proxy客户端是否支持该消息 msg, ok := serverMessages[messageType] if !ok { err = fmt.Errorf("未知的消息类型: %v", messageType) cfg.ErrorCh <- err _ = session.Close() return } // 读取消息内容 parsedMsg, err := msg.Read(session) if err != nil { cfg.ErrorCh <- err _ = session.Close() return } if logger.IsDebug() { logger.Debugf(context.TODO(), "[VNC服务端->Proxy客户端] 消息类型:%s,消息内容:%s", rfb.ServerMessageType(parsedMsg.Type()), parsedMsg) } cfg.Output <- parsedMsg } } }() return nil } ================================================ FILE: handler/ClientSecurityHandler.go ================================================ package handler import ( "context" "encoding/binary" "fmt" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" ) // ClientSecurityHandler vnc握手步骤第二步 // 1. 读取vnc服务端支持的安全认证套件数量及类型 // 2. 匹配vnc服务端与proxy客户端的安全认证套件 // 3. 进入安全认证套件认证流程 // 4. 获取认证结果,如果认证失败,获取失败的原因。 type ClientSecurityHandler struct{} func (*ClientSecurityHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: 执行vnc握手第二步:[Security]") } cfg := session.Options() // 读取vnc服务端支持的安全认证套件数量 var numSecurityTypes uint8 if err := binary.Read(session, binary.BigEndian, &numSecurityTypes); err != nil { return err } // 读取vnc服务端支持的安全认证套件类型 secTypes := make([]rfb.SecurityType, numSecurityTypes) if err := binary.Read(session, binary.BigEndian, &secTypes); err != nil { return err } // 匹配vnc服务端与proxy客户端的安全认证套件 var secType rfb.ISecurityHandler for _, st := range cfg.SecurityHandlers { for _, sc := range secTypes { if st.Type() == sc { secType = st } } } // 发送proxy客户端选中的安全认证套件 if err := binary.Write(session, binary.BigEndian, cfg.SecurityHandlers[0].Type()); err != nil { return err } if err := session.Flush(); err != nil { return err } // 进入安全认证套件认证流程 err := secType.Auth(session) if err != nil { return fmt.Errorf("安全认证失败, error:%v", err) } // 读取安全认证结果 var authCode uint32 if err := binary.Read(session, binary.BigEndian, &authCode); err != nil { return err } if logger.IsDebug() { logger.Debugf(context.TODO(), "安全认证中, 安全认证套件类型: %d,认证结果(0为成功): %d", rfb.ClientMessageType(secType.Type()), authCode) } //如果认证失败,则读取失败原因 if authCode == 1 { var reasonLength uint32 if err = binary.Read(session, binary.BigEndian, &reasonLength); err != nil { return err } reasonText := make([]byte, reasonLength) if err = binary.Read(session, binary.BigEndian, &reasonText); err != nil { return err } return fmt.Errorf("%s", reasonText) } session.SetSecurityHandler(secType) return nil } ================================================ FILE: handler/ClientServerInitHandler.go ================================================ package handler import ( "context" "encoding/binary" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" ) // ClientServerInitHandler vnc握手第四步 // 1. 读取vnc服务端发送的屏幕宽高,像素格式,桌面名称 type ClientServerInitHandler struct{} func (*ClientServerInitHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: 执行vnc握手第四步:[ServerInit]") } var err error srvInit := messages.ServerInit{} if err = binary.Read(session, binary.BigEndian, &srvInit.FBWidth); err != nil { return err } if err = binary.Read(session, binary.BigEndian, &srvInit.FBHeight); err != nil { return err } if err = binary.Read(session, binary.BigEndian, &srvInit.PixelFormat); err != nil { return err } if err = binary.Read(session, binary.BigEndian, &srvInit.NameLength); err != nil { return err } srvInit.NameText = make([]byte, srvInit.NameLength) if err = binary.Read(session, binary.BigEndian, &srvInit.NameText); err != nil { return err } if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: serverInit: %s", srvInit) } session.SetDesktopName(srvInit.NameText) // 如果协议是aten1,则执行特殊的逻辑 if session.ProtocolVersion() == "aten1" { session.SetWidth(800) session.SetHeight(600) // 发送像素格式消息 session.SetPixelFormat(rfb.NewPixelFormatAten()) } else { session.SetWidth(srvInit.FBWidth) session.SetHeight(srvInit.FBHeight) //告诉vnc服务端,proxy客户端支持的像素格式,发送`SetPixelFormat`消息 pixelMsg := messages.SetPixelFormat{PF: rfb.PixelFormat32bit} err = pixelMsg.Write(session) if err != nil { return err } session.SetPixelFormat(rfb.PixelFormat32bit) } // aten1协议需要再次读取扩展信息 if session.ProtocolVersion() == "aten1" { ikvm := struct { _ [8]byte IKVMVideoEnable uint8 IKVMKMEnable uint8 IKVMKickEnable uint8 VUSBEnable uint8 }{} if err = binary.Read(session, binary.BigEndian, &ikvm); err != nil { return err } } return nil } ================================================ FILE: handler/ClientVersionHandler.go ================================================ package handler import ( "context" "encoding/binary" "fmt" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" ) // ClientVersionHandler vnc握手第一步 // 1. 连接到vnc服务端后,读取其支持的rfb协议版本。 // 2. 解析版本,判断该版本proxy客户端是否支持。 // 3. 如果支持该版本,则发送支持的版本给vnc服务端 type ClientVersionHandler struct{} func (*ClientVersionHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: 执行vnc握手第一步:[Version]") } var version [rfb.ProtoVersionLength]byte if err := binary.Read(session, binary.BigEndian, &version); err != nil { return err } major, minor, err := ParseProtoVersion(version[:]) if err != nil { return err } pv := rfb.ProtoVersionUnknown if major == 3 { if minor >= 8 { pv = rfb.ProtoVersion38 } else if minor >= 3 { pv = rfb.ProtoVersion38 } } if pv == rfb.ProtoVersionUnknown { return fmt.Errorf("rfb协议握手失败; 不支持的版本 '%v'", string(version[:])) } session.SetProtocolVersion(string(version[:])) if err = binary.Write(session, binary.BigEndian, []byte(pv)); err != nil { return err } return session.Flush() } func ParseProtoVersion(pv []byte) (uint, uint, error) { var major, minor uint if len(pv) < rfb.ProtoVersionLength { return 0, 0, fmt.Errorf("协议版本的长度太短 (%v < %v)", len(pv), rfb.ProtoVersionLength) } l, err := fmt.Sscanf(string(pv), "RFB %d.%d\n", &major, &minor) if l != 2 { return 0, 0, fmt.Errorf("解析rfb协议失败") } if err != nil { return 0, 0, err } return major, minor, nil } ================================================ FILE: handler/ServerClientInitHandler.go ================================================ package handler import ( "context" "encoding/binary" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" ) // ServerClientInitHandler vnc握手步骤第三步 // 读取vnc客户端发送的是否支持共享屏幕标识 type ServerClientInitHandler struct{} func (*ServerClientInitHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debugf(context.TODO(), "[VNC客户端->Proxy服务端]: 执行vnc握手第三步:[ClientInit]") } // 读取分享屏幕标识符,proxy会无视该标识,因为通过proxy链接的vnc服务端都是默认支持分享的。 var shared uint8 if err := binary.Read(session, binary.BigEndian, &shared); err != nil { return err } return nil } ================================================ FILE: handler/ServerMessageHandler.go ================================================ package handler import ( "context" "encoding/binary" "fmt" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" ) // ServerMessageHandler vnc握手已结束,进入消息交互阶段 // 启动两个协程, // 1. 处理proxy服务端的ServerMessage,在ServerMessageCh通道的消息都转发写入到该会话中. // 2. 从会话中读取clientMessages,并判断是否支持该消息,如果支持则转发到ClientMessageCh通道中。如果不支持则关闭该会话并报错。 type ServerMessageHandler struct{} func (*ServerMessageHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debug(context.TODO(), "[VNC客户端->Proxy服务端]: vnc握手已结束,进入消息交互阶段[ServerMessageHandler]") } cfg := session.Options() var err error clientMessages := make(map[rfb.ClientMessageType]rfb.Message) for _, m := range cfg.Messages { clientMessages[rfb.ClientMessageType(m.Type())] = m } // 处理proxy服务端发送给vnc客户端的消息 go func() { //defer wg.Done() for { select { case <-session.Wait(): // 如果收到退出信号,则退出协程 return case msg := <-cfg.Input: // 收到proxy服务端消息,则转发写入到vnc客户端会话中。 if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy服务端->VNC客户端] 消息类型:%s,消息内容:%s", rfb.ServerMessageType(msg.Type()), msg.String()) } if err = msg.Write(session); err != nil { cfg.ErrorCh <- err _ = session.Close() return } } } }() // 处理vnc客户端发送给proxy服务端的消息 go func() { for { select { case <-session.Wait(): return default: // 从vnc客户端的会话中读取消息类型 var messageType rfb.ClientMessageType if err = binary.Read(session, binary.BigEndian, &messageType); err != nil { cfg.ErrorCh <- fmt.Errorf("读取vnc客户端数据失败,err:%v", err) _ = session.Close() return } // 判断vnc客户端发送的消息类型proxy服务端是否支持。 msg, ok := clientMessages[messageType] if !ok { cfg.ErrorCh <- fmt.Errorf("不支持的消息类型: %v", messageType) _ = session.Close() return } // 从会话中读取消息内容 parsedMsg, e := msg.Read(session) if e != nil { cfg.ErrorCh <- fmt.Errorf("解析消息失败,err:%v", e) _ = session.Close() return } if logger.IsDebug() { logger.Debugf(context.TODO(), "[VNC客户端->Proxy服务端] 消息类型:%s,消息内容:%s", rfb.ClientMessageType(parsedMsg.Type()), parsedMsg.String()) } cfg.Output <- parsedMsg } } }() return nil } ================================================ FILE: handler/ServerSecurityHandler.go ================================================ package handler import ( "context" "encoding/binary" "fmt" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" ) // ServerSecurityHandler vnc握手步骤第二步 // 1.发送proxy服务端支持的安全认证套件数量及类型。 // 2.读取vnc客户端支持的安全认证套件类型,判断是否支持, // 3.选择互相支持的安全认证套件进行认证,进入认证逻辑,如果认证成功则进入下一步,认证失败则报错。 type ServerSecurityHandler struct{} func (*ServerSecurityHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debugf(context.TODO(), "[VNC客户端->Proxy服务端]: 执行vnc握手第二步:[Security]") } cfg := session.Options() var secType rfb.SecurityType if session.ProtocolVersion() == rfb.ProtoVersion37 || session.ProtocolVersion() == rfb.ProtoVersion38 { if err := binary.Write(session, binary.BigEndian, uint8(len(cfg.SecurityHandlers))); err != nil { return err } for _, sType := range cfg.SecurityHandlers { if err := binary.Write(session, binary.BigEndian, sType.Type()); err != nil { return err } } } else { st := uint32(0) for _, sType := range cfg.SecurityHandlers { if uint32(sType.Type()) > st { st = uint32(sType.Type()) secType = sType.Type() } } if err := binary.Write(session, binary.BigEndian, st); err != nil { return err } } if err := session.Flush(); err != nil { return err } if session.ProtocolVersion() == rfb.ProtoVersion38 { if err := binary.Read(session, binary.BigEndian, &secType); err != nil { return err } } secTypes := make(map[rfb.SecurityType]rfb.ISecurityHandler) for _, sType := range cfg.SecurityHandlers { secTypes[sType.Type()] = sType } sType, ok := secTypes[secType] if !ok { return fmt.Errorf("security type %d not implemented", secType) } var authCode uint32 authErr := sType.Auth(session) if authErr != nil { authCode = uint32(1) } if err := binary.Write(session, binary.BigEndian, authCode); err != nil { return err } if authErr == nil { if err := session.Flush(); err != nil { return err } session.SetSecurityHandler(sType) return nil } if session.ProtocolVersion() == rfb.ProtoVersion38 { if err := binary.Write(session, binary.BigEndian, uint32(len(authErr.Error()))); err != nil { return err } if err := binary.Write(session, binary.BigEndian, []byte(authErr.Error())); err != nil { return err } if err := session.Flush(); err != nil { return err } } return authErr } ================================================ FILE: handler/ServerServerInitHandler.go ================================================ package handler import ( "context" "encoding/binary" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" ) // ServerServerInitHandler vnc握手步骤第四步 // 1. 发送proxy服务端的参数信息,屏幕宽高,像素格式,桌面名称 type ServerServerInitHandler struct{} func (*ServerServerInitHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy服务端->VNC客户端]: 执行vnc握手第四步:[ServerInit]") } if err := binary.Write(session, binary.BigEndian, session.Options().Width); err != nil { return err } if err := binary.Write(session, binary.BigEndian, session.Options().Height); err != nil { return err } if err := binary.Write(session, binary.BigEndian, session.Options().PixelFormat); err != nil { return err } desktopName := session.Options().DesktopName size := uint32(len(session.Options().DesktopName)) if size == 0 { desktopName = []byte("vprix") size = uint32(len(desktopName)) } if err := binary.Write(session, binary.BigEndian, size); err != nil { return err } if err := binary.Write(session, binary.BigEndian, desktopName); err != nil { return err } if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy服务端->VNC客户端]: ServerInit[Width:%d,Height:%d,PixelFormat:%s,DesktopName:%s]", session.Options().Width, session.Options().Height, session.Options().PixelFormat, desktopName) } return session.Flush() } ================================================ FILE: handler/ServerVersionHandler.go ================================================ package handler import ( "context" "encoding/binary" "fmt" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" ) // ServerVersionHandler vnc握手步骤第一步。 // 1. vnc客户端链接到proxy服务端后,proxy服务端发送rfb版本信息。 // 2. 发送版本信息后,接受vnc客户端返回的版本信息,进行版本匹配。 // 3. 确定版本信息是相互支持的,如果不支持,则返回错误信息,如果支持则进行下一步。 type ServerVersionHandler struct{} func (*ServerVersionHandler) Handle(session rfb.ISession) error { if logger.IsDebug() { logger.Debugf(context.TODO(), "[VNC客户端->Proxy服务端]: 执行vnc握手第一步:[Version]") } var version [rfb.ProtoVersionLength]byte if err := binary.Write(session, binary.BigEndian, []byte(rfb.ProtoVersion38)); err != nil { return err } if err := session.Flush(); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &version); err != nil { return err } major, minor, err := ParseProtoVersion(version[:]) if err != nil { return err } pv := rfb.ProtoVersionUnknown if major == 3 { if minor >= 8 { pv = rfb.ProtoVersion38 } else if minor >= 3 { pv = rfb.ProtoVersion33 } } if pv == rfb.ProtoVersionUnknown { return fmt.Errorf("rfb协议握手; 不支持的协议版本 '%v'", string(version[:])) } session.SetProtocolVersion(pv) return nil } ================================================ FILE: internal/dbuffer/buffer.go ================================================ package dbuffer import ( "io" ) // ByteBuffer provides byte buffer, which can be used for minimizing // memory allocations. // // ByteBuffer may be used with functions appending data to the given []byte // slice. See example code for details. // // Use Get for obtaining an empty byte buffer. type ByteBuffer struct { // B is a byte buffer to use in append-like workloads. // See example code for details. B []byte } // Len returns the size of the byte buffer. func (b *ByteBuffer) Len() int { return len(b.B) } // ReadFrom implements io.ReaderFrom. // // The function appends all the data read from r to b. func (b *ByteBuffer) ReadFrom(r io.Reader) (int64, error) { p := b.B nStart := int64(len(p)) nMax := int64(cap(p)) n := nStart if nMax == 0 { nMax = 64 p = make([]byte, nMax) } else { p = p[:nMax] } for { if n == nMax { nMax *= 2 bNew := make([]byte, nMax) copy(bNew, p) p = bNew } nn, err := r.Read(p[n:]) n += int64(nn) if err != nil { b.B = p[:n] n -= nStart if err == io.EOF { return n, nil } return n, err } } } // WriteTo implements io.WriterTo. func (b *ByteBuffer) WriteTo(w io.Writer) (int64, error) { n, err := w.Write(b.B) return int64(n), err } // Bytes returns b.B, i.e. all the bytes accumulated in the buffer. // // The purpose of this function is bytes.Buffer compatibility. func (b *ByteBuffer) Bytes() []byte { return b.B } // Write implements io.Writer - it appends p to ByteBuffer.B func (b *ByteBuffer) Write(p []byte) (int, error) { b.B = append(b.B, p...) return len(p), nil } // WriteByte appends the byte c to the buffer. // // The purpose of this function is bytes.Buffer compatibility. // // The function always returns nil. func (b *ByteBuffer) WriteByte(c byte) error { b.B = append(b.B, c) return nil } // WriteString appends s to ByteBuffer.B. func (b *ByteBuffer) WriteString(s string) (int, error) { b.B = append(b.B, s...) return len(s), nil } // Set sets ByteBuffer.B to p. func (b *ByteBuffer) Set(p []byte) { b.B = append(b.B[:0], p...) } // SetString sets ByteBuffer.B to s. func (b *ByteBuffer) SetString(s string) { b.B = append(b.B[:0], s...) } // String returns string representation of ByteBuffer.B. func (b *ByteBuffer) String() string { return string(b.B) } // Reset makes ByteBuffer.B empty. func (b *ByteBuffer) Reset() { b.B = b.B[:0] } // ChangeLen changes the buffer length. func (b *ByteBuffer) ChangeLen(newLen int) { if cap(b.B) < newLen { b.B = make([]byte, newLen) } else { b.B = b.B[:newLen] } } ================================================ FILE: internal/dbuffer/pool.go ================================================ package dbuffer import ( "sort" "sync" "sync/atomic" ) const ( minBitSize = 6 // 2**6=64 is a CPU cache line size steps = 20 minSize = 1 << minBitSize maxSize = 1 << (minBitSize + steps - 1) calibrateCallsThreshold = 42000 maxPercentile = 0.95 ) // BufferPool represents byte buffer pool. // // Distinct pools may be used for distinct types of byte buffers. // Properly determined byte buffer types with their own pools may help reducing // memory waste. type BufferPool struct { calls [steps]uint64 calibrating uint64 defaultSize uint64 maxSize uint64 pool sync.Pool } var defaultBufferPool BufferPool // GetByteBuffer returns an empty byte buffer from the pool. // // Got byte buffer may be returned to the pool via Put call. // This reduces the number of memory allocations required for byte buffer // management. func GetByteBuffer() *ByteBuffer { return defaultBufferPool.Get() } // Get returns new byte buffer with zero length. // // The byte buffer may be returned to the pool via Put after the use // in order to minimize GC overhead. func (p *BufferPool) Get() *ByteBuffer { v := p.pool.Get() if v != nil { return v.(*ByteBuffer) } return &ByteBuffer{ B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)), } } // ReleaseByteBuffer returns byte buffer to the pool. // // ByteBuffer.B mustn't be touched after returning it to the pool. // Otherwise data races will occur. func ReleaseByteBuffer(b *ByteBuffer) { defaultBufferPool.Put(b) } // Put releases byte buffer obtained via Get to the pool. // // The buffer mustn't be accessed after returning to the pool. func (p *BufferPool) Put(b *ByteBuffer) { idx := index(len(b.B)) if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold { p.calibrate() } maxSize := int(atomic.LoadUint64(&p.maxSize)) if maxSize == 0 || cap(b.B) <= maxSize { b.Reset() p.pool.Put(b) } } func (p *BufferPool) calibrate() { if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) { return } a := make(callSizes, 0, steps) var callsSum uint64 for i := uint64(0); i < steps; i++ { calls := atomic.SwapUint64(&p.calls[i], 0) callsSum += calls a = append(a, callSize{ calls: calls, size: minSize << i, }) } sort.Sort(a) defaultSize := a[0].size maxSize := defaultSize maxSum := uint64(float64(callsSum) * maxPercentile) callsSum = 0 for i := 0; i < steps; i++ { if callsSum > maxSum { break } callsSum += a[i].calls size := a[i].size if size > maxSize { maxSize = size } } atomic.StoreUint64(&p.defaultSize, defaultSize) atomic.StoreUint64(&p.maxSize, maxSize) atomic.StoreUint64(&p.calibrating, 0) } type callSize struct { calls uint64 size uint64 } type callSizes []callSize func (ci callSizes) Len() int { return len(ci) } func (ci callSizes) Less(i, j int) bool { return ci[i].calls > ci[j].calls } func (ci callSizes) Swap(i, j int) { ci[i], ci[j] = ci[j], ci[i] } func index(n int) int { n-- n >>= minBitSize idx := 0 for n > 0 { n >>= 1 idx++ } if idx >= steps { idx = steps - 1 } return idx } ================================================ FILE: internal/syncPool/sync_pool.go ================================================ package syncPool import "sync" type SyncPool struct { pool sync.Pool newFunc func() interface{} initFunc func(interface{}) } // NewSyncPool 创建对象池 // newFunc:创建对象的方法 // init: 对象被创建后,返回之前,调用该方法初始化对象 func NewSyncPool(newFunc func() interface{}, init func(interface{})) *SyncPool { return &SyncPool{ newFunc: newFunc, initFunc: init, pool: sync.Pool{ New: newFunc, }, } } // Get 获取对象 func (that *SyncPool) Get() interface{} { var object = that.pool.Get() if that.initFunc != nil { that.initFunc(object) } return object } // Put 把对象放回对象池 func (that *SyncPool) Put(value interface{}) { that.pool.Put(value) } ================================================ FILE: messages/clientClientCutText.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // ClientCutText 客户端发送剪切板内容到服务端 type ClientCutText struct { _ [3]byte // 填充 Length uint32 // 剪切板内容长度 Text []byte // 剪切板 } func (that *ClientCutText) Clone() rfb.Message { c := &ClientCutText{ Length: that.Length, Text: that.Text, } return c } func (that *ClientCutText) Supported(rfb.ISession) bool { return true } // String func (that *ClientCutText) String() string { return fmt.Sprintf("length: %d", that.Length) } // Type returns MessageType func (that *ClientCutText) Type() rfb.MessageType { return rfb.MessageType(rfb.ClientCutText) } // Read 从会话中解析消息内容 func (that *ClientCutText) Read(session rfb.ISession) (rfb.Message, error) { msg := &ClientCutText{} // 读取填充字节 var pad [3]byte if err := binary.Read(session, binary.BigEndian, &pad); err != nil { return nil, err } // 读取消息长度 if err := binary.Read(session, binary.BigEndian, &msg.Length); err != nil { return nil, err } // 读取指定长度的消息内容 msg.Text = make([]byte, msg.Length) if err := binary.Read(session, binary.BigEndian, &msg.Text); err != nil { return nil, err } return msg, nil } // Write 把消息按协议格式写入会话 func (that *ClientCutText) Write(session rfb.ISession) error { // 写入消息类型 if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } // 写入3给字节的填充 var pad [3]byte if err := binary.Write(session, binary.BigEndian, &pad); err != nil { return err } if uint32(len(that.Text)) > that.Length { that.Length = uint32(len(that.Text)) } // 写入剪切板内容长度 if err := binary.Write(session, binary.BigEndian, that.Length); err != nil { return err } // 写入消息内容 if err := binary.Write(session, binary.BigEndian, that.Text); err != nil { return err } return session.Flush() } ================================================ FILE: messages/clientClientFence.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // ClientFence 支持 Fence扩展的客户端发送此扩展以请求数据流的同步。 type ClientFence struct { flags uint32 length uint8 payload []byte } func (that *ClientFence) Clone() rfb.Message { c := &ClientFence{ flags: that.flags, length: that.length, payload: that.payload, } return c } func (that *ClientFence) Supported(session rfb.ISession) bool { return true } func (that *ClientFence) String() string { return fmt.Sprintf("(type=%d)", that.Type()) } func (that *ClientFence) Type() rfb.MessageType { return rfb.MessageType(rfb.ClientFence) } // 读取数据 func (that *ClientFence) Read(session rfb.ISession) (rfb.Message, error) { msg := &ClientFence{} bytes := make([]byte, 3) //c.Read(bytes) if _, err := session.Read(bytes); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.flags); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.length); err != nil { return nil, err } bytes = make([]byte, msg.length) if _, err := session.Read(bytes); err != nil { return nil, err } msg.payload = bytes return msg, nil } func (that *ClientFence) Write(session rfb.ISession) error { // 写入消息类型 if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } //写入填充 var pad [3]byte if err := binary.Write(session, binary.BigEndian, pad); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.flags); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.length); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.payload); err != nil { return err } return session.Flush() } ================================================ FILE: messages/clientEnableContinuousUpdates.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // EnableContinuousUpdates 客户端发送连续更新消息 type EnableContinuousUpdates struct { flag uint8 x uint16 y uint16 width uint16 height uint16 } func (that *EnableContinuousUpdates) Clone() rfb.Message { c := &EnableContinuousUpdates{ flag: that.flag, x: that.x, y: that.y, width: that.width, height: that.height, } return c } func (that *EnableContinuousUpdates) Supported(rfb.ISession) bool { return true } func (that *EnableContinuousUpdates) String() string { return fmt.Sprintf("(type=%d,flag=%d,x=%d,y=%d,width=%d,height=%d)", that.Type(), that.flag, that.x, that.y, that.width, that.height) } func (that *EnableContinuousUpdates) Type() rfb.MessageType { return rfb.MessageType(rfb.EnableContinuousUpdates) } // 读取数据 func (that *EnableContinuousUpdates) Read(session rfb.ISession) (rfb.Message, error) { msg := &EnableContinuousUpdates{} if err := binary.Read(session, binary.BigEndian, &msg.flag); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.x); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.y); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.width); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.height); err != nil { return nil, err } return msg, nil } func (that *EnableContinuousUpdates) Write(session rfb.ISession) error { // 写入消息类型 if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.flag); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.x); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.y); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.width); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.height); err != nil { return err } return session.Flush() } ================================================ FILE: messages/clientFramebufferUpdateRequest.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // FramebufferUpdateRequest 请求帧缓存更新消息 // incremental 通常为非 0 值,服务器只需要发有变化的图像信息。 // 当客户端丢失了缓存的帧缓冲信息,或者刚建立连接,需要完整的图像信息时, // 将 incremental 置为 0,获取全量信息。 type FramebufferUpdateRequest struct { Inc uint8 // 是否是增量请求 X, Y uint16 // 区域的起始坐标 Width, Height uint16 // 区域的宽度和高度 } func (that *FramebufferUpdateRequest) Clone() rfb.Message { c := &FramebufferUpdateRequest{ Inc: that.Inc, X: that.X, Y: that.Y, Width: that.Width, Height: that.Height, } return c } func (that *FramebufferUpdateRequest) Supported(session rfb.ISession) bool { return true } // String returns string func (that *FramebufferUpdateRequest) String() string { return fmt.Sprintf("incremental: %d, x: %d, y: %d, width: %d, height: %d", that.Inc, that.X, that.Y, that.Width, that.Height) } // Type returns MessageType func (that *FramebufferUpdateRequest) Type() rfb.MessageType { return rfb.MessageType(rfb.FramebufferUpdateRequest) } // Read 从会话中解析消息内容 func (that *FramebufferUpdateRequest) Read(session rfb.ISession) (rfb.Message, error) { msg := &FramebufferUpdateRequest{} if err := binary.Read(session, binary.BigEndian, msg); err != nil { return nil, err } return msg, nil } // Write 把消息按协议格式写入会话 func (that *FramebufferUpdateRequest) Write(session rfb.ISession) error { if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that); err != nil { return err } return session.Flush() } ================================================ FILE: messages/clientKeyEvent.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // KeyEvent 键盘按键事件 type KeyEvent struct { Down uint8 // 1 表示键位按下,0 表示弹起 _ [2]byte // 对齐字节,方便解析 Key rfb.Key // 表示具体的键位,https://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.html#keysym_encoding } func (that *KeyEvent) Clone() rfb.Message { c := &KeyEvent{ Down: that.Down, Key: that.Key, } return c } func (that *KeyEvent) Supported(session rfb.ISession) bool { return true } // String returns string func (that *KeyEvent) String() string { return fmt.Sprintf("down: %d, key: %v", that.Down, that.Key) } // Type returns MessageType func (that *KeyEvent) Type() rfb.MessageType { return rfb.MessageType(rfb.KeyEvent) } // Read 从会话中解析消息内容 func (that *KeyEvent) Read(session rfb.ISession) (rfb.Message, error) { msg := &KeyEvent{} if err := binary.Read(session, binary.BigEndian, msg); err != nil { return nil, err } return msg, nil } // Write 把消息按协议格式写入会话 func (that *KeyEvent) Write(session rfb.ISession) error { if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that); err != nil { return err } return session.Flush() } ================================================ FILE: messages/clientPointerEvent.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // PointerEvent 鼠标事件 type PointerEvent struct { Mask uint8 //8 位掩码,表示键位状态,1为按下,0为弹起 X, Y uint16 // 当前 X,Y 坐标 } func (that *PointerEvent) Clone() rfb.Message { c := &PointerEvent{ Mask: that.Mask, X: that.X, Y: that.Y, } return c } func (that *PointerEvent) Supported(session rfb.ISession) bool { return true } // String returns string func (that *PointerEvent) String() string { return fmt.Sprintf("mask %d, x: %d, y: %d", that.Mask, that.X, that.Y) } // Type returns MessageType func (that *PointerEvent) Type() rfb.MessageType { return rfb.MessageType(rfb.PointerEvent) } // Read 从会话中解析消息内容 func (that *PointerEvent) Read(session rfb.ISession) (rfb.Message, error) { msg := &PointerEvent{} if err := binary.Read(session, binary.BigEndian, msg); err != nil { return nil, err } return msg, nil } // Write 把消息按协议格式写入会话 func (that *PointerEvent) Write(session rfb.ISession) error { if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that); err != nil { return err } return session.Flush() } ================================================ FILE: messages/clientQEMUExtKeyEvent.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) type QEMUExtKeyEvent struct { SubMessageType uint8 // submessage type DownFlag uint16 // down-flag KeySym rfb.Key // key symbol KeyCode uint32 // scan code } func (that *QEMUExtKeyEvent) Clone() rfb.Message { c := &QEMUExtKeyEvent{ SubMessageType: that.SubMessageType, DownFlag: that.DownFlag, KeySym: that.KeySym, KeyCode: that.KeyCode, } return c } func (that *QEMUExtKeyEvent) Supported(session rfb.ISession) bool { return true } func (that *QEMUExtKeyEvent) Type() rfb.MessageType { return rfb.MessageType(rfb.QEMUExtendedKeyEvent) } func (that *QEMUExtKeyEvent) String() string { return fmt.Sprintf("SubMessageType=%d,DownFlag=%d,KeySym=%d,KeyCode=%d", that.SubMessageType, that.DownFlag, that.KeySym, that.KeyCode) } func (that *QEMUExtKeyEvent) Read(session rfb.ISession) (rfb.Message, error) { msg := &QEMUExtKeyEvent{} if err := binary.Read(session, binary.BigEndian, msg); err != nil { return nil, err } return msg, nil } func (that *QEMUExtKeyEvent) Write(session rfb.ISession) error { if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that); err != nil { return err } return nil } ================================================ FILE: messages/clientSetDesktopSize.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/internal/dbuffer" "github.com/vprix/vncproxy/rfb" ) // SetDesktopSize 客户端发起设置桌面大小 type SetDesktopSize struct { buff *dbuffer.ByteBuffer } func (that *SetDesktopSize) Clone() rfb.Message { c := &SetDesktopSize{ buff: dbuffer.GetByteBuffer(), } _, _ = c.buff.Write(that.buff.Bytes()) return c } func (that *SetDesktopSize) Supported(rfb.ISession) bool { return true } func (that *SetDesktopSize) String() string { return fmt.Sprintf("(type=%d)", that.Type()) } func (that *SetDesktopSize) Type() rfb.MessageType { return rfb.MessageType(rfb.SetDesktopSize) } // 读取数据 func (that *SetDesktopSize) Read(session rfb.ISession) (rfb.Message, error) { msg := &SetDesktopSize{buff: dbuffer.GetByteBuffer()} pad := make([]byte, 1) if _, err := session.Read(pad); err != nil { return nil, err } var width uint16 _, _ = msg.buff.Write(pad) if err := binary.Read(session, binary.BigEndian, &width); err != nil { return nil, err } if err := binary.Write(msg.buff, binary.BigEndian, width); err != nil { return nil, err } var height uint16 if err := binary.Read(session, binary.BigEndian, &height); err != nil { return nil, err } if err := binary.Write(msg.buff, binary.BigEndian, height); err != nil { return nil, err } var numberOfScreens uint8 if err := binary.Read(session, binary.BigEndian, &numberOfScreens); err != nil { return nil, err } if err := binary.Write(msg.buff, binary.BigEndian, numberOfScreens); err != nil { return nil, err } pad = make([]byte, 1) if err := binary.Read(session, binary.BigEndian, &pad); err != nil { return nil, err } _, _ = msg.buff.Write(pad) for i := 0; i < int(numberOfScreens); i++ { b, err := that.readExtendedDesktopSize(session) if err != nil { return nil, err } _, _ = msg.buff.Write(b) } return msg, nil } func (that *SetDesktopSize) Write(session rfb.ISession) error { // 写入消息类型 if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } _, err := session.Write(that.buff.Bytes()) if err != nil { return err } dbuffer.ReleaseByteBuffer(that.buff) that.buff = nil return session.Flush() } // No. of bytes Type Description // 4 U32 id // 2 U16 x-position // 2 U16 y-position // 2 U16 width // 2 U16 height // 4 U32 flags func (that *SetDesktopSize) readExtendedDesktopSize(session rfb.ISession) ([]byte, error) { desktopSizeBuf := make([]byte, 16) if err := binary.Read(session, binary.BigEndian, &desktopSizeBuf); err != nil { return nil, err } return desktopSizeBuf, nil } ================================================ FILE: messages/clientSetEncodings.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/gogf/gf/v2/text/gstr" "github.com/vprix/vncproxy/rfb" ) // SetEncodings 设置编码类型消息 type SetEncodings struct { _ [1]byte // padding EncNum uint16 // number-of-encodings Encodings []rfb.EncodingType } func (that *SetEncodings) Clone() rfb.Message { c := &SetEncodings{ EncNum: that.EncNum, Encodings: that.Encodings, } return c } func (that *SetEncodings) Supported(_ rfb.ISession) bool { return true } // String return string func (that *SetEncodings) String() string { s := fmt.Sprintf("encNum: %d, encodings[]: ", that.EncNum) var s1 []string for _, e := range that.Encodings { s1 = append(s1, fmt.Sprintf("%s", e)) } return s + gstr.Implode(",", s1) } // Type returns MessageType func (that *SetEncodings) Type() rfb.MessageType { return rfb.MessageType(rfb.SetEncodings) } // Read 从会话中解析消息内容 func (that *SetEncodings) Read(session rfb.ISession) (rfb.Message, error) { msg := &SetEncodings{} //读取一个字节的填充数据 var pad [1]byte if err := binary.Read(session, binary.BigEndian, &pad); err != nil { return nil, err } //读取编码格式数量 if err := binary.Read(session, binary.BigEndian, &msg.EncNum); err != nil { return nil, err } var enc rfb.EncodingType //读取指定数据量的编码信息 for i := uint16(0); i < msg.EncNum; i++ { if err := binary.Read(session, binary.BigEndian, &enc); err != nil { return nil, err } msg.Encodings = append(msg.Encodings, enc) } if err := session.SetEncodings(msg.Encodings); err != nil { return nil, err } return msg, nil } // Write 把消息按协议格式写入会话 func (that *SetEncodings) Write(session rfb.ISession) error { // 写入消息类型 if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } // 写入一个字节的填充数据 var pad [1]byte if err := binary.Write(session, binary.BigEndian, pad); err != nil { return err } // 写入当前支持的编码类型的数量 if uint16(len(that.Encodings)) > that.EncNum { that.EncNum = uint16(len(that.Encodings)) } if err := binary.Write(session, binary.BigEndian, that.EncNum); err != nil { return err } // 写入当前支持的编码类型的列表 for _, enc := range that.Encodings { if err := binary.Write(session, binary.BigEndian, enc); err != nil { return err } } return session.Flush() } ================================================ FILE: messages/clientSetPixelFormat.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // SetPixelFormat 设置像素格式 type SetPixelFormat struct { _ [3]byte // 填充 PF rfb.PixelFormat // 像素格式 } func (that *SetPixelFormat) Clone() rfb.Message { c := &SetPixelFormat{ PF: that.PF, } return c } func (that *SetPixelFormat) Supported(session rfb.ISession) bool { return true } // String returns string func (that *SetPixelFormat) String() string { return fmt.Sprintf("%s", that.PF) } // Type returns MessageType func (that *SetPixelFormat) Type() rfb.MessageType { return rfb.MessageType(rfb.SetPixelFormat) } // Write 写入像素格式 func (that *SetPixelFormat) Write(session rfb.ISession) error { if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that); err != nil { return err } pf := session.Options().PixelFormat // Invalidate the color map. if pf.TrueColor != 0 { session.SetColorMap(rfb.ColorMap{}) } return session.Flush() } // Read 从链接中读取像素格式到当前对象 func (that *SetPixelFormat) Read(session rfb.ISession) (rfb.Message, error) { msg := &SetPixelFormat{} if err := binary.Read(session, binary.BigEndian, msg); err != nil { return nil, err } return msg, nil } ================================================ FILE: messages/default_message.go ================================================ package messages import "github.com/vprix/vncproxy/rfb" var ( // DefaultClientMessage 默认client支持的消息 DefaultClientMessage = []rfb.Message{ &SetPixelFormat{}, &SetEncodings{}, &FramebufferUpdateRequest{}, &KeyEvent{}, &PointerEvent{}, &ClientCutText{}, &ClientFence{}, &SetDesktopSize{}, &EnableContinuousUpdates{}, } // DefaultServerMessages 默认server支持的消息 DefaultServerMessages = []rfb.Message{ &FramebufferUpdate{}, &SetColorMapEntries{}, &Bell{}, &ServerCutText{}, &EndOfContinuousUpdates{}, &ServerFence{}, } ) ================================================ FILE: messages/serverBell.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // Bell 响铃 type Bell struct{} func (that *Bell) Clone() rfb.Message { return &Bell{} } func (that *Bell) Supported(session rfb.ISession) bool { return true } // String return string func (that *Bell) String() string { return fmt.Sprintf("bell") } // Type 消息类型 func (that *Bell) Type() rfb.MessageType { return rfb.MessageType(rfb.Bell) } // Read 响铃消息只有消息类型,没有数据 func (that *Bell) Read(session rfb.ISession) (rfb.Message, error) { return &Bell{}, nil } // Write 写入响应消息类型 func (that *Bell) Write(session rfb.ISession) error { if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } return session.Flush() } ================================================ FILE: messages/serverEndOfContinuousUpdates.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // EndOfContinuousUpdates Bell 结束连续更新 type EndOfContinuousUpdates struct{} func (that *EndOfContinuousUpdates) Clone() rfb.Message { return &EndOfContinuousUpdates{} } func (that *EndOfContinuousUpdates) Supported(session rfb.ISession) bool { return true } // String return string func (that *EndOfContinuousUpdates) String() string { return fmt.Sprintf("EndOfContinuousUpdates") } // Type 消息类型 func (that *EndOfContinuousUpdates) Type() rfb.MessageType { return rfb.MessageType(rfb.EndOfContinuousUpdates) } // Read 响铃消息只有消息类型,没有数据 func (that *EndOfContinuousUpdates) Read(session rfb.ISession) (rfb.Message, error) { return &EndOfContinuousUpdates{}, nil } // Write 写入响应消息类型 func (that *EndOfContinuousUpdates) Write(session rfb.ISession) error { if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } return session.Flush() } ================================================ FILE: messages/serverFramebufferUpdate.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/rfb" "golang.org/x/net/context" ) // FramebufferUpdate 帧缓冲更新 type FramebufferUpdate struct { _ [1]byte // 填充 NumRect uint16 // 多少个像素数据的矩形 Rects []*rfb.Rectangle // 像素数据的矩形列表 } func (that *FramebufferUpdate) String() string { return fmt.Sprintf("rects %d rectangle[]: { %v }", that.NumRect, that.Rects) } func (that *FramebufferUpdate) Supported(rfb.ISession) bool { return true } func (that *FramebufferUpdate) Type() rfb.MessageType { return rfb.MessageType(rfb.FramebufferUpdate) } // 读取帧数据 func (that *FramebufferUpdate) Read(session rfb.ISession) (rfb.Message, error) { msg := &FramebufferUpdate{} var pad [1]byte if err := binary.Read(session, binary.BigEndian, &pad); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.NumRect); err != nil { return nil, err } if logger.IsDebug() { logger.Debugf(context.TODO(), "FramebufferUpdate->读取帧数据有 %d 个矩形-------", msg.NumRect) } for i := uint16(0); i < msg.NumRect; i++ { rect := rfb.NewRectangle() if logger.IsDebug() { logger.Debugf(context.TODO(), "开始读取第 %d 个矩形", i) } if err := rect.Read(session); err != nil { return nil, err } // 如果服务器告诉客户端这是最后一个rect,则停止解析 if rect.EncType == rfb.EncLastRectPseudo { if logger.IsDebug() { logger.Debugf(context.TODO(), "读取第 %d 个矩形成功,但是是最后一帧:EncLastRectPseudo", i) } msg.Rects = append(msg.Rects, rect) break } //if rect.EncType == rfb.EncDesktopSizePseudo { // session.ResetAllEncodings() //} if logger.IsDebug() { logger.Debugf(context.TODO(), "结束读取第 %d 个矩形,宽高:(%dx%d) 编码格式:%s", i, rect.Width, rect.Height, rect.EncType) } msg.Rects = append(msg.Rects, rect) } return msg, nil } // 写入帧数据 func (that *FramebufferUpdate) Write(session rfb.ISession) error { // 写入消息类型 if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } // 填充字节 var pad [1]byte if err := binary.Write(session, binary.BigEndian, pad); err != nil { return err } // 写入矩形数量 if err := binary.Write(session, binary.BigEndian, that.NumRect); err != nil { return err } // 编码后写入 for _, rect := range that.Rects { if err := rect.Write(session); err != nil { return err } } return session.Flush() } func (that *FramebufferUpdate) Clone() rfb.Message { c := &FramebufferUpdate{ NumRect: that.NumRect, } for _, rect := range that.Rects { c.Rects = append(c.Rects, rect.Clone()) } return c } ================================================ FILE: messages/serverInit.go ================================================ package messages import ( "fmt" "github.com/vprix/vncproxy/rfb" ) // ServerInit 握手的时候服务端初始化消息 type ServerInit struct { FBWidth uint16 FBHeight uint16 PixelFormat rfb.PixelFormat NameLength uint32 NameText []byte } func (srvInit ServerInit) String() string { return fmt.Sprintf("ServerInit->Width: %d, Height: %d, PixelFormat: %s, NameLength: %d, MameText: %s", srvInit.FBWidth, srvInit.FBHeight, srvInit.PixelFormat, srvInit.NameLength, srvInit.NameText) } ================================================ FILE: messages/serverServerCutText.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // ServerCutText 服务端剪切板发送到客户端 type ServerCutText struct { _ [3]byte // 填充 Length uint32 // 剪切板内容长度 Text []byte // 剪切板内容 } func (that *ServerCutText) Clone() rfb.Message { return &ServerCutText{ Length: that.Length, Text: that.Text, } } func (that *ServerCutText) Supported(session rfb.ISession) bool { return true } // String returns string func (that *ServerCutText) String() string { return fmt.Sprintf("lenght: %d", that.Length) } func (that *ServerCutText) Type() rfb.MessageType { return rfb.MessageType(rfb.ServerCutText) } // 读取消息数据 func (that *ServerCutText) Read(session rfb.ISession) (rfb.Message, error) { // 每次读取以后生成的都是一个新的对象 msg := &ServerCutText{} var pad [3]byte if err := binary.Read(session, binary.BigEndian, &pad); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.Length); err != nil { return nil, err } msg.Text = make([]byte, msg.Length) if err := binary.Read(session, binary.BigEndian, &msg.Text); err != nil { return nil, err } return msg, nil } func (that *ServerCutText) Write(session rfb.ISession) error { // 写入消息类型 if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } //写入填充 var pad [3]byte if err := binary.Write(session, binary.BigEndian, pad); err != nil { return err } if that.Length < uint32(len(that.Text)) { that.Length = uint32(len(that.Text)) } if err := binary.Write(session, binary.BigEndian, that.Length); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.Text); err != nil { return err } return session.Flush() } ================================================ FILE: messages/serverServerFence.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // ServerFence 支持 Fence扩展的服务器发送此扩展以请求数据流的同步。 type ServerFence struct { flags uint32 length uint8 payload []byte } func (that *ServerFence) Clone() rfb.Message { c := &ServerFence{ flags: that.flags, length: that.length, payload: that.payload, } return c } func (that *ServerFence) Supported(session rfb.ISession) bool { return true } func (that *ServerFence) String() string { return fmt.Sprintf("type=%d", that.Type()) } func (that *ServerFence) Type() rfb.MessageType { return rfb.MessageType(rfb.ServerFence) } // 读取数据 func (that *ServerFence) Read(session rfb.ISession) (rfb.Message, error) { msg := &ServerFence{} bytes := make([]byte, 3) //c.Read(bytes) if _, err := session.Read(bytes); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.flags); err != nil { return nil, err } if err := binary.Read(session, binary.BigEndian, &msg.length); err != nil { return nil, err } bytes = make([]byte, msg.length) if _, err := session.Read(bytes); err != nil { return nil, err } msg.payload = bytes return msg, nil } func (that *ServerFence) Write(session rfb.ISession) error { // 写入消息类型 if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } //写入填充 var pad [3]byte if err := binary.Write(session, binary.BigEndian, pad); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.flags); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.length); err != nil { return err } if err := binary.Write(session, binary.BigEndian, that.payload); err != nil { return err } return session.Flush() } ================================================ FILE: messages/serverSetColorMapEntries.go ================================================ package messages import ( "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) // SetColorMapEntries 设置颜色表的内容 // See RFC 6143 Section 7.6.2 type SetColorMapEntries struct { _ [1]byte //填充 FirstColor uint16 // 颜色的起始位置, ColorsNum uint16 // 颜色的数目 Colors []rfb.Color } func (that *SetColorMapEntries) Clone() rfb.Message { c := &SetColorMapEntries{ FirstColor: that.FirstColor, ColorsNum: that.ColorsNum, Colors: that.Colors, } return c } func (that *SetColorMapEntries) Supported(session rfb.ISession) bool { return true } // String returns string func (that *SetColorMapEntries) String() string { return fmt.Sprintf("first color: %d, numcolors: %d, colors[]: { %v }", that.FirstColor, that.ColorsNum, that.Colors) } // Type returns MessageType func (*SetColorMapEntries) Type() rfb.MessageType { return rfb.MessageType(rfb.SetColorMapEntries) } func (that *SetColorMapEntries) Read(session rfb.ISession) (rfb.Message, error) { msg := &SetColorMapEntries{} // 先读取一个字节的填充 var pad [1]byte if err := binary.Read(session, binary.BigEndian, &pad); err != nil { return nil, err } // 单个消息不必指定整个色彩映射表,而可能能只更新几个条目。 //例如,如果我想更新条目 5 和 6,我会在FirstColor中指定,后跟两组 RGB 值。first-colour:5 number-of-colours:2 if err := binary.Read(session, binary.BigEndian, &msg.FirstColor); err != nil { return nil, err } // 获取此次要更新几个颜色 if err := binary.Read(session, binary.BigEndian, &msg.ColorsNum); err != nil { return nil, err } msg.Colors = make([]rfb.Color, msg.ColorsNum) colorMap := session.Options().ColorMap //读取指定的颜色数据 for i := uint16(0); i < msg.ColorsNum; i++ { color := &msg.Colors[i] err := color.Read(session) if err != nil { return nil, err } colorMap[msg.FirstColor+i] = *color } session.SetColorMap(colorMap) return msg, nil } func (that *SetColorMapEntries) Write(session rfb.ISession) error { // 写入消息类型 if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil { return err } // 填充 var pad [1]byte if err := binary.Write(session, binary.BigEndian, &pad); err != nil { return err } // 首个颜色 if err := binary.Write(session, binary.BigEndian, that.FirstColor); err != nil { return err } // 要更新的颜色数目 if that.ColorsNum < uint16(len(that.Colors)) { that.ColorsNum = uint16(len(that.Colors)) } if err := binary.Write(session, binary.BigEndian, that.ColorsNum); err != nil { return err } // 颜色数据 for i := 0; i < len(that.Colors); i++ { color := that.Colors[i] if err := binary.Write(session, binary.BigEndian, color); err != nil { return err } } return session.Flush() } ================================================ FILE: rfb/color_map.go ================================================ package rfb import "encoding/binary" // ColorMap 颜色地图 type ColorMap [256]Color // Color 表示颜色地图中的一个颜色。 type Color struct { pf *PixelFormat cm *ColorMap cmIndex uint32 // Only valid if pf.TrueColor is false. R, G, B uint16 } // 写入颜色数据 func (that *Color) Write(session ISession) error { var err error pf := session.Options().PixelFormat order := pf.Order() pixel := that.cmIndex if that.pf.TrueColor != 0 { pixel = uint32(that.R) << pf.RedShift pixel |= uint32(that.G) << pf.GreenShift pixel |= uint32(that.B) << pf.BlueShift } switch pf.BPP { case 8: err = binary.Write(session, order, byte(pixel)) case 16: err = binary.Write(session, order, uint16(pixel)) case 32: err = binary.Write(session, order, uint32(pixel)) } return err } // 从链接中读取颜色偏移量 func (that *Color) Read(session ISession) error { order := that.pf.Order() var pixel uint32 switch that.pf.BPP { case 8: var px uint8 if err := binary.Read(session, order, &px); err != nil { return err } pixel = uint32(px) case 16: var px uint16 if err := binary.Read(session, order, &px); err != nil { return err } pixel = uint32(px) case 32: var px uint32 if err := binary.Read(session, order, &px); err != nil { return err } pixel = px } if that.pf.TrueColor != 0 { that.R = uint16((pixel >> that.pf.RedShift) & uint32(that.pf.RedMax)) that.G = uint16((pixel >> that.pf.GreenShift) & uint32(that.pf.GreenMax)) that.B = uint16((pixel >> that.pf.BlueShift) & uint32(that.pf.BlueMax)) } else { *that = that.cm[pixel] that.cmIndex = pixel } return nil } ================================================ FILE: rfb/desktop.go ================================================ package rfb // //type Desktop struct { // desktopName []byte // 桌面名称 // fbHeight uint16 // 缓冲帧高度 // fbWidth uint16 // 缓冲帧宽度 // colorMap ColorMap // 颜色地图 // pixelFormat PixelFormat // 像素格式 //} // //// PixelFormat 获取像素格式 //func (that *Desktop) PixelFormat() PixelFormat { // return that.pixelFormat //} // //// SetPixelFormat 设置像素格式 //func (that *Desktop) SetPixelFormat(pf PixelFormat) { // that.pixelFormat = pf //} // //// ColorMap 获取颜色地图 //func (that *Desktop) ColorMap() ColorMap { // return that.colorMap //} // //// SetColorMap 设置颜色地图 //func (that *Desktop) SetColorMap(cm ColorMap) { // that.colorMap = cm //} // //// Width 获取桌面宽度 //func (that *Desktop) Width() uint16 { // return that.fbWidth //} // //// SetWidth 设置桌面宽度 //func (that *Desktop) SetWidth(width uint16) { // that.fbWidth = width //} // //// Height 获取桌面高度 //func (that *Desktop) Height() uint16 { // return that.fbHeight //} // //// SetHeight 设置桌面高度 //func (that *Desktop) SetHeight(height uint16) { // that.fbHeight = height //} // //// DesktopName 获取该会话的桌面名称 //func (that *Desktop) DesktopName() []byte { // return that.desktopName //} // //// SetDesktopName 设置桌面名称 //func (that *Desktop) SetDesktopName(name []byte) { // that.desktopName = name //} ================================================ FILE: rfb/encoding.go ================================================ package rfb // IEncoding vnc像素数据编码格式的接口定义 type IEncoding interface { Type() EncodingType Supported(ISession) bool Clone(...bool) IEncoding Read(ISession, *Rectangle) error Write(ISession, *Rectangle) error } ================================================ FILE: rfb/encodingtype.go ================================================ package rfb type EncodingType int32 //go:generate stringer -type=EncodingType // https://www.iana.org/assignments/rfb/rfb.xml#rfb-4 const ( EncRaw EncodingType = 0 // 不编码,原始的格式 EncCopyRect EncodingType = 1 //从帧缓冲复制 EncRRE EncodingType = 2 // 二维游程编码 EncCoRRE EncodingType = 4 // 二维游程编码的变体 EncHexTile EncodingType = 5 // RRE 的变种,图块游程编码 EncZlib EncodingType = 6 // zlib压缩 EncTight EncodingType = 7 // tightvnc项目设置的编码 EncZlibHex EncodingType = 8 // zlib压缩Hextile EncUltra1 EncodingType = 9 EncUltra2 EncodingType = 10 EncTRLE EncodingType = 15 //图块游程编码 EncZRLE EncodingType = 16 //zlib 压缩的游程编码 EncH264 EncodingType = 20 EncJPEG EncodingType = 21 EncJRLE EncodingType = 22 EncAtenAST2100 EncodingType = 87 EncAtenASTJPEG EncodingType = 88 EncAtenHermon EncodingType = 89 EncAtenYarkon EncodingType = 90 EncAtenPilot3 EncodingType = 91 EncJPEGQualityLevelPseudo10 EncodingType = -23 EncJPEGQualityLevelPseudo9 EncodingType = -24 EncJPEGQualityLevelPseudo8 EncodingType = -25 EncJPEGQualityLevelPseudo7 EncodingType = -26 EncJPEGQualityLevelPseudo6 EncodingType = -27 EncJPEGQualityLevelPseudo5 EncodingType = -28 EncJPEGQualityLevelPseudo4 EncodingType = -29 EncJPEGQualityLevelPseudo3 EncodingType = -30 EncJPEGQualityLevelPseudo2 EncodingType = -31 EncJPEGQualityLevelPseudo1 EncodingType = -32 EncDesktopSizePseudo EncodingType = -223 //桌面分辨率伪编码 EncLastRectPseudo EncodingType = -224 // 表示是最后一个矩形的伪编码 EncPointerPosPseudo EncodingType = -232 EncCursorPseudo EncodingType = -239 //光标掩码 EncXCursorPseudo EncodingType = -240 EncCompressionLevel10 EncodingType = -247 EncCompressionLevel9 EncodingType = -248 EncCompressionLevel8 EncodingType = -249 EncCompressionLevel7 EncodingType = -250 EncCompressionLevel6 EncodingType = -251 EncCompressionLevel5 EncodingType = -252 EncCompressionLevel4 EncodingType = -253 EncCompressionLevel3 EncodingType = -254 EncCompressionLevel2 EncodingType = -255 EncCompressionLevel1 EncodingType = -256 EncQEMUPointerMotionChangePseudo EncodingType = -257 EncQEMUExtendedKeyEventPseudo EncodingType = -258 EncTightPng EncodingType = -260 EncLedStatePseudo EncodingType = -261 EncDesktopNamePseudo EncodingType = -307 EncExtendedDesktopSizePseudo EncodingType = -308 EncXvpPseudo EncodingType = -309 EncClientRedirect EncodingType = -311 EncFencePseudo EncodingType = -312 EncContinuousUpdatesPseudo EncodingType = -313 EncCursorWithAlphaPseudo EncodingType = -314 EncExtendedClipboardPseudo EncodingType = -1063131698 //C0A1E5CE EncTightPNGBase64 EncodingType = 21 + 0x574d5600 EncTightDiffComp EncodingType = 22 + 0x574d5600 EncVMWDefineCursor EncodingType = 100 + 0x574d5600 EncVMWCursorState EncodingType = 101 + 0x574d5600 EncVMWCursorPosition EncodingType = 102 + 0x574d5600 EncVMWTypematicInfo EncodingType = 103 + 0x574d5600 EncVMWLEDState EncodingType = 104 + 0x574d5600 EncVMWServerPush2 EncodingType = 123 + 0x574d5600 EncVMWServerCaps EncodingType = 122 + 0x574d5600 EncVMWFrameStamp EncodingType = 124 + 0x574d5600 EncOffscreenCopyRect EncodingType = 126 + 0x574d5600 ) ================================================ FILE: rfb/encodingtype_string.go ================================================ // Code generated by "stringer -type=EncodingType"; DO NOT EDIT. package rfb import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[EncRaw-0] _ = x[EncCopyRect-1] _ = x[EncRRE-2] _ = x[EncCoRRE-4] _ = x[EncHexTile-5] _ = x[EncZlib-6] _ = x[EncTight-7] _ = x[EncZlibHex-8] _ = x[EncUltra1-9] _ = x[EncUltra2-10] _ = x[EncTRLE-15] _ = x[EncZRLE-16] _ = x[EncH264-20] _ = x[EncJPEG-21] _ = x[EncJRLE-22] _ = x[EncAtenAST2100-87] _ = x[EncAtenASTJPEG-88] _ = x[EncAtenHermon-89] _ = x[EncAtenYarkon-90] _ = x[EncAtenPilot3-91] _ = x[EncJPEGQualityLevelPseudo10 - -23] _ = x[EncJPEGQualityLevelPseudo9 - -24] _ = x[EncJPEGQualityLevelPseudo8 - -25] _ = x[EncJPEGQualityLevelPseudo7 - -26] _ = x[EncJPEGQualityLevelPseudo6 - -27] _ = x[EncJPEGQualityLevelPseudo5 - -28] _ = x[EncJPEGQualityLevelPseudo4 - -29] _ = x[EncJPEGQualityLevelPseudo3 - -30] _ = x[EncJPEGQualityLevelPseudo2 - -31] _ = x[EncJPEGQualityLevelPseudo1 - -32] _ = x[EncDesktopSizePseudo - -223] _ = x[EncLastRectPseudo - -224] _ = x[EncPointerPosPseudo - -232] _ = x[EncCursorPseudo - -239] _ = x[EncXCursorPseudo - -240] _ = x[EncCompressionLevel10 - -247] _ = x[EncCompressionLevel9 - -248] _ = x[EncCompressionLevel8 - -249] _ = x[EncCompressionLevel7 - -250] _ = x[EncCompressionLevel6 - -251] _ = x[EncCompressionLevel5 - -252] _ = x[EncCompressionLevel4 - -253] _ = x[EncCompressionLevel3 - -254] _ = x[EncCompressionLevel2 - -255] _ = x[EncCompressionLevel1 - -256] _ = x[EncQEMUPointerMotionChangePseudo - -257] _ = x[EncQEMUExtendedKeyEventPseudo - -258] _ = x[EncTightPng - -260] _ = x[EncLedStatePseudo - -261] _ = x[EncDesktopNamePseudo - -307] _ = x[EncExtendedDesktopSizePseudo - -308] _ = x[EncXvpPseudo - -309] _ = x[EncClientRedirect - -311] _ = x[EncFencePseudo - -312] _ = x[EncContinuousUpdatesPseudo - -313] _ = x[EncCursorWithAlphaPseudo - -314] _ = x[EncExtendedClipboardPseudo - -1063131698] _ = x[EncTightPNGBase64-1464686101] _ = x[EncTightDiffComp-1464686102] _ = x[EncVMWDefineCursor-1464686180] _ = x[EncVMWCursorState-1464686181] _ = x[EncVMWCursorPosition-1464686182] _ = x[EncVMWTypematicInfo-1464686183] _ = x[EncVMWLEDState-1464686184] _ = x[EncVMWServerPush2-1464686203] _ = x[EncVMWServerCaps-1464686202] _ = x[EncVMWFrameStamp-1464686204] _ = x[EncOffscreenCopyRect-1464686206] } const _EncodingType_name = "EncExtendedClipboardPseudoEncCursorWithAlphaPseudoEncContinuousUpdatesPseudoEncFencePseudoEncClientRedirectEncXvpPseudoEncExtendedDesktopSizePseudoEncDesktopNamePseudoEncLedStatePseudoEncTightPngEncQEMUExtendedKeyEventPseudoEncQEMUPointerMotionChangePseudoEncCompressionLevel1EncCompressionLevel2EncCompressionLevel3EncCompressionLevel4EncCompressionLevel5EncCompressionLevel6EncCompressionLevel7EncCompressionLevel8EncCompressionLevel9EncCompressionLevel10EncXCursorPseudoEncCursorPseudoEncPointerPosPseudoEncLastRectPseudoEncDesktopSizePseudoEncJPEGQualityLevelPseudo1EncJPEGQualityLevelPseudo2EncJPEGQualityLevelPseudo3EncJPEGQualityLevelPseudo4EncJPEGQualityLevelPseudo5EncJPEGQualityLevelPseudo6EncJPEGQualityLevelPseudo7EncJPEGQualityLevelPseudo8EncJPEGQualityLevelPseudo9EncJPEGQualityLevelPseudo10EncRawEncCopyRectEncRREEncCoRREEncHexTileEncZlibEncTightEncZlibHexEncUltra1EncUltra2EncTRLEEncZRLEEncH264EncJPEGEncJRLEEncAtenAST2100EncAtenASTJPEGEncAtenHermonEncAtenYarkonEncAtenPilot3EncTightPNGBase64EncTightDiffCompEncVMWDefineCursorEncVMWCursorStateEncVMWCursorPositionEncVMWTypematicInfoEncVMWLEDStateEncVMWServerCapsEncVMWServerPush2EncVMWFrameStampEncOffscreenCopyRect" var _EncodingType_map = map[EncodingType]string{ -1063131698: _EncodingType_name[0:26], -314: _EncodingType_name[26:50], -313: _EncodingType_name[50:76], -312: _EncodingType_name[76:90], -311: _EncodingType_name[90:107], -309: _EncodingType_name[107:119], -308: _EncodingType_name[119:147], -307: _EncodingType_name[147:167], -261: _EncodingType_name[167:184], -260: _EncodingType_name[184:195], -258: _EncodingType_name[195:224], -257: _EncodingType_name[224:256], -256: _EncodingType_name[256:276], -255: _EncodingType_name[276:296], -254: _EncodingType_name[296:316], -253: _EncodingType_name[316:336], -252: _EncodingType_name[336:356], -251: _EncodingType_name[356:376], -250: _EncodingType_name[376:396], -249: _EncodingType_name[396:416], -248: _EncodingType_name[416:436], -247: _EncodingType_name[436:457], -240: _EncodingType_name[457:473], -239: _EncodingType_name[473:488], -232: _EncodingType_name[488:507], -224: _EncodingType_name[507:524], -223: _EncodingType_name[524:544], -32: _EncodingType_name[544:570], -31: _EncodingType_name[570:596], -30: _EncodingType_name[596:622], -29: _EncodingType_name[622:648], -28: _EncodingType_name[648:674], -27: _EncodingType_name[674:700], -26: _EncodingType_name[700:726], -25: _EncodingType_name[726:752], -24: _EncodingType_name[752:778], -23: _EncodingType_name[778:805], 0: _EncodingType_name[805:811], 1: _EncodingType_name[811:822], 2: _EncodingType_name[822:828], 4: _EncodingType_name[828:836], 5: _EncodingType_name[836:846], 6: _EncodingType_name[846:853], 7: _EncodingType_name[853:861], 8: _EncodingType_name[861:871], 9: _EncodingType_name[871:880], 10: _EncodingType_name[880:889], 15: _EncodingType_name[889:896], 16: _EncodingType_name[896:903], 20: _EncodingType_name[903:910], 21: _EncodingType_name[910:917], 22: _EncodingType_name[917:924], 87: _EncodingType_name[924:938], 88: _EncodingType_name[938:952], 89: _EncodingType_name[952:965], 90: _EncodingType_name[965:978], 91: _EncodingType_name[978:991], 1464686101: _EncodingType_name[991:1008], 1464686102: _EncodingType_name[1008:1024], 1464686180: _EncodingType_name[1024:1042], 1464686181: _EncodingType_name[1042:1059], 1464686182: _EncodingType_name[1059:1079], 1464686183: _EncodingType_name[1079:1098], 1464686184: _EncodingType_name[1098:1112], 1464686202: _EncodingType_name[1112:1128], 1464686203: _EncodingType_name[1128:1145], 1464686204: _EncodingType_name[1145:1161], 1464686206: _EncodingType_name[1161:1181], } func (i EncodingType) String() string { if str, ok := _EncodingType_map[i]; ok { return str } return "EncodingType(" + strconv.FormatInt(int64(i), 10) + ")" } ================================================ FILE: rfb/handler.go ================================================ package rfb type IHandler interface { Handle(session ISession) error } // ProtoVersionLength rfb协议长度 const ProtoVersionLength = 12 const ( // ProtoVersionUnknown 未知协议 ProtoVersionUnknown = "" // ProtoVersion33 版本 003.003 ProtoVersion33 = "RFB 003.003\n" // ProtoVersion38 版本 003.008 ProtoVersion38 = "RFB 003.008\n" // ProtoVersion37 版本 003.007 ProtoVersion37 = "RFB 003.007\n" ) ================================================ FILE: rfb/keys.go ================================================ package rfb import "fmt" type Key uint32 // Keys 按键列表 type Keys []Key var keymap = map[rune]Key{ '-': Minus, '0': Digit0, '1': Digit1, '2': Digit2, '3': Digit3, '4': Digit4, '5': Digit5, '6': Digit6, '7': Digit7, '8': Digit8, '9': Digit9, } // IntToKeys 返回表示键入int类型所需按下的键的键。 func IntToKeys(v int) Keys { k := Keys{} for _, c := range fmt.Sprintf("%d", v) { k = append(k, keymap[c]) } return k } // Latin 1 (byte 3 = 0) // ISO/IEC 8859-1 = Unicode U+0020..U+00FF const ( Space Key = iota + 0x0020 Exclaim // exclamation mark QuoteDbl NumberSign Dollar Percent Ampersand Apostrophe ParenLeft ParenRight Asterisk Plus Comma Minus Period Slash Digit0 Digit1 Digit2 Digit3 Digit4 Digit5 Digit6 Digit7 Digit8 Digit9 Colon Semicolon Less Equal Greater Question At A B C D E F G H I J K L M N O P Q R S T U V W X Y Z BracketLeft Backslash BracketRight AsciiCircum Underscore Grave SmallA SmallB SmallC SmallD SmallE SmallF SmallG SmallH SmallI SmallJ SmallK SmallL SmallM SmallN SmallO SmallP SmallQ SmallR SmallS SmallT SmallU SmallV SmallW SmallX SmallY SmallZ BraceLeft Bar BraceRight AsciiTilde ) const ( BackSpace Key = iota + 0xff08 Tab Linefeed Clear _ Return ) const ( Pause Key = iota + 0xff13 ScrollLock SysReq Escape Key = 0xff1b Delete Key = 0xffff ) const ( // Cursor control & motion. Home Key = iota + 0xff50 Left Up Right Down PageUp PageDown End Begin ) const ( // Misc functions. Select Key = 0xff60 Print Execute Insert Undo Redo Menu Find Cancel Help Break ModeSwitch Key = 0xff7e NumLock Key = 0xff7f ) const ( // Keypad functions. KeypadSpace Key = 0xff80 KeypadTab Key = 0xff89 KeypadEnter Key = 0xff8d ) const ( // Keypad functions cont. KeypadF1 Key = iota + 0xff91 KeypadF2 KeypadF3 KeypadF4 KeypadHome KeypadLeft KeypadUp KeypadRight KeypadDown KeypadPrior KeypadPageUp KeypadNext KeypadPageDown KeypadEnd KeypadBegin KeypadInsert KeypadDelete KeypadMultiply KeypadAdd KeypadSeparator KeypadSubtract KeypadDecimal KeypadDivide Keypad0 Keypad1 Keypad2 Keypad3 Keypad4 Keypad5 Keypad6 Keypad7 Keypad8 Keypad9 KeypadEqual Key = 0xffbd ) const ( F1 Key = iota + 0xffbe F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 ) const ( ShiftLeft Key = iota + 0xffe1 ShiftRight ControlLeft ControlRight CapsLock ShiftLock MetaLeft MetaRight AltLeft AltRight SuperLeft SuperRight HyperLeft HyperRight ) ================================================ FILE: rfb/keys_string.go ================================================ package rfb import "fmt" const keyName = "SpaceExclaimQuoteDblNumberSignDollarPercentAmpersandApostropheParenLeftParenRightAsteriskPlusCommaMinusPeriodSlashDigit0Digit1Digit2Digit3Digit4Digit5Digit6Digit7Digit8Digit9ColonSemicolonLessEqualGreaterQuestionAtABCDEFGHIJKLMNOPQRSTUVWXYZBracketLeftBackslashBracketRightAsciiCircumUnderscoreGraveSmallASmallBSmallCSmallDSmallESmallFSmallGSmallHSmallISmallJSmallKSmallLSmallMSmallNSmallOSmallPSmallQSmallRSmallSSmallTSmallUSmallVSmallWSmallXSmallYSmallZBraceLeftBarBraceRightAsciiTildeBackSpaceTabLinefeedClearReturnPauseScrollLockSysReqEscapeHomeLeftUpRightDownPageUpPageDownEndBeginSelectModeSwitchNumLockKeypadSpaceKeypadTabKeypadEnterKeypadF1KeypadF2KeypadF3KeypadF4KeypadHomeKeypadLeftKeypadUpKeypadRightKeypadDownKeypadPriorKeypadPageUpKeypadNextKeypadPageDownKeypadEndKeypadBeginKeypadInsertKeypadDeleteKeypadMultiplyKeypadAddKeypadSeparatorKeypadSubtractKeypadDecimalKeypadDivideKeypad0Keypad1Keypad2Keypad3Keypad4Keypad5Keypad6Keypad7Keypad8Keypad9KeypadEqualF1F2F3F4F5F6F7F8F9F10F11F12ShiftLeftShiftRightControlLeftControlRightCapsLockShiftLockMetaLeftMetaRightAltLeftAltRightSuperLeftSuperRightHyperLeftHyperRightDelete" var keyMap = map[Key]string{ 32: keyName[0:5], 33: keyName[5:12], 34: keyName[12:20], 35: keyName[20:30], 36: keyName[30:36], 37: keyName[36:43], 38: keyName[43:52], 39: keyName[52:62], 40: keyName[62:71], 41: keyName[71:81], 42: keyName[81:89], 43: keyName[89:93], 44: keyName[93:98], 45: keyName[98:103], 46: keyName[103:109], 47: keyName[109:114], 48: keyName[114:120], 49: keyName[120:126], 50: keyName[126:132], 51: keyName[132:138], 52: keyName[138:144], 53: keyName[144:150], 54: keyName[150:156], 55: keyName[156:162], 56: keyName[162:168], 57: keyName[168:174], 58: keyName[174:179], 59: keyName[179:188], 60: keyName[188:192], 61: keyName[192:197], 62: keyName[197:204], 63: keyName[204:212], 64: keyName[212:214], 65: keyName[214:215], 66: keyName[215:216], 67: keyName[216:217], 68: keyName[217:218], 69: keyName[218:219], 70: keyName[219:220], 71: keyName[220:221], 72: keyName[221:222], 73: keyName[222:223], 74: keyName[223:224], 75: keyName[224:225], 76: keyName[225:226], 77: keyName[226:227], 78: keyName[227:228], 79: keyName[228:229], 80: keyName[229:230], 81: keyName[230:231], 82: keyName[231:232], 83: keyName[232:233], 84: keyName[233:234], 85: keyName[234:235], 86: keyName[235:236], 87: keyName[236:237], 88: keyName[237:238], 89: keyName[238:239], 90: keyName[239:240], 91: keyName[240:251], 92: keyName[251:260], 93: keyName[260:272], 94: keyName[272:283], 95: keyName[283:293], 96: keyName[293:298], 97: keyName[298:304], 98: keyName[304:310], 99: keyName[310:316], 100: keyName[316:322], 101: keyName[322:328], 102: keyName[328:334], 103: keyName[334:340], 104: keyName[340:346], 105: keyName[346:352], 106: keyName[352:358], 107: keyName[358:364], 108: keyName[364:370], 109: keyName[370:376], 110: keyName[376:382], 111: keyName[382:388], 112: keyName[388:394], 113: keyName[394:400], 114: keyName[400:406], 115: keyName[406:412], 116: keyName[412:418], 117: keyName[418:424], 118: keyName[424:430], 119: keyName[430:436], 120: keyName[436:442], 121: keyName[442:448], 122: keyName[448:454], 123: keyName[454:463], 124: keyName[463:466], 125: keyName[466:476], 126: keyName[476:486], 65288: keyName[486:495], 65289: keyName[495:498], 65290: keyName[498:506], 65291: keyName[506:511], 65293: keyName[511:517], 65299: keyName[517:522], 65300: keyName[522:532], 65301: keyName[532:538], 65307: keyName[538:544], 65360: keyName[544:548], 65361: keyName[548:552], 65362: keyName[552:554], 65363: keyName[554:559], 65364: keyName[559:563], 65365: keyName[563:569], 65366: keyName[569:577], 65367: keyName[577:580], 65368: keyName[580:585], 65376: keyName[585:591], 65406: keyName[591:601], 65407: keyName[601:608], 65408: keyName[608:619], 65417: keyName[619:628], 65421: keyName[628:639], 65425: keyName[639:647], 65426: keyName[647:655], 65427: keyName[655:663], 65428: keyName[663:671], 65429: keyName[671:681], 65430: keyName[681:691], 65431: keyName[691:699], 65432: keyName[699:710], 65433: keyName[710:720], 65434: keyName[720:731], 65435: keyName[731:743], 65436: keyName[743:753], 65437: keyName[753:767], 65438: keyName[767:776], 65439: keyName[776:787], 65440: keyName[787:799], 65441: keyName[799:811], 65442: keyName[811:825], 65443: keyName[825:834], 65444: keyName[834:849], 65445: keyName[849:863], 65446: keyName[863:876], 65447: keyName[876:888], 65448: keyName[888:895], 65449: keyName[895:902], 65450: keyName[902:909], 65451: keyName[909:916], 65452: keyName[916:923], 65453: keyName[923:930], 65454: keyName[930:937], 65455: keyName[937:944], 65456: keyName[944:951], 65457: keyName[951:958], 65469: keyName[958:969], 65470: keyName[969:971], 65471: keyName[971:973], 65472: keyName[973:975], 65473: keyName[975:977], 65474: keyName[977:979], 65475: keyName[979:981], 65476: keyName[981:983], 65477: keyName[983:985], 65478: keyName[985:987], 65479: keyName[987:990], 65480: keyName[990:993], 65481: keyName[993:996], 65505: keyName[996:1005], 65506: keyName[1005:1015], 65507: keyName[1015:1026], 65508: keyName[1026:1038], 65509: keyName[1038:1046], 65510: keyName[1046:1055], 65511: keyName[1055:1063], 65512: keyName[1063:1072], 65513: keyName[1072:1079], 65514: keyName[1079:1087], 65515: keyName[1087:1096], 65516: keyName[1096:1106], 65517: keyName[1106:1115], 65518: keyName[1115:1125], 65535: keyName[1125:1131], } func (i Key) String() string { if str, ok := keyMap[i]; ok { return str } return fmt.Sprintf("Key(%d)", i) } ================================================ FILE: rfb/message.go ================================================ package rfb type Message interface { Type() MessageType String() string Supported(ISession) bool Read(ISession) (Message, error) Write(ISession) error Clone() Message } ================================================ FILE: rfb/message_type_client_string.go ================================================ // Code generated by "stringer -type=ClientMessageType"; DO NOT EDIT. package rfb import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[SetPixelFormat-0] _ = x[SetEncodings-2] _ = x[FramebufferUpdateRequest-3] _ = x[KeyEvent-4] _ = x[PointerEvent-5] _ = x[ClientCutText-6] _ = x[ClientFence-248] _ = x[QEMUExtendedKeyEvent-255] } const ( _ClientMessageType_name_0 = "SetPixelFormat" _ClientMessageType_name_1 = "SetEncodingsFramebufferUpdateRequestKeyEventPointerEventClientCutText" _ClientMessageType_name_2 = "ClientFence" _ClientMessageType_name_3 = "QEMUExtendedKeyEvent" ) var ( _ClientMessageType_index_1 = [...]uint8{0, 12, 36, 44, 56, 69} ) func (i ClientMessageType) String() string { switch { case i == 0: return _ClientMessageType_name_0 case 2 <= i && i <= 6: i -= 2 return _ClientMessageType_name_1[_ClientMessageType_index_1[i]:_ClientMessageType_index_1[i+1]] case i == 248: return _ClientMessageType_name_2 case i == 255: return _ClientMessageType_name_3 default: return "ClientMessageType(" + strconv.FormatInt(int64(i), 10) + ")" } } ================================================ FILE: rfb/message_type_server_string.go ================================================ // Code generated by "stringer -type=ServerMessageType"; DO NOT EDIT. package rfb import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[FramebufferUpdate-0] _ = x[SetColorMapEntries-1] _ = x[Bell-2] _ = x[ServerCutText-3] } const _ServerMessageType_name = "FramebufferUpdateSetColorMapEntriesBellServerCutText" var _ServerMessageType_index = [...]uint8{0, 17, 35, 39, 52} func (i ServerMessageType) String() string { if i >= ServerMessageType(len(_ServerMessageType_index)-1) { return "ServerMessageType(" + strconv.FormatInt(int64(i), 10) + ")" } return _ServerMessageType_name[_ServerMessageType_index[i]:_ServerMessageType_index[i+1]] } ================================================ FILE: rfb/messagetype.go ================================================ package rfb type MessageType uint8 // ClientMessageType vnc客户端发送给vnc服务端的消息类型 type ClientMessageType MessageType //go:generate stringer -type=ClientMessageType const ( SetPixelFormat ClientMessageType = 0 // 设置像素格式 SetEncodings ClientMessageType = 2 // 设置消息的编码格式 FramebufferUpdateRequest ClientMessageType = 3 // 请求帧缓冲内容 KeyEvent ClientMessageType = 4 // 键盘事件消息 PointerEvent ClientMessageType = 5 // 鼠标事件消息 ClientCutText ClientMessageType = 6 // 剪切板消息 EnableContinuousUpdates ClientMessageType = 150 // 打开连续更新 ClientFence ClientMessageType = 248 //客户端到服务端的数据同步请求 SetDesktopSize ClientMessageType = 251 //客户端设置桌面大小 QEMUExtendedKeyEvent ClientMessageType = 255 // qumu虚拟机的扩展按键消息 ) // ServerMessageType 服务端发送给客户端的消息类型 type ServerMessageType MessageType //go:generate stringer -type=ServerMessageType const ( FramebufferUpdate ServerMessageType = 0 // 帧缓冲区更新消息 SetColorMapEntries ServerMessageType = 1 // 设置颜色地图 Bell ServerMessageType = 2 // 响铃 ServerCutText ServerMessageType = 3 // 设置剪切板数据 EndOfContinuousUpdates ServerMessageType = 150 //结束连续更新 ServerFence ServerMessageType = 248 //支持 Fence 扩展的服务器发送此扩展以请求数据流的同步 ) ================================================ FILE: rfb/options.go ================================================ package rfb import "io" type Option func(*Options) type GetConn func(sess ISession) (io.ReadWriteCloser, error) // Options 配置信息 type Options struct { // 公共配置 Handlers []IHandler // 处理程序列表 SecurityHandlers []ISecurityHandler // 安全验证 Encodings []IEncoding // 支持的编码类型 PixelFormat PixelFormat // 像素格式 ColorMap ColorMap // 颜色地图 Input chan Message // 输入消息 Output chan Message // 输出消息 Messages []Message // 支持的消息类型 DisableServerMessageType []ServerMessageType // 禁用的消息,碰到这些消息,则跳过 DisableClientMessageType []ClientMessageType // 禁用的消息,碰到这些消息,则跳过 QuitCh chan struct{} // 退出 ErrorCh chan error // 错误通道 // 服务端配置 DesktopName []byte // 桌面名称,作为服务端配置的时候,需要设置 Height uint16 // 缓冲帧高度,作为服务端配置的时候,需要设置 Width uint16 // 缓冲帧宽度,作为服务端配置的时候,需要设置 // 客户端配置 DrawCursor bool // 是否绘制鼠标指针 Exclusive bool // 是否独占 // 生成连接的方法 GetConn GetConn } // OptHandlers 设置流程处理程序 func OptHandlers(opt ...IHandler) Option { return func(options *Options) { options.Handlers = append(options.Handlers, opt...) } } // OptSecurityHandlers 设置权限认证处理程序 func OptSecurityHandlers(opt ...ISecurityHandler) Option { return func(options *Options) { options.SecurityHandlers = append(options.SecurityHandlers, opt...) } } // OptEncodings 设置支持的编码格式 func OptEncodings(opt ...IEncoding) Option { return func(options *Options) { options.Encodings = append(options.Encodings, opt...) } } // OptMessages 设置支持的消息类型 func OptMessages(opt ...Message) Option { return func(options *Options) { options.Messages = append(options.Messages, opt...) } } // OptPixelFormat 设置像素格式 func OptPixelFormat(opt PixelFormat) Option { return func(options *Options) { options.PixelFormat = opt } } // OptGetConn 设置生成连接方法 func OptGetConn(opt GetConn) Option { return func(options *Options) { options.GetConn = opt } } func OptDesktopName(opt []byte) Option { return func(options *Options) { options.DesktopName = opt } } func OptHeight(opt int) Option { return func(options *Options) { options.Height = uint16(opt) } } func OptWidth(opt int) Option { return func(options *Options) { options.Width = uint16(opt) } } // OptDisableServerMessageType 要屏蔽的服务端消息 func OptDisableServerMessageType(opt ...ServerMessageType) Option { return func(options *Options) { options.DisableServerMessageType = opt } } // OptDisableClientMessageType 要屏蔽的客户端消息 func OptDisableClientMessageType(opt ...ClientMessageType) Option { return func(options *Options) { options.DisableClientMessageType = opt } } ================================================ FILE: rfb/pixel_format.go ================================================ package rfb import ( "encoding/binary" "fmt" ) const PixelFormatLen = 16 var ( // PixelFormat8bit 获取8bit像素格式 PixelFormat8bit = NewPixelFormat(8) // PixelFormat16bit 获取15bit像素格式 PixelFormat16bit = NewPixelFormat(16) // PixelFormat32bit 获取32bit像素格式 PixelFormat32bit = NewPixelFormat(32) // PixelFormatAten returns pixel format used in Aten IKVM PixelFormatAten = NewPixelFormatAten() ) // PixelFormat 像素格式结构体 type PixelFormat struct { BPP uint8 // 1 byte,像素的位数,位数越大,色彩越丰富。只支持[8|16|32] 该值必须大于Depth Depth uint8 // 1 byte,色深,像素中表示色彩的位数 BigEndian uint8 // 1 byte,多字节像素的字节序,非零即大端序 TrueColor uint8 // 1 byte,1 表示真彩色,pixel 的值表示 RGB 颜色;0 表示调色板,pexel 的值表示颜色在调色板的偏移量 RedMax uint16 // 2 byte,红色的长度 GreenMax uint16 // 2 byte,绿色的长度 BlueMax uint16 // 2 byte,蓝色的长度 RedShift uint8 // 1 byte,红色的位移量 GreenShift uint8 // 1 byte,绿色的位移量 BlueShift uint8 // 1 byte,蓝色的偏移量 _ [3]byte // 填充字节 } func (that PixelFormat) String() string { return fmt.Sprintf("{ bpp: %d depth: %d big-endian: %d true-color: %d red-max: %d green-max: %d blue-max: %d red-shift: %d green-shift: %d blue-shift: %d }", that.BPP, that.Depth, that.BigEndian, that.TrueColor, that.RedMax, that.GreenMax, that.BlueMax, that.RedShift, that.GreenShift, that.BlueShift) } // Order 确定像素格式是使用了大端字节序还是小端字节序 func (that PixelFormat) Order() binary.ByteOrder { if that.BigEndian == 1 { return binary.BigEndian } return binary.LittleEndian } func NewPixelFormat(bpp uint8) PixelFormat { bigEndian := uint8(0) // rgbMax := uint16(math.Exp2(float64(bpp))) - 1 rMax := uint16(255) gMax := uint16(255) bMax := uint16(255) var ( tc = uint8(1) rs, gs, bs uint8 depth uint8 ) switch bpp { case 8: tc = 0 depth = 8 rs, gs, bs = 0, 0, 0 case 16: depth = 16 rs, gs, bs = 0, 4, 8 case 32: depth = 24 // rs, gs, bs = 0, 8, 16 rs, gs, bs = 16, 8, 0 } return PixelFormat{ BPP: bpp, Depth: depth, BigEndian: bigEndian, TrueColor: tc, RedMax: rMax, GreenMax: gMax, BlueMax: bMax, RedShift: rs, GreenShift: gs, BlueShift: bs, //_: [3]byte{}, } } func NewPixelFormatAten() PixelFormat { return PixelFormat{ BPP: 16, Depth: 15, BigEndian: 0, TrueColor: 1, RedMax: (1 << 5) - 1, GreenMax: (1 << 5) - 1, BlueMax: (1 << 5) - 1, RedShift: 10, GreenShift: 5, BlueShift: 0, //_: [3]byte{}, } } ================================================ FILE: rfb/rectangle.go ================================================ package rfb import ( "encoding/binary" "fmt" ) // Rectangle 表示像素数据的矩形 type Rectangle struct { X uint16 Y uint16 Width uint16 Height uint16 EncType EncodingType Enc IEncoding } func NewRectangle() *Rectangle { return &Rectangle{} } func (that *Rectangle) String() string { return fmt.Sprintf("X:%d,Y:%d,Width:%d,Height:%d,EncType:%s", that.X, that.Y, that.Width, that.Height, that.EncType) } // 读取矩形数据 func (that *Rectangle) Read(sess ISession) error { var err error //读取x坐标 if err = binary.Read(sess, binary.BigEndian, &that.X); err != nil { return err } // 读取y坐标 if err = binary.Read(sess, binary.BigEndian, &that.Y); err != nil { return err } // 读取x坐标上的宽度 if err = binary.Read(sess, binary.BigEndian, &that.Width); err != nil { return err } // 读取y坐标上的高度 if err = binary.Read(sess, binary.BigEndian, &that.Height); err != nil { return err } // 读取编码类型 if err = binary.Read(sess, binary.BigEndian, &that.EncType); err != nil { return err } that.Enc = sess.NewEncoding(that.EncType) if that.Enc == nil { return fmt.Errorf("不支持的编码类型: %s", that.EncType) } return that.Enc.Read(sess, that) } // 写入矩形数据 func (that *Rectangle) Write(sess ISession) error { var err error if err = binary.Write(sess, binary.BigEndian, that.X); err != nil { return err } if err = binary.Write(sess, binary.BigEndian, that.Y); err != nil { return err } if err = binary.Write(sess, binary.BigEndian, that.Width); err != nil { return err } if err = binary.Write(sess, binary.BigEndian, that.Height); err != nil { return err } if err = binary.Write(sess, binary.BigEndian, that.EncType); err != nil { return err } // 通过预定义的编码格式写入 return that.Enc.Write(sess, that) } func (that *Rectangle) Clone() *Rectangle { r := &Rectangle{ X: that.X, Y: that.Y, Width: that.Width, Height: that.Height, EncType: that.EncType, Enc: that.Enc.Clone(true), } return r } ================================================ FILE: rfb/security.go ================================================ package rfb // SecurityType 安全认证类型 type SecurityType uint8 //go:generate stringer -type=SecurityType const ( SecTypeUnknown SecurityType = SecurityType(0) // 未知认证类型 SecTypeNone SecurityType = SecurityType(1) // 不需要认证 SecTypeVNC SecurityType = SecurityType(2) // vnc密码认证 SecTypeTight SecurityType = SecurityType(16) // tight vnc主导的认证模式 SecTypeVeNCrypt SecurityType = SecurityType(19) // VeNCrypt 通用认证类型 ) // SecuritySubType 认证子类型 type SecuritySubType uint32 //go:generate stringer -type=SecuritySubType // SecSubTypeUnknown 未知的子类型认证 const ( SecSubTypeUnknown SecuritySubType = SecuritySubType(0) ) // VeNCrypt 安全认证会有两种认证版本0.1和0.2 // 以下表示0.1 const ( SecSubTypeVeNCrypt01Unknown SecuritySubType = SecuritySubType(0) SecSubTypeVeNCrypt01Plain SecuritySubType = SecuritySubType(19) SecSubTypeVeNCrypt01TLSNone SecuritySubType = SecuritySubType(20) SecSubTypeVeNCrypt01TLSVNC SecuritySubType = SecuritySubType(21) SecSubTypeVeNCrypt01TLSPlain SecuritySubType = SecuritySubType(22) SecSubTypeVeNCrypt01X509None SecuritySubType = SecuritySubType(23) SecSubTypeVeNCrypt01X509VNC SecuritySubType = SecuritySubType(24) SecSubTypeVeNCrypt01X509Plain SecuritySubType = SecuritySubType(25) ) // 以下表示0.2版本的类型 const ( SecSubTypeVeNCrypt02Unknown SecuritySubType = SecuritySubType(0) SecSubTypeVeNCrypt02Plain SecuritySubType = SecuritySubType(256) SecSubTypeVeNCrypt02TLSNone SecuritySubType = SecuritySubType(257) SecSubTypeVeNCrypt02TLSVNC SecuritySubType = SecuritySubType(258) SecSubTypeVeNCrypt02TLSPlain SecuritySubType = SecuritySubType(259) SecSubTypeVeNCrypt02X509None SecuritySubType = SecuritySubType(260) SecSubTypeVeNCrypt02X509VNC SecuritySubType = SecuritySubType(261) SecSubTypeVeNCrypt02X509Plain SecuritySubType = SecuritySubType(262) ) // ISecurityHandler 认证方式的接口 type ISecurityHandler interface { Type() SecurityType SubType() SecuritySubType Auth(ISession) error } ================================================ FILE: rfb/securitysubtype_string.go ================================================ // Code generated by "stringer -type=SecuritySubType"; DO NOT EDIT. package rfb import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[SecSubTypeUnknown-0] _ = x[SecSubTypeVeNCrypt01Unknown-0] _ = x[SecSubTypeVeNCrypt01Plain-19] _ = x[SecSubTypeVeNCrypt01TLSNone-20] _ = x[SecSubTypeVeNCrypt01TLSVNC-21] _ = x[SecSubTypeVeNCrypt01TLSPlain-22] _ = x[SecSubTypeVeNCrypt01X509None-23] _ = x[SecSubTypeVeNCrypt01X509VNC-24] _ = x[SecSubTypeVeNCrypt01X509Plain-25] _ = x[SecSubTypeVeNCrypt02Unknown-0] _ = x[SecSubTypeVeNCrypt02Plain-256] _ = x[SecSubTypeVeNCrypt02TLSNone-257] _ = x[SecSubTypeVeNCrypt02TLSVNC-258] _ = x[SecSubTypeVeNCrypt02TLSPlain-259] _ = x[SecSubTypeVeNCrypt02X509None-260] _ = x[SecSubTypeVeNCrypt02X509VNC-261] _ = x[SecSubTypeVeNCrypt02X509Plain-262] } const ( _SecuritySubType_name_0 = "SecSubTypeUnknown" _SecuritySubType_name_1 = "SecSubTypeVeNCrypt01PlainSecSubTypeVeNCrypt01TLSNoneSecSubTypeVeNCrypt01TLSVNCSecSubTypeVeNCrypt01TLSPlainSecSubTypeVeNCrypt01X509NoneSecSubTypeVeNCrypt01X509VNCSecSubTypeVeNCrypt01X509Plain" _SecuritySubType_name_2 = "SecSubTypeVeNCrypt02PlainSecSubTypeVeNCrypt02TLSNoneSecSubTypeVeNCrypt02TLSVNCSecSubTypeVeNCrypt02TLSPlainSecSubTypeVeNCrypt02X509NoneSecSubTypeVeNCrypt02X509VNCSecSubTypeVeNCrypt02X509Plain" ) var ( _SecuritySubType_index_1 = [...]uint8{0, 25, 52, 78, 106, 134, 161, 190} _SecuritySubType_index_2 = [...]uint8{0, 25, 52, 78, 106, 134, 161, 190} ) func (i SecuritySubType) String() string { switch { case i == 0: return _SecuritySubType_name_0 case 19 <= i && i <= 25: i -= 19 return _SecuritySubType_name_1[_SecuritySubType_index_1[i]:_SecuritySubType_index_1[i+1]] case 256 <= i && i <= 262: i -= 256 return _SecuritySubType_name_2[_SecuritySubType_index_2[i]:_SecuritySubType_index_2[i+1]] default: return "SecuritySubType(" + strconv.FormatInt(int64(i), 10) + ")" } } ================================================ FILE: rfb/securitytype_string.go ================================================ // Code generated by "stringer -type=SecurityType"; DO NOT EDIT. package rfb import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[SecTypeUnknown-0] _ = x[SecTypeNone-1] _ = x[SecTypeVNC-2] _ = x[SecTypeTight-16] _ = x[SecTypeVeNCrypt-19] } const ( _SecurityType_name_0 = "SecTypeUnknownSecTypeNoneSecTypeVNC" _SecurityType_name_1 = "SecTypeTight" _SecurityType_name_2 = "SecTypeVeNCrypt" ) var ( _SecurityType_index_0 = [...]uint8{0, 14, 25, 35} ) func (i SecurityType) String() string { switch { case i <= 2: return _SecurityType_name_0[_SecurityType_index_0[i]:_SecurityType_index_0[i+1]] case i == 16: return _SecurityType_name_1 case i == 19: return _SecurityType_name_2 default: return "SecurityType(" + strconv.FormatInt(int64(i), 10) + ")" } } ================================================ FILE: rfb/session.go ================================================ package rfb import ( "github.com/gogf/gf/v2/container/gmap" "io" ) // ISession vnc连接的接口 type ISession interface { io.ReadWriteCloser Conn() io.ReadWriteCloser Start() Flush() error // 清空缓冲区 Wait() <-chan struct{} // 等待会话处理结束 Init(...Option) error Options() Options SetPixelFormat(PixelFormat) SetColorMap(ColorMap) SetWidth(uint16) SetHeight(uint16) SetDesktopName([]byte) ProtocolVersion() string // 获取当前的rfb协议 SetProtocolVersion(string) // 设置rfb协议 SetSecurityHandler(ISecurityHandler) // 设置安全认证处理方法 SecurityHandler() ISecurityHandler // 获取当前安全认证的处理方法 Encodings() []IEncoding // 获取该会话支持的图像编码类型 SetEncodings([]EncodingType) error // 设置该链接支持的图像编码类型 NewEncoding(EncodingType) IEncoding Swap() *gmap.Map // 获取会话的自定义存储数据 Type() SessionType } type SessionType uint8 //go:generate stringer -type=SessionType const ( ClientSessionType SessionType = 0 ServerSessionType SessionType = 1 RecorderSessionType SessionType = 2 PlayerSessionType SessionType = 3 CanvasSessionType SessionType = 4 ) ================================================ FILE: rfb/session_string.go ================================================ // Code generated by "stringer -type=SessionType"; DO NOT EDIT. package rfb import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[ClientSessionType-0] _ = x[ServerSessionType-1] _ = x[RecorderSessionType-2] _ = x[PlayerSessionType-3] _ = x[CanvasSessionType-4] } const _SessionType_name = "ClientSessionTypeServerSessionTypeRecorderSessionTypePlayerSessionTypeCanvasSessionType" var _SessionType_index = [...]uint8{0, 17, 34, 53, 70, 87} func (i SessionType) String() string { if i >= SessionType(len(_SessionType_index)-1) { return "SessionType(" + strconv.FormatInt(int64(i), 10) + ")" } return _SessionType_name[_SessionType_index[i]:_SessionType_index[i+1]] } ================================================ FILE: rfb/target_config.go ================================================ package rfb import ( "fmt" "time" ) type TargetConfig struct { Network string // 网络协议 Timeout time.Duration // 超时时间 Host string // vnc服务端地址 Port int // vnc服务端端口 Password []byte // vnc服务端密码 } func (that TargetConfig) Addr() string { return fmt.Sprintf("%s:%d", that.Host, that.Port) } func (that TargetConfig) GetNetwork() string { if len(that.Network) == 0 { return "tcp" } return that.Network } func (that TargetConfig) GetTimeout() time.Duration { if that.Timeout == 0 { return 10 * time.Second } return that.Timeout } ================================================ FILE: security/security_none.go ================================================ package security import "github.com/vprix/vncproxy/rfb" // ClientAuthNone vnc客户端认证 type ClientAuthNone struct{} // ServerAuthNone 服务端认证 type ServerAuthNone struct{} var _ rfb.ISecurityHandler = new(ClientAuthNone) var _ rfb.ISecurityHandler = new(ServerAuthNone) func (*ClientAuthNone) Type() rfb.SecurityType { return rfb.SecTypeNone } func (*ClientAuthNone) SubType() rfb.SecuritySubType { return rfb.SecSubTypeUnknown } func (*ClientAuthNone) Auth(rfb.ISession) error { return nil } func (*ServerAuthNone) Type() rfb.SecurityType { return rfb.SecTypeNone } func (*ServerAuthNone) SubType() rfb.SecuritySubType { return rfb.SecSubTypeUnknown } func (*ServerAuthNone) Auth(rfb.ISession) error { return nil } ================================================ FILE: security/security_tight.go ================================================ package security ================================================ FILE: security/security_vencryptplain.go ================================================ package security import ( "bytes" "encoding/binary" "fmt" "github.com/vprix/vncproxy/rfb" ) type ClientAuthVeNCrypt02Plain struct { Username []byte Password []byte } func (*ClientAuthVeNCrypt02Plain) Type() rfb.SecurityType { return rfb.SecTypeVeNCrypt } func (*ClientAuthVeNCrypt02Plain) SubType() rfb.SecuritySubType { return rfb.SecSubTypeVeNCrypt02Plain } func (auth *ClientAuthVeNCrypt02Plain) Auth(session rfb.ISession) error { // 发送认证版本号 if err := binary.Write(session, binary.BigEndian, []uint8{0, 2}); err != nil { return err } if err := session.Flush(); err != nil { return err } var ( major, minor uint8 ) // 对比版本号 if err := binary.Read(session, binary.BigEndian, &major); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &minor); err != nil { return err } res := uint8(1) if major == 0 && minor == 2 { res = uint8(0) } if err := binary.Write(session, binary.BigEndian, res); err != nil { return err } if err := session.Flush(); err != nil { return err } // 选择认证子类型,只支持 SecSubTypeVeNCrypt02Plain 用户名密码认证 if err := binary.Write(session, binary.BigEndian, uint8(1)); err != nil { return err } if err := binary.Write(session, binary.BigEndian, auth.SubType()); err != nil { return err } if err := session.Flush(); err != nil { return err } var secType rfb.SecuritySubType if err := binary.Read(session, binary.BigEndian, &secType); err != nil { return err } // 客户端选择的认证类型服务端不支持 if secType != auth.SubType() { if err := binary.Write(session, binary.BigEndian, uint8(1)); err != nil { return err } if err := session.Flush(); err != nil { return err } return fmt.Errorf("invalid sectype") } // 服务端未设置用户名密码认证数据 if len(auth.Password) == 0 || len(auth.Username) == 0 { return fmt.Errorf("Security Handshake failed; no username and/or password provided for VeNCryptAuth. ") } var ( uLength, pLength uint32 ) // 获取用户名和密码长度 if err := binary.Read(session, binary.BigEndian, &uLength); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &pLength); err != nil { return err } // 获取用户名和密码内容 username := make([]byte, uLength) password := make([]byte, pLength) if err := binary.Read(session, binary.BigEndian, &username); err != nil { return err } if err := binary.Read(session, binary.BigEndian, &password); err != nil { return err } // 对比用户名密码是否正确,如果不正确则报错 if !bytes.Equal(auth.Username, username) || !bytes.Equal(auth.Password, password) { return fmt.Errorf("invalid username/password") } return nil } ================================================ FILE: security/security_vnc.go ================================================ package security import ( "bytes" "crypto/des" "encoding/binary" "fmt" "github.com/gogf/gf/v2/util/grand" "github.com/vprix/vncproxy/rfb" ) // ChallengeLen 随机认证串的长度 const ChallengeLen = 16 // ServerAuthVNC vnc服务端使用vnc auth认证方式 type ServerAuthVNC struct { Challenge []byte Password []byte Crypted []byte } var _ rfb.ISecurityHandler = new(ServerAuthVNC) var _ rfb.ISecurityHandler = new(ClientAuthVNC) func (*ServerAuthVNC) Type() rfb.SecurityType { return rfb.SecTypeVNC } func (*ServerAuthVNC) SubType() rfb.SecuritySubType { return rfb.SecSubTypeUnknown } // 写入随机字符串 func (that *ServerAuthVNC) writeChallenge(session rfb.ISession) error { if err := binary.Write(session, binary.BigEndian, that.Challenge); err != nil { return err } return session.Flush() } func (that *ServerAuthVNC) ReadChallenge(session rfb.ISession) error { var crypted [ChallengeLen]byte if err := binary.Read(session, binary.BigEndian, &crypted); err != nil { return err } that.Crypted = crypted[:] return nil } func (that *ServerAuthVNC) Auth(session rfb.ISession) error { if len(that.Challenge) != ChallengeLen { that.Challenge = grand.B(ChallengeLen) } if err := that.writeChallenge(session); err != nil { return err } if err := that.ReadChallenge(session); err != nil { return err } // 加密随机认证串,并把加密后的串与客户端穿过来的串进行对比,如果对比一致,则说明密码一致 encrypted, err := AuthVNCEncode(that.Password, that.Challenge) if err != nil { return err } if !bytes.Equal(encrypted, that.Crypted) { return fmt.Errorf("密码错误") } return nil } // ClientAuthVNC vnc 客户端使用vnc auth认证方式 type ClientAuthVNC struct { Challenge []byte Password []byte } func (*ClientAuthVNC) Type() rfb.SecurityType { return rfb.SecTypeVNC } func (*ClientAuthVNC) SubType() rfb.SecuritySubType { return rfb.SecSubTypeUnknown } func (that *ClientAuthVNC) Auth(session rfb.ISession) error { if len(that.Password) == 0 { return fmt.Errorf("安全认证失败,因为没有传入VNCAuth认证方式所用的密码") } var challenge [ChallengeLen]byte if err := binary.Read(session, binary.BigEndian, &challenge); err != nil { return err } // 使用密码对认证串加密 encrypted, err := AuthVNCEncode(that.Password, challenge[:]) if err != nil { return err } // 发送加密后的认证串 if err = binary.Write(session, binary.BigEndian, encrypted); err != nil { return err } return session.Flush() } // AuthVNCEncode 加密随机认证串 func AuthVNCEncode(password []byte, challenge []byte) ([]byte, error) { if len(challenge) != ChallengeLen { return nil, fmt.Errorf("随机认证串的长度不正确,正确的应该是16字节") } // 截取密码的前八位,因为只有前八位才有用 key := make([]byte, 8) copy(key, password) // 对密码的每个字节进行翻转 for i := range key { key[i] = (key[i]&0x55)<<1 | (key[i]&0xAA)>>1 // Swap adjacent bits key[i] = (key[i]&0x33)<<2 | (key[i]&0xCC)>>2 // Swap adjacent pairs key[i] = (key[i]&0x0F)<<4 | (key[i]&0xF0)>>4 // Swap the 2 halves } // 使用密码对随即认证串进行加密 cipher, err := des.NewCipher(key) if err != nil { return nil, err } for i := 0; i < len(challenge); i += cipher.BlockSize() { cipher.Encrypt(challenge[i:i+cipher.BlockSize()], challenge[i:i+cipher.BlockSize()]) } return challenge, nil } ================================================ FILE: session/canvas.go ================================================ package session import ( "github.com/gogf/gf/v2/container/gmap" "github.com/vprix/vncproxy/canvas" "github.com/vprix/vncproxy/encodings" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "io" ) type CanvasSession struct { canvas *canvas.VncCanvas options rfb.Options // 客户端配置信息 protocol string //协议版本 encodings []rfb.IEncoding // 支持的编码列 swap *gmap.Map } // NewCanvasSession 创建客户端会话 func NewCanvasSession(opts ...rfb.Option) *CanvasSession { sess := &CanvasSession{ swap: gmap.New(true), } sess.configure(opts...) return sess } // Init 初始化参数 func (that *CanvasSession) Init(opts ...rfb.Option) error { that.configure(opts...) return nil } func (that *CanvasSession) configure(opts ...rfb.Option) { for _, o := range opts { o(&that.options) } if that.options.PixelFormat.BPP == 0 { that.options.PixelFormat = rfb.PixelFormat32bit } if that.options.QuitCh == nil { that.options.QuitCh = make(chan struct{}) } if that.options.ErrorCh == nil { that.options.ErrorCh = make(chan error, 32) } if that.options.Input == nil { that.options.Input = make(chan rfb.Message) } if that.options.Output == nil { that.options.Output = make(chan rfb.Message) } if len(that.options.Handlers) == 0 { that.options.Handlers = DefaultClientHandlers } if len(that.options.Messages) == 0 { that.options.Messages = messages.DefaultServerMessages } if len(that.options.Encodings) == 0 { that.options.Encodings = encodings.DefaultEncodings } } func (that *CanvasSession) Start() { that.canvas = canvas.NewVncCanvas(int(that.options.Width), int(that.options.Height)) that.canvas.DrawCursor = that.options.DrawCursor } // Conn 获取会话底层的网络链接 func (that *CanvasSession) Conn() io.ReadWriteCloser { return that.canvas } // Options 获取配置信息 func (that *CanvasSession) Options() rfb.Options { return that.options } // ProtocolVersion 获取会话使用的协议版本 func (that *CanvasSession) ProtocolVersion() string { return that.protocol } // SetProtocolVersion 设置支持的协议版本 func (that *CanvasSession) SetProtocolVersion(pv string) { that.protocol = pv } // Encodings 获取当前支持的编码格式 func (that *CanvasSession) Encodings() []rfb.IEncoding { return that.encodings } // SetEncodings 设置编码格式 func (that *CanvasSession) SetEncodings(encs []rfb.EncodingType) error { msg := &messages.SetEncodings{ EncNum: uint16(len(encs)), Encodings: encs, } //if logger.IsDebug() { // logger.Debugf("[Proxy客户端->VNC服务端] 消息类型:%s,消息内容:%s", msg.Type(), msg.String()) //} return msg.Write(that) } func (that *CanvasSession) Flush() error { return nil } // Wait 等待会话处理完成 func (that *CanvasSession) Wait() <-chan struct{} { return that.options.QuitCh } // SecurityHandler 返回安全认证处理方法 func (that *CanvasSession) SecurityHandler() rfb.ISecurityHandler { return nil } // SetSecurityHandler 设置安全认证处理方法 func (that *CanvasSession) SetSecurityHandler(_ rfb.ISecurityHandler) { } // NewEncoding 通过编码类型判断是否支持编码对象 func (that *CanvasSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding { for _, enc := range that.encodings { if enc.Type() == typ && enc.Supported(that) { return enc.Clone() } } return nil } // Read 从链接中读取数据 func (that *CanvasSession) Read(buf []byte) (int, error) { return that.canvas.Read(buf) } // Write 写入数据到链接 func (that *CanvasSession) Write(buf []byte) (int, error) { return that.canvas.Write(buf) } // Close 关闭会话 func (that *CanvasSession) Close() error { if that.options.QuitCh != nil { close(that.options.QuitCh) that.options.QuitCh = nil } return that.canvas.Close() } // Swap session存储的临时变量 func (that *CanvasSession) Swap() *gmap.Map { return that.swap } // Type session类型 func (that *CanvasSession) Type() rfb.SessionType { return rfb.CanvasSessionType } // SetPixelFormat 设置像素格式 func (that *CanvasSession) SetPixelFormat(pf rfb.PixelFormat) { that.options.PixelFormat = pf } // SetColorMap 设置颜色地图 func (that *CanvasSession) SetColorMap(cm rfb.ColorMap) { that.options.ColorMap = cm } // SetWidth 设置桌面宽度 func (that *CanvasSession) SetWidth(width uint16) { that.options.Width = width } // SetHeight 设置桌面高度 func (that *CanvasSession) SetHeight(height uint16) { that.options.Height = height } // SetDesktopName 设置桌面名称 func (that *CanvasSession) SetDesktopName(name []byte) { that.options.DesktopName = name } ================================================ FILE: session/client.go ================================================ package session import ( "bufio" "context" "fmt" "github.com/gogf/gf/v2/container/gmap" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/encodings" "github.com/vprix/vncproxy/handler" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "io" ) var ( DefaultClientHandlers = []rfb.IHandler{ &handler.ClientVersionHandler{}, &handler.ClientSecurityHandler{}, &handler.ClientClientInitHandler{}, &handler.ClientServerInitHandler{}, &handler.ClientMessageHandler{}, } ) // ClientSession proxy 客户端 type ClientSession struct { c io.ReadWriteCloser // 网络链接 br *bufio.Reader bw *bufio.Writer // 客户端配置信息 options rfb.Options //协议版本 protocol string // 最终选择的安全认证方式 securityHandler rfb.ISecurityHandler //交换区 swap *gmap.Map } var _ rfb.ISession = new(ClientSession) // NewClient 创建客户端会话 func NewClient(opts ...rfb.Option) *ClientSession { sess := &ClientSession{ swap: gmap.New(true), } sess.configure(opts...) return sess } // Init 初始化参数 func (that *ClientSession) Init(opts ...rfb.Option) error { that.configure(opts...) return nil } func (that *ClientSession) configure(opts ...rfb.Option) { for _, o := range opts { o(&that.options) } if that.options.PixelFormat.BPP == 0 { that.options.PixelFormat = rfb.PixelFormat32bit } if that.options.QuitCh == nil { that.options.QuitCh = make(chan struct{}) } if that.options.ErrorCh == nil { that.options.ErrorCh = make(chan error, 32) } if that.options.Input == nil { that.options.Input = make(chan rfb.Message) } if that.options.Output == nil { that.options.Output = make(chan rfb.Message) } if len(that.options.Handlers) == 0 { that.options.Handlers = DefaultClientHandlers } if len(that.options.Messages) == 0 { that.options.Messages = messages.DefaultServerMessages } if len(that.options.Encodings) == 0 { that.options.Encodings = encodings.DefaultEncodings } } func (that *ClientSession) Start() { var err error that.c, err = that.options.GetConn(that) if err != nil { that.options.ErrorCh <- err return } that.br = bufio.NewReader(that.c) that.bw = bufio.NewWriter(that.c) if len(that.options.Handlers) == 0 { that.options.Handlers = DefaultClientHandlers } for _, h := range that.options.Handlers { if err := h.Handle(that); err != nil { that.options.ErrorCh <- fmt.Errorf("握手失败,请检查服务是否启动: %v", err) err = that.Close() if err != nil { that.options.ErrorCh <- fmt.Errorf("关闭client失败: %v", err) } return } } } // Conn 获取会话底层的网络链接 func (that *ClientSession) Conn() io.ReadWriteCloser { return that.c } // Options 获取配置信息 func (that *ClientSession) Options() rfb.Options { return that.options } // ProtocolVersion 获取会话使用的协议版本 func (that *ClientSession) ProtocolVersion() string { return that.protocol } // SetProtocolVersion 设置支持的协议版本 func (that *ClientSession) SetProtocolVersion(pv string) { that.protocol = pv } // Encodings 获取当前支持的编码格式 func (that *ClientSession) Encodings() []rfb.IEncoding { return that.options.Encodings } // SetEncodings 设置编码格式 func (that *ClientSession) SetEncodings(encs []rfb.EncodingType) error { msg := &messages.SetEncodings{ EncNum: uint16(len(encs)), Encodings: encs, } if logger.IsDebug() { logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端] 消息类型:%s,消息内容:%s", rfb.ClientMessageType(msg.Type()), msg.String()) } return msg.Write(that) } func (that *ClientSession) Flush() error { return that.bw.Flush() } // Wait 等待会话处理完成 func (that *ClientSession) Wait() <-chan struct{} { return that.options.QuitCh } // SecurityHandler 返回安全认证处理方法 func (that *ClientSession) SecurityHandler() rfb.ISecurityHandler { return that.securityHandler } // SetSecurityHandler 设置安全认证处理方法 func (that *ClientSession) SetSecurityHandler(securityHandler rfb.ISecurityHandler) { that.securityHandler = securityHandler } // NewEncoding 获取编码对象 func (that *ClientSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding { for _, enc := range that.options.Encodings { if enc.Type() == typ && enc.Supported(that) { return enc.Clone() } } return nil } // Read 从链接中读取数据 func (that *ClientSession) Read(buf []byte) (int, error) { return that.br.Read(buf) } // Write 写入数据到链接 func (that *ClientSession) Write(buf []byte) (int, error) { return that.bw.Write(buf) } // Close 关闭会话 func (that *ClientSession) Close() error { if that.options.QuitCh != nil { that.options.QuitCh <- struct{}{} } return that.c.Close() } // Swap session存储的临时变量 func (that *ClientSession) Swap() *gmap.Map { return that.swap } // Type session类型 func (that *ClientSession) Type() rfb.SessionType { return rfb.ClientSessionType } // SetPixelFormat 设置像素格式 func (that *ClientSession) SetPixelFormat(pf rfb.PixelFormat) { that.options.PixelFormat = pf } // SetColorMap 设置颜色地图 func (that *ClientSession) SetColorMap(cm rfb.ColorMap) { that.options.ColorMap = cm } // SetWidth 设置桌面宽度 func (that *ClientSession) SetWidth(width uint16) { that.options.Width = width } // SetHeight 设置桌面高度 func (that *ClientSession) SetHeight(height uint16) { that.options.Height = height } // SetDesktopName 设置桌面名称 func (that *ClientSession) SetDesktopName(name []byte) { that.options.DesktopName = name } ================================================ FILE: session/player.go ================================================ package session import ( "bufio" "encoding/binary" "github.com/gogf/gf/v2/container/gmap" "github.com/vprix/vncproxy/encodings" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "io" ) type PlayerSession struct { c io.ReadWriteCloser br *bufio.Reader bw *bufio.Writer options rfb.Options // 配置信息 protocol string //协议版本 securityHandler rfb.ISecurityHandler // 安全认证方式 swap *gmap.Map } func NewPlayerSession(opts ...rfb.Option) *PlayerSession { sess := &PlayerSession{ swap: gmap.New(true), } sess.configure(opts...) return sess } // Init 初始化参数 func (that *PlayerSession) Init(opts ...rfb.Option) error { that.configure(opts...) return nil } func (that *PlayerSession) configure(opts ...rfb.Option) { for _, o := range opts { o(&that.options) } if that.options.PixelFormat.BPP == 0 { that.options.PixelFormat = rfb.PixelFormat32bit } if that.options.QuitCh == nil { that.options.QuitCh = make(chan struct{}) } if that.options.ErrorCh == nil { that.options.ErrorCh = make(chan error, 32) } if that.options.Input == nil { that.options.Input = make(chan rfb.Message) } if that.options.Output == nil { that.options.Output = make(chan rfb.Message) } if len(that.options.Messages) == 0 { that.options.Messages = messages.DefaultClientMessage } if len(that.options.Encodings) == 0 { that.options.Encodings = encodings.DefaultEncodings } } func (that *PlayerSession) Start() { var err error that.c, err = that.options.GetConn(that) if err != nil { that.options.ErrorCh <- err return } that.br = bufio.NewReader(that.c) that.bw = bufio.NewWriter(that.c) version := make([]byte, len(RBSVersion)) _, err = that.br.Read(version) if err != nil { that.options.ErrorCh <- err return } // 读取rfb协议 version = make([]byte, len(rfb.ProtoVersion38)) _, err = that.br.Read(version) if err != nil { that.options.ErrorCh <- err return } that.protocol = string(version) var secTypeNone int32 err = binary.Read(that.br, binary.BigEndian, &secTypeNone) if err != nil { that.options.ErrorCh <- err return } var fbWeight uint16 err = binary.Read(that.br, binary.BigEndian, &fbWeight) if err != nil { that.options.ErrorCh <- err return } that.SetWidth(fbWeight) var fbHeight uint16 err = binary.Read(that.br, binary.BigEndian, &fbHeight) if err != nil { that.options.ErrorCh <- err return } that.SetHeight(fbHeight) var pixelFormat rfb.PixelFormat err = binary.Read(that.br, binary.BigEndian, &pixelFormat) if err != nil { that.options.ErrorCh <- err return } that.SetPixelFormat(pixelFormat) var desktopNameSize uint32 err = binary.Read(that.br, binary.BigEndian, &desktopNameSize) if err != nil { that.options.ErrorCh <- err return } desktopName := make([]byte, desktopNameSize) _, err = that.Read(desktopName) if err != nil { that.options.ErrorCh <- err return } that.SetDesktopName(desktopName) return } // Conn 获取会话底层的网络链接 func (that *PlayerSession) Conn() io.ReadWriteCloser { return that.c } // Options 获取配置信息 func (that *PlayerSession) Options() rfb.Options { return that.options } // ProtocolVersion 获取会话使用的协议版本 func (that *PlayerSession) ProtocolVersion() string { return that.protocol } // SetProtocolVersion 设置支持的协议版本 func (that *PlayerSession) SetProtocolVersion(pv string) { that.protocol = pv } // Encodings 获取当前支持的编码格式 func (that *PlayerSession) Encodings() []rfb.IEncoding { return that.options.Encodings } // SetEncodings 设置编码格式 func (that *PlayerSession) SetEncodings(_ []rfb.EncodingType) error { return nil } func (that *PlayerSession) Flush() error { return that.bw.Flush() } // Wait 等待会话处理完成 func (that *PlayerSession) Wait() <-chan struct{} { return that.options.QuitCh } // SecurityHandler 返回安全认证处理方法 func (that *PlayerSession) SecurityHandler() rfb.ISecurityHandler { return nil } // SetSecurityHandler 设置安全认证处理方法 func (that *PlayerSession) SetSecurityHandler(_ rfb.ISecurityHandler) { } // NewEncoding 通过编码类型判断是否支持编码对象 func (that *PlayerSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding { for _, enc := range that.options.Encodings { if enc.Type() == typ && enc.Supported(that) { return enc.Clone() } } return nil } // Read 从链接中读取数据 func (that *PlayerSession) Read(buf []byte) (int, error) { return that.br.Read(buf) } // Write 写入数据到链接 func (that *PlayerSession) Write(buf []byte) (int, error) { return that.bw.Write(buf) } // Close 关闭会话 func (that *PlayerSession) Close() error { if that.options.QuitCh != nil { that.options.QuitCh <- struct{}{} } return that.c.Close() } func (that *PlayerSession) Swap() *gmap.Map { return that.swap } func (that *PlayerSession) Type() rfb.SessionType { return rfb.PlayerSessionType } // SetPixelFormat 设置像素格式 func (that *PlayerSession) SetPixelFormat(pf rfb.PixelFormat) { that.options.PixelFormat = pf } // SetColorMap 设置颜色地图 func (that *PlayerSession) SetColorMap(cm rfb.ColorMap) { that.options.ColorMap = cm } // SetWidth 设置桌面宽度 func (that *PlayerSession) SetWidth(width uint16) { that.options.Width = width } // SetHeight 设置桌面高度 func (that *PlayerSession) SetHeight(height uint16) { that.options.Height = height } // SetDesktopName 设置桌面名称 func (that *PlayerSession) SetDesktopName(name []byte) { that.options.DesktopName = name } ================================================ FILE: session/recorder.go ================================================ package session import ( "bufio" "encoding/binary" "github.com/gogf/gf/v2/container/gmap" "github.com/vprix/vncproxy/encodings" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "io" ) const RBSVersion = "RBS 001.001\n" type RecorderSession struct { c io.ReadWriteCloser bw *bufio.Writer options rfb.Options // 客户端配置信息 protocol string //协议版本 swap *gmap.Map } var _ rfb.ISession = new(RecorderSession) // NewRecorder 创建客户端会话 func NewRecorder(opts ...rfb.Option) *RecorderSession { recorder := &RecorderSession{ swap: gmap.New(true), } recorder.configure(opts...) return recorder } // Init 初始化参数 func (that *RecorderSession) Init(opts ...rfb.Option) error { that.configure(opts...) return nil } func (that *RecorderSession) configure(opts ...rfb.Option) { for _, o := range opts { o(&that.options) } if that.options.PixelFormat.BPP == 0 { that.options.PixelFormat = rfb.PixelFormat32bit } if that.options.QuitCh == nil { that.options.QuitCh = make(chan struct{}) } if that.options.ErrorCh == nil { that.options.ErrorCh = make(chan error, 32) } if that.options.Input == nil { that.options.Input = make(chan rfb.Message) } if that.options.Output == nil { that.options.Output = make(chan rfb.Message) } if len(that.options.Handlers) == 0 { that.options.Handlers = DefaultClientHandlers } if len(that.options.Messages) == 0 { that.options.Messages = messages.DefaultServerMessages } if len(that.options.Encodings) == 0 { that.options.Encodings = encodings.DefaultEncodings } } func (that *RecorderSession) Start() { var err error that.c, err = that.options.GetConn(that) if err != nil { that.options.ErrorCh <- err return } that.bw = bufio.NewWriter(that.c) _, err = that.Write([]byte(RBSVersion)) if err != nil { that.options.ErrorCh <- err return } _, err = that.Write([]byte(that.ProtocolVersion())) if err != nil { that.options.ErrorCh <- err return } err = binary.Write(that.bw, binary.BigEndian, int32(rfb.SecTypeNone)) if err != nil { that.options.ErrorCh <- err return } err = binary.Write(that.bw, binary.BigEndian, int16(that.options.Width)) if err != nil { that.options.ErrorCh <- err return } err = binary.Write(that.bw, binary.BigEndian, int16(that.options.Height)) if err != nil { that.options.ErrorCh <- err return } err = binary.Write(that.bw, binary.BigEndian, that.options.PixelFormat) if err != nil { that.options.ErrorCh <- err return } nameSize := len(that.options.DesktopName) err = binary.Write(that.bw, binary.BigEndian, uint32(nameSize)) if err != nil { that.options.ErrorCh <- err return } _, err = that.Write(that.options.DesktopName) if err != nil { that.options.ErrorCh <- err return } err = that.Flush() if err != nil { that.options.ErrorCh <- err return } return } // Conn 获取会话底层的网络链接 func (that *RecorderSession) Conn() io.ReadWriteCloser { return that.c } // Options 获取配置信息 func (that *RecorderSession) Options() rfb.Options { return that.options } // ProtocolVersion 获取会话使用的协议版本 func (that *RecorderSession) ProtocolVersion() string { return that.protocol } // SetProtocolVersion 设置支持的协议版本 func (that *RecorderSession) SetProtocolVersion(pv string) { that.protocol = pv } // Encodings 获取当前支持的编码格式 func (that *RecorderSession) Encodings() []rfb.IEncoding { return that.options.Encodings } // SetEncodings 设置编码格式 func (that *RecorderSession) SetEncodings(_ []rfb.EncodingType) error { return nil } func (that *RecorderSession) Flush() error { return that.bw.Flush() } // Wait 等待会话处理完成 func (that *RecorderSession) Wait() <-chan struct{} { return that.options.QuitCh } // SecurityHandler 返回安全认证处理方法 func (that *RecorderSession) SecurityHandler() rfb.ISecurityHandler { return nil } // SetSecurityHandler 设置安全认证处理方法 func (that *RecorderSession) SetSecurityHandler(_ rfb.ISecurityHandler) { } // NewEncoding 通过编码类型判断是否支持编码对象 func (that *RecorderSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding { for _, enc := range that.options.Encodings { if enc.Type() == typ && enc.Supported(that) { return enc.Clone() } } return nil } // Read 从链接中读取数据 func (that *RecorderSession) Read(_ []byte) (int, error) { return 0, nil } // Write 写入数据到链接 func (that *RecorderSession) Write(buf []byte) (int, error) { return that.bw.Write(buf) } // Close 关闭会话 func (that *RecorderSession) Close() error { if that.options.QuitCh != nil { that.options.QuitCh <- struct{}{} } return that.c.Close() } // Swap session存储的临时变量 func (that *RecorderSession) Swap() *gmap.Map { return that.swap } // Type session类型 func (that *RecorderSession) Type() rfb.SessionType { return rfb.RecorderSessionType } // SetPixelFormat 设置像素格式 func (that *RecorderSession) SetPixelFormat(pf rfb.PixelFormat) { that.options.PixelFormat = pf } // SetColorMap 设置颜色地图 func (that *RecorderSession) SetColorMap(cm rfb.ColorMap) { that.options.ColorMap = cm } // SetWidth 设置桌面宽度 func (that *RecorderSession) SetWidth(width uint16) { that.options.Width = width } // SetHeight 设置桌面高度 func (that *RecorderSession) SetHeight(height uint16) { that.options.Height = height } // SetDesktopName 设置桌面名称 func (that *RecorderSession) SetDesktopName(name []byte) { that.options.DesktopName = name } ================================================ FILE: session/server.go ================================================ package session import ( "bufio" "github.com/gogf/gf/v2/container/gmap" "github.com/vprix/vncproxy/encodings" "github.com/vprix/vncproxy/handler" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "io" ) var ( DefaultServerHandlers = []rfb.IHandler{ &handler.ServerVersionHandler{}, &handler.ServerSecurityHandler{}, &handler.ServerClientInitHandler{}, &handler.ServerServerInitHandler{}, &handler.ServerMessageHandler{}, } ) type ServerSession struct { c io.ReadWriteCloser br *bufio.Reader bw *bufio.Writer options rfb.Options // 配置信息 protocol string //协议版本 encodings []rfb.IEncoding // 支持的编码列 securityHandler rfb.ISecurityHandler // 安全认证方式 swap *gmap.Map } var _ rfb.ISession = new(ServerSession) func NewServerSession(opts ...rfb.Option) *ServerSession { sess := &ServerSession{ swap: gmap.New(true), } sess.configure(opts...) return sess } // Init 初始化参数 func (that *ServerSession) Init(opts ...rfb.Option) error { that.configure(opts...) return nil } func (that *ServerSession) configure(opts ...rfb.Option) { for _, o := range opts { o(&that.options) } if that.options.PixelFormat.BPP == 0 { that.options.PixelFormat = rfb.PixelFormat32bit } if that.options.QuitCh == nil { that.options.QuitCh = make(chan struct{}) } if that.options.ErrorCh == nil { that.options.ErrorCh = make(chan error, 32) } if that.options.Input == nil { that.options.Input = make(chan rfb.Message) } if that.options.Output == nil { that.options.Output = make(chan rfb.Message) } if len(that.options.Messages) == 0 { that.options.Messages = messages.DefaultClientMessage } if len(that.options.Encodings) == 0 { that.options.Encodings = encodings.DefaultEncodings } } func (that *ServerSession) Start() { var err error that.c, err = that.options.GetConn(that) if err != nil { that.options.ErrorCh <- err return } that.br = bufio.NewReader(that.c) that.bw = bufio.NewWriter(that.c) if len(that.options.Handlers) == 0 { that.options.Handlers = DefaultServerHandlers } for _, h := range that.options.Handlers { if err = h.Handle(that); err != nil { that.options.ErrorCh <- err err = that.Close() if err != nil { that.options.ErrorCh <- err } return } } return } func (that *ServerSession) Conn() io.ReadWriteCloser { return that.c } func (that *ServerSession) Options() rfb.Options { return that.options } // ProtocolVersion 获取会话使用的协议版本 func (that *ServerSession) ProtocolVersion() string { return that.protocol } // SetProtocolVersion 设置支持的协议版本 func (that *ServerSession) SetProtocolVersion(pv string) { that.protocol = pv } // Encodings 获取当前支持的编码格式 func (that *ServerSession) Encodings() []rfb.IEncoding { return that.encodings } // SetEncodings 设置编码格式 func (that *ServerSession) SetEncodings(encs []rfb.EncodingType) error { es := make(map[rfb.EncodingType]rfb.IEncoding) for _, enc := range that.options.Encodings { es[enc.Type()] = enc } for _, encType := range encs { if enc, ok := es[encType]; ok { that.encodings = append(that.encodings, enc) } } return nil } func (that *ServerSession) Flush() error { return that.bw.Flush() } // Wait 等待会话处理完成 func (that *ServerSession) Wait() <-chan struct{} { return that.options.QuitCh } // SecurityHandler 返回安全认证处理方法 func (that *ServerSession) SecurityHandler() rfb.ISecurityHandler { return that.securityHandler } // SetSecurityHandler 设置安全认证处理方法 func (that *ServerSession) SetSecurityHandler(securityHandler rfb.ISecurityHandler) { that.securityHandler = securityHandler } // NewEncoding 通过编码类型判断是否支持编码对象 func (that *ServerSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding { for _, enc := range that.encodings { if enc.Type() == typ && enc.Supported(that) { return enc.Clone() } } return nil } // Read 从链接中读取数据 func (that *ServerSession) Read(buf []byte) (int, error) { return that.br.Read(buf) } // Write 写入数据到链接 func (that *ServerSession) Write(buf []byte) (int, error) { return that.bw.Write(buf) } // Close 关闭会话 func (that *ServerSession) Close() error { if that.options.QuitCh != nil { that.options.QuitCh <- struct{}{} } return that.c.Close() } // Swap session存储的临时变量 func (that *ServerSession) Swap() *gmap.Map { return that.swap } // Type session类型 func (that *ServerSession) Type() rfb.SessionType { return rfb.ServerSessionType } // SetPixelFormat 设置像素格式 func (that *ServerSession) SetPixelFormat(pf rfb.PixelFormat) { that.options.PixelFormat = pf } // SetColorMap 设置颜色地图 func (that *ServerSession) SetColorMap(cm rfb.ColorMap) { that.options.ColorMap = cm } // SetWidth 设置桌面宽度 func (that *ServerSession) SetWidth(width uint16) { that.options.Width = width } // SetHeight 设置桌面高度 func (that *ServerSession) SetHeight(height uint16) { that.options.Height = height } // SetDesktopName 设置桌面名称 func (that *ServerSession) SetDesktopName(name []byte) { that.options.DesktopName = name } ================================================ FILE: vnc/player.go ================================================ package vnc import ( "context" "encoding/binary" "fmt" "github.com/gogf/gf/v2/container/gtype" "github.com/gogf/gf/v2/os/gfile" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/handler" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/session" "io" "os" "sync" "time" ) type Player struct { svrSession *session.ServerSession // vnc客户端连接到proxy的会话 playerSession *session.PlayerSession errorCh chan error closed *gtype.Bool syncOnce sync.Once } func NewPlayer(filePath string, svrSession *session.ServerSession) *Player { playerSession := session.NewPlayerSession( rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { if !gfile.Exists(filePath) { return nil, fmt.Errorf("要读取的文件[%s]不存在", filePath) } return gfile.OpenFile(filePath, os.O_RDONLY, 0644) }), ) return &Player{ errorCh: make(chan error, 32), svrSession: svrSession, playerSession: playerSession, closed: gtype.NewBool(false), } } // Start 启动 func (that *Player) Start() error { err := that.svrSession.Init(rfb.OptHandlers([]rfb.IHandler{ &handler.ServerVersionHandler{}, &handler.ServerSecurityHandler{}, that, // 把链接到vnc服务端的逻辑加入 &handler.ServerClientInitHandler{}, &handler.ServerServerInitHandler{}, &handler.ServerMessageHandler{}, }...)) if err != nil { return err } that.svrSession.Start() err = <-that.errorCh return err } // Handle 建立远程链接 func (that *Player) Handle(sess rfb.ISession) error { that.playerSession.Start() that.svrSession = sess.(*session.ServerSession) that.svrSession.SetWidth(that.playerSession.Options().Width) that.svrSession.SetHeight(that.playerSession.Options().Height) that.svrSession.SetDesktopName(that.playerSession.Options().DesktopName) that.svrSession.SetPixelFormat(that.playerSession.Options().PixelFormat) go that.handleIO() return nil } func (that *Player) handleIO() { for that.closed.Val() == false { select { case <-that.svrSession.Wait(): return case <-that.playerSession.Wait(): return case err := <-that.svrSession.Options().ErrorCh: that.errorCh <- err that.Close() case err := <-that.playerSession.Options().ErrorCh: that.errorCh <- err that.Close() case msg := <-that.svrSession.Options().Output: if logger.IsDebug() { logger.Debugf(context.TODO(), "收到vnc客户端发送过来的消息,%s", msg) } if msg.Type() == rfb.MessageType(rfb.FramebufferUpdateRequest) { that.syncOnce.Do(func() { go that.readRbs() }) } } } } func (that *Player) readRbs() { for that.closed.Val() == false { // 从会话中读取消息类型 var messageType rfb.ServerMessageType if err := binary.Read(that.playerSession, binary.BigEndian, &messageType); err != nil { that.playerSession.Options().ErrorCh <- err return } msg := &messages.FramebufferUpdate{} // 读取消息内容 parsedMsg, err := msg.Read(that.playerSession) if err != nil { that.playerSession.Options().ErrorCh <- err return } that.svrSession.Options().Input <- parsedMsg var sleep int64 _ = binary.Read(that.playerSession, binary.BigEndian, &sleep) if sleep > 0 { time.Sleep(time.Duration(sleep)) } } } func (that *Player) Close() { that.closed.Set(true) _ = that.svrSession.Close() _ = that.playerSession.Close() } ================================================ FILE: vnc/proxy.go ================================================ package vnc import ( "github.com/gogf/gf/v2/container/gtype" "github.com/gogf/gf/v2/util/gconv" "github.com/vprix/vncproxy/handler" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/session" ) type Proxy struct { remoteSession rfb.ISession // 链接到vnc远端服务的会话 svrSession rfb.ISession // vnc客户端连接到proxy的会话 errorCh chan error closed *gtype.Bool } // NewVncProxy 生成vnc proxy服务对象 func NewVncProxy(remoteSession *session.ClientSession, serverSession *session.ServerSession) *Proxy { vncProxy := &Proxy{ svrSession: serverSession, remoteSession: remoteSession, // 这里选择8是随便选的,后期应该会改 errorCh: make(chan error, 8), closed: gtype.NewBool(false), } return vncProxy } // Start 启动 func (that *Proxy) Start() error { hds := []rfb.IHandler{ &handler.ServerVersionHandler{}, &handler.ServerSecurityHandler{}, that, // 把链接到vnc服务端的逻辑加入 &handler.ServerClientInitHandler{}, &handler.ServerServerInitHandler{}, &handler.ServerMessageHandler{}, } err := that.svrSession.Init( rfb.OptHandlers(hds...), ) if err != nil { return err } that.svrSession.Start() err = <-that.errorCh return err } func (that *Proxy) handleIO() { for that.closed.Val() == false { select { case msg := <-that.remoteSession.Options().ErrorCh: // 如果链接到vnc服务端的会话报错,则需要把链接到proxy的vnc客户端全部关闭 that.errorCh <- msg _ = that.svrSession.Close() case msg := <-that.svrSession.Options().ErrorCh: // 链接到proxy的vnc客户端链接报错,则把错误转发给vnc proxy that.errorCh <- msg _ = that.remoteSession.Close() case msg := <-that.remoteSession.Options().Output: // 收到vnc服务端发送给proxy客户端的消息,转发给proxy服务端, proxy服务端内部会把该消息转发给vnc客户端 sSessCfg := that.svrSession.Options() disabled := false // 如果该消息禁用,则跳过不转发该消息 for _, t := range sSessCfg.DisableServerMessageType { if rfb.MessageType(t) == msg.Type() { disabled = true break } } if !disabled { sSessCfg.Input <- msg } case msg := <-that.svrSession.Options().Output: // vnc客户端发送消息到proxy服务端的时候,需要对消息进行检查 // 有些消息不支持转发给vnc服务端 switch rfb.ClientMessageType(msg.Type()) { case rfb.SetPixelFormat: // 发现是设置像素格式的消息,则忽略 that.remoteSession.SetPixelFormat(msg.(*messages.SetPixelFormat).PF) that.remoteSession.Options().Input <- msg continue case rfb.SetEncodings: // 设置编码格式的消息 var encTypes []rfb.EncodingType // 判断编码是否再支持的列表 for _, s := range that.remoteSession.Encodings() { for _, cEnc := range msg.(*messages.SetEncodings).Encodings { if cEnc == s.Type() { encTypes = append(encTypes, s.Type()) } } } // 发送编码消息给vnc服务端 that.remoteSession.Options().Input <- &messages.SetEncodings{EncNum: gconv.Uint16(len(encTypes)), Encodings: encTypes} default: cliCfg := that.remoteSession.Options() disabled := false for _, t := range cliCfg.DisableClientMessageType { if rfb.MessageType(t) == msg.Type() { disabled = true break } } if !disabled { that.remoteSession.Options().Input <- msg } } } } that.errorCh <- nil } // Handle 建立远程链接 func (that *Proxy) Handle(sess rfb.ISession) (err error) { that.remoteSession.Start() if err != nil { return err } that.svrSession = sess.(*session.ServerSession) that.svrSession.SetWidth(that.remoteSession.Options().Width) that.svrSession.SetHeight(that.remoteSession.Options().Height) desktopName := that.remoteSession.Options().DesktopName if len(desktopName) <= 0 { desktopName = []byte("vprix") } that.svrSession.SetDesktopName(desktopName) that.svrSession.SetPixelFormat(that.remoteSession.Options().PixelFormat) go that.handleIO() return nil } func (that *Proxy) Close() { that.closed.Set(true) _ = that.svrSession.Close() _ = that.remoteSession.Close() } ================================================ FILE: vnc/recorder.go ================================================ package vnc import ( "context" "encoding/binary" "github.com/gogf/gf/v2/container/gtype" "github.com/gogf/gf/v2/os/gtime" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/session" ) type Recorder struct { errorCh chan error closed *gtype.Bool cliSession *session.ClientSession // 链接到vnc服务端的会话 recorderSession *session.RecorderSession } func NewRecorder(recorderSess *session.RecorderSession, cliSession *session.ClientSession) *Recorder { recorder := &Recorder{ recorderSession: recorderSess, cliSession: cliSession, errorCh: make(chan error, 32), closed: gtype.NewBool(false), } return recorder } func (that *Recorder) Start() error { var err error that.cliSession.Start() encS := []rfb.EncodingType{ rfb.EncCursorPseudo, rfb.EncPointerPosPseudo, rfb.EncCopyRect, rfb.EncZRLE, rfb.EncHexTile, rfb.EncZlib, rfb.EncRRE, } err = that.cliSession.SetEncodings(encS) if err != nil { return err } // 设置参数信息 that.recorderSession.SetProtocolVersion(that.cliSession.ProtocolVersion()) that.recorderSession.SetWidth(that.cliSession.Options().Width) that.recorderSession.SetHeight(that.cliSession.Options().Height) that.recorderSession.SetPixelFormat(that.cliSession.Options().PixelFormat) that.recorderSession.SetDesktopName(that.cliSession.Options().DesktopName) that.recorderSession.Start() reqMsg := messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height} err = reqMsg.Write(that.cliSession) if err != nil { return err } var lastUpdate *gtime.Time for { select { case msg := <-that.recorderSession.Options().Output: logger.Debugf(context.TODO(), "client message received.messageType:%d,message:%s", msg.Type(), msg) case msg := <-that.cliSession.Options().Output: if rfb.ServerMessageType(msg.Type()) == rfb.FramebufferUpdate { err = msg.Write(that.recorderSession) if err != nil { return err } if lastUpdate == nil { _ = binary.Write(that.recorderSession, binary.BigEndian, int64(0)) } else { secsPassed := gtime.Now().UnixNano() - lastUpdate.UnixNano() _ = binary.Write(that.recorderSession, binary.BigEndian, secsPassed) } err = that.recorderSession.Flush() if err != nil { return err } lastUpdate = gtime.Now() reqMsg = messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height} err = reqMsg.Write(that.cliSession) if err != nil { return err } } case <-that.cliSession.Wait(): return nil case <-that.recorderSession.Wait(): return nil case err = <-that.cliSession.Options().ErrorCh: that.errorCh <- err that.Close() case err = <-that.recorderSession.Options().ErrorCh: that.errorCh <- err that.Close() case err = <-that.errorCh: return err } } } func (that *Recorder) Close() { that.closed.Set(true) _ = that.cliSession.Close() _ = that.recorderSession.Close() } ================================================ FILE: vnc/screenshot.go ================================================ package vnc import ( "fmt" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/security" "github.com/vprix/vncproxy/session" "golang.org/x/net/context" "io" "net" "time" ) type Screenshot struct { cliSession *session.ClientSession // 链接到vnc服务端的会话 canvasSession *session.CanvasSession timeout time.Duration } func NewScreenshot(targetCfg rfb.TargetConfig) *Screenshot { securityHandlers := []rfb.ISecurityHandler{ &security.ClientAuthNone{}, } if len(targetCfg.Password) > 0 { securityHandlers = []rfb.ISecurityHandler{ &security.ClientAuthVNC{Password: targetCfg.Password}, } } canvasSession := session.NewCanvasSession() cliSession := session.NewClient( rfb.OptSecurityHandlers(securityHandlers...), rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) { return net.DialTimeout(targetCfg.GetNetwork(), targetCfg.Addr(), targetCfg.GetTimeout()) }), ) recorder := &Screenshot{ canvasSession: canvasSession, cliSession: cliSession, timeout: targetCfg.GetTimeout(), } return recorder } func (that *Screenshot) GetImage() (io.ReadWriteCloser, error) { var err error that.cliSession.Start() encS := []rfb.EncodingType{ rfb.EncCursorPseudo, rfb.EncPointerPosPseudo, rfb.EncHexTile, rfb.EncTight, rfb.EncZRLE, } defer func() { _ = that.cliSession.Close() }() err = that.cliSession.SetEncodings(encS) if err != nil { return nil, err } // 设置参数信息 that.canvasSession.SetProtocolVersion(that.cliSession.ProtocolVersion()) that.canvasSession.SetWidth(that.cliSession.Options().Width) that.canvasSession.SetHeight(that.cliSession.Options().Height) that.canvasSession.SetPixelFormat(that.cliSession.Options().PixelFormat) that.canvasSession.SetDesktopName(that.cliSession.Options().DesktopName) that.canvasSession.Start() defer func() { _ = that.canvasSession.Close() }() reqMsg := messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height} err = reqMsg.Write(that.cliSession) if err != nil { return nil, err } ctx, cancel := context.WithTimeout(context.Background(), that.timeout) defer cancel() for { select { case <-ctx.Done(): return nil, fmt.Errorf("获取截图超时") case msg := <-that.cliSession.Options().Output: if rfb.ServerMessageType(msg.Type()) == rfb.FramebufferUpdate { err = msg.Write(that.canvasSession) if err != nil { return nil, err } err = that.canvasSession.Flush() return that.canvasSession.Conn(), err } if logger.IsDebug() { logger.Debugf(context.TODO(), "获取到来自vnc服务端的消息%v", msg) } } } } ================================================ FILE: vnc/video.go ================================================ package vnc import ( "context" "github.com/osgochina/dmicro/logger" "github.com/vprix/vncproxy/encodings" "github.com/vprix/vncproxy/messages" "github.com/vprix/vncproxy/rfb" "github.com/vprix/vncproxy/security" "github.com/vprix/vncproxy/session" "io" "net" "time" ) type Video struct { cliCfg *rfb.Options targetCfg rfb.TargetConfig cliSession *session.ClientSession // 链接到vnc服务端的会话 canvasSession *session.CanvasSession } func NewVideo(cliCfg *rfb.Options, targetCfg rfb.TargetConfig) *Video { if cliCfg == nil { cliCfg = &rfb.Options{ PixelFormat: rfb.PixelFormat32bit, Messages: messages.DefaultServerMessages, Encodings: encodings.DefaultEncodings, Output: make(chan rfb.Message), Input: make(chan rfb.Message), ErrorCh: make(chan error), } } if cliCfg.Output == nil { cliCfg.Output = make(chan rfb.Message) } if cliCfg.Input == nil { cliCfg.Input = make(chan rfb.Message) } if cliCfg.ErrorCh == nil { cliCfg.ErrorCh = make(chan error) } if len(targetCfg.Password) > 0 { cliCfg.SecurityHandlers = []rfb.ISecurityHandler{ &security.ClientAuthVNC{Password: targetCfg.Password}, } } else { cliCfg.SecurityHandlers = []rfb.ISecurityHandler{ &security.ClientAuthNone{}, } } recorder := &Video{ //canvasSession: session.NewCanvasSession(*cliCfg), targetCfg: targetCfg, cliCfg: cliCfg, } return recorder } func (that *Video) Start() error { var err error timeout := 10 * time.Second if that.targetCfg.Timeout > 0 { timeout = that.targetCfg.Timeout } network := "tcp" if len(that.targetCfg.Network) > 0 { network = that.targetCfg.Network } that.cliCfg.GetConn = func(sess rfb.ISession) (io.ReadWriteCloser, error) { return net.DialTimeout(network, that.targetCfg.Addr(), timeout) } that.cliSession = session.NewClient() that.cliSession.Start() encS := []rfb.EncodingType{ rfb.EncCursorPseudo, rfb.EncPointerPosPseudo, rfb.EncCopyRect, rfb.EncTight, rfb.EncZRLE, rfb.EncHexTile, rfb.EncZlib, rfb.EncRRE, } err = that.cliSession.SetEncodings(encS) if err != nil { return err } // 设置参数信息 that.canvasSession.SetProtocolVersion(that.cliSession.ProtocolVersion()) that.canvasSession.SetWidth(that.cliSession.Options().Width) that.canvasSession.SetHeight(that.cliSession.Options().Height) that.canvasSession.SetPixelFormat(that.cliSession.Options().PixelFormat) that.canvasSession.SetDesktopName(that.cliSession.Options().DesktopName) that.canvasSession.Start() reqMsg := messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height} err = reqMsg.Write(that.cliSession) if err != nil { return err } for { select { case msg := <-that.cliCfg.Output: logger.Debugf(context.TODO(), "client message received.messageType:%d,message:%s", msg.Type(), msg) case msg := <-that.cliCfg.Input: if rfb.ServerMessageType(msg.Type()) == rfb.FramebufferUpdate { err = msg.Write(that.canvasSession) if err != nil { return err } err = that.canvasSession.Flush() if err != nil { return err } reqMsg = messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height} err = reqMsg.Write(that.cliSession) if err != nil { return err } } case err = <-that.cliCfg.ErrorCh: return err } } } func (that *Video) Close() { _ = that.cliSession.Close() _ = that.canvasSession.Close() }