[
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# 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)\n## VncProxy简介\n\n`VncProxy` 是使用`Golang`实现的`Vnc`远程桌面代理组件，完全解析`rfb`协议，支持远程桌面代理，rbs文件录屏，rbs文件回放，截图，录制视频.\n\n* 全协议支持的vnc proxy。\n  * 支持Tcp代理\n  * 支持Websocket代理\n* 屏幕录像，保存为`RBS`文件\n* 重播服务器，支持vnc客户端链接，播放`RBS`文件。\n* 支持实时录制视频\n* 支持通过`RBS`文件录制视频。\n* 支持屏幕截图\n\n## 支持的编码格式\n\n- [x] Raw\n- [x] CopyRect\n- [x] CoRRE\n- [x] rre\n- [x] Hextile\n- [x] Tight\n- [x] TightPng\n- [x] ZLib\n- [x] Zrle\n- [x] CursorPseudo\n- [x] CursorWithAlphaPseudo\n- [x] DesktopNamePseudo\n- [x] DesktopSizePseudo\n- [x] ExtendedDesktopSizePseudo\n- [x] LedStatePseudo\n- [x] CursorPosPseudo\n- [x] XCursorPseudo\n- [ ] jpeg\n- [ ] jrle\n- [ ] trle\n\n## 组件说明\n\n### Proxy\n\n1. 启动`server`接受`vnc viewer`的链接.\n2. 启动`client`连接到指定的`vnc server`.\n3. 为`vnc viewer`和`vnc server`之间建立起消息转发通道。\n4. 因为`rfb`协议被完全解析，可以针对通信的消息进行转发处理，产生了后续的功能。\n\n### Recorder\n\n1. 启动`client`连接到指定的`vnc server`.\n2. 发送帧缓冲区更新消息`FramebufferUpdateRequest`到`vnc server`。\n3. 处理`vnc server`回复的界面更新消息`FramebufferUpdate`。\n4. 把这一过程以`rbs`文件格式记录下来。\n\n### Player\n\n1. 启动`server`接受`vnc viewer`的链接.\n2. 读取`rbs`文件，并按格式生成`FramebufferUpdate`消息发送给`vnc viewer`。\n3. `vnc viewer`的界面就会回放动作。\n\n### Video\n\n1. 支持`Proxy`,`Recorder`和`rbs`文件作为输入源。\n2. 把`FramebufferUpdate`消息转换为视频文件。\n\n### Screenshot\n\n1. 支持`Proxy`,`Recorder`和`rbs`文件作为输入源。\n2. 把当前的界面视图转换为图片文件。\n\n## 使用说明\n\n`vncProxy`项目有多种应用场景。\n可以作为单独的应用程序编译，也可以作为库被其他应用程序引用。\n接下来，分别介绍各种场景下的使用方式。\n### 编译\n\n```shell\n# 使用方式:\n# build.sh [-s app_name] [-v version] [-g go_bin]\n# app_name 需要编译的应用名称\n#          选项: proxy,player,recorder,video,screenshot.\n#          默认是所有应用,多个应用可以逗号分割.\n# version  编译后的文件版本号,默认为当前git的commit id.\n# go_bin   使用的golang程序\n\n# 编译所有应用\n$ ./build \n\n# 编译proxy\n$ ./build -s proxy -v v0.1.0\n\n# 编译player,recorder\n$ ./build -s player,recorder -v v0.1.0\n```\n\n编译后的二进制文件在`./bin/`目录\n\n### Proxy\n\n代码路径在`./cmd/proxy`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n\n#### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./proxy --help\n\n# 查看版本信息\n$ ./proxy version\n\n```\n\n#### 启动tcp服务\n```shell\n\n# 启动tcp server接受vnc viewer的连接\n# vncHost  vnc服务器host\n# vncPort  vnc服务器port\n# vncPassword  vnc服务器密码\n# tcpHost  本地监听的地址\n# tcpPort  本地监听的端口\n# proxyPassword  vnc连接的密码\n# debug  使用debug模式启动服务\n\n$ ./proxy start tcpServer --vncHost=192.168.1.2 \\     \n                          --vncPort=5901 \\           \n                          --vncPassword=vprix \\       \n                          --tcpHost=0.0.0.0 \\        \n                          --tcpPort=8989 \\           \n                          --proxyPassword=12345612 \\  \n                          --debug                    \n```\n\n#### 启动WebSocket服务\n```shell\n\n# 启动ws server接受novnc的连接\n# vncHost  vnc服务器host\n# vncPort  vnc服务器port\n# vncPassword  vnc服务器密码\n# wsHost  本地监听的地址\n# wsPort  本地监听的端口\n# wsPath  websocket连接的地址\n# proxyPassword  vnc连接的密码\n# debug  使用debug模式启动服务\n\n$ ./proxy start wsServer  --vncHost=192.168.1.2 \\      \n                          --vncPort=5901         \\     \n                          --vncPassword=vprix \\       \n                          --wsHost=0.0.0.0 \\          \n                          --wsPort=8988    \\           \n                          --wsPath=/websockify \\         \n                          --proxyPassword=12345612 \\    \n                          --debug              \n```\n\n### Recorder\n\n代码路径在`./cmd/recorder`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n#### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./recorder --help\n\n# 查看版本信息\n$ ./recorder version\n\n```\n\n#### 启动Recorder服务\n```shell\n\n# rbsFile  要保存的rbs文件路径(必填)\n# vncHost  vnc服务器host\n# vncPort  vnc服务器port\n# vncPassword  vnc服务器密码\n# debug  使用debug模式启动服务\n\n$ ./recorder start --rbsFile=/path/to/foo.rbs\n\t\t\t\t\t\t\t--vncHost=192.168.1.2 \n\t\t\t\t\t\t\t--vncPort=5901\n\t\t\t\t\t\t\t--vncPassword=vprix\n\t\t\t\t\t\t\t--debug             \n```\n\n### Player\n\n代码路径在`./cmd/player`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n#### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./player --help\n\n# 查看版本信息\n$ ./player version\n```\n\n#### 启动Player Tcp服务\n```shell\n\n# rbsFile  要保存的rbs文件路径(必填)\n# tcpHost   本地监听的tcp协议地址 默认0.0.0.0\n# tcpPort  本地监听的tcp协议端口 默认8989\n# proxyPassword  连接到proxy的密码   不传入密码则使用auth none\n# debug  使用debug模式启动服务\n\n$ ./player start tcpServer  --rbsFile=/path/to/foo.rbs\n                            --tcpHost=0.0.0.0\n                            --tcpPort=8989\n                            --proxyPassword=12345612\n                            --debug             \n```\n\n#### 启动Player WS服务\n```shell\n\n# rbsFile  要保存的rbs文件路径(必填)\n# wsHost   启动websocket服务的本地地址  默认 0.0.0.0\n# wsPort   启动websocket服务的本地端口 默认8988\n# wsPath   启动websocket服务的url path 默认'/'\n# proxyPassword  连接到proxy的密码   不传入密码则使用auth none\n# debug  使用debug模式启动服务\n\n$ ./player start wsServer --rbsFile=/path/to/foo.rbs\n                          --wsHost=0.0.0.0\n                          --wsPort=8989\n                          --wsPath=/\n                          --proxyPassword=12345612\n                          --debug             \n```\n### Screenshot\n代码路径在`./cmd/screenshot`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n#### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./screenshot --help\n\n# 查看版本信息\n$ ./screenshot version\n```\n\n#### 启动Screenshot 获取vnc服务器的屏幕截图\n```shell\n\n# imageFile  要生成的截图地址,暂时只支持jpeg格式(必填)\n# vncHost   要连接的vnc服务端地址(必填)\n# vncPort   要连接的vnc服务端端口(必填)\n# vncPassword  要连接的vnc服务端密码，不传则使用auth none\n\n$ ./screenshot --imageFile=./screen.jpeg --vncHost=127.0.0.1 --vncPort=5900 --vncPassword=12345612       \n```\n\n## 项目参考\n\n本项目参考了以下项目完成。\n* [vncproxy](https://github.com/amitbet/vncproxy)\n* [vnc2video](https://github.com/amitbet/vnc2video)\n* [rfbproto](https://github.com/rfbproto/rfbproto)\n\n## 交流\n\n我在做这个项目的过程中碰到了很多问题，查遍了互联网，缺少中文资料，大部分信息都是雷同的。\n所以我萌生了开源的想法，帮助更多有需要的人。\n\n我建立了一个可供交流的微信群，以便大家在使用的过程中碰到疑问，能有解答的地方。\n当然，如果你对vnc有兴趣，也可以加我微信，多多交流。\n欢迎各位贡献代码。\n\n![微信二维码](/docs/images/5bb8dbe702ce04b0bdde8c26583b152.jpg)\n\n\n\n"
  },
  {
    "path": "bin/.keep",
    "content": ""
  },
  {
    "path": "build",
    "content": "#!/usr/bin/env bash\nset -e\n\nexport  GOPROXY=https://proxy.golang.com.cn,direct\n\n\nhelp() {\n    echo \"使用方式:\"\n    echo \"  build.sh [-s app_name] [-v version] [-g go_bin]\"\n    echo \"参数详解:\"\n    echo \"  app_name 需要编译的应用名称,选项: proxy,player,recorder,video,screenshot.默认是所有应用,多个应用可以逗号分割\"\n    echo \"  version 编译后的文件版本号,默认为当前git的commit id\"\n    echo \"  go_bin 使用的golang程序\"\n    exit\n}\n\ngetOutFile(){\n  build_name=$1\n  output_dir=$2\n  output_file=\"${output_dir}\"/${build_name}\n}\n\n\n\nwhile getopts 's:v:g:h' OPT; do\n    case $OPT in\n        s) app_names=\"$OPTARG\";;\n        v) build_version=\"$OPTARG\";;\n        g) goBin=\"$OPTARG\";;\n        h) help;;\n        ?) help;;\n    esac\ndone\n\n\n## 获取当前环境\n## shellcheck disable=SC2046\ncd $(dirname \"$0\")/ || exit 1;\n\n\n# 如果go bin 不存在，则去环境变量中查找\nif [ ! -x \"$goBin\" ]; then\n    goBin=$(which go)\nfi\nif [ ! -x \"$goBin\" ]; then\n    echo \"No goBin found.\"\n    exit 2\nfi\n\n\n# 编译时间\nbuild_date=$(date +\"%Y-%m-%d %H:%M:%S\")\n# 编译时候当前git的commit id\nbuild_git=$(git rev-parse --short HEAD)\n# 编译的golang版本\ngo_version=$(${goBin} version)\n#编译版本\nif [ -z \"$build_version\" ]; then\n    build_version=\"$build_git\"\nfi\n\nif [ -z \"$app_names\" ]; then\n    app_names=\"proxy,player,recorder,video,screenshot\"\nfi\n\n\necho \"start to build project $app_names\" \"$build_date\"\n# shellcheck disable=SC2154\necho \"$go_version\"\npwd\nroot_dir=\"$(pwd)\"\n\nldflags=()\n\n# 链接时设置变量值\nldflags+=(\"-X\" \"\\\"main.BuildVersion=${build_version}\\\"\")\nldflags+=(\"-X\" \"\\\"github.com/osgochina/dmicro/easyservice.BuildVersion=${build_version}\\\"\")\nldflags+=(\"-X\" \"\\\"github.com/osgochina/dmicro/easyservice.BuildGoVersion=${go_version}\\\"\")\nldflags+=(\"-X\" \"\\\"github.com/osgochina/dmicro/easyservice.BuildGitCommitId=${build_git}\\\"\")\nldflags+=(\"-X\" \"\\\"github.com/osgochina/dmicro/easyservice.BuildTime=${build_date}\\\"\")\n\n\nfor app_name in $(echo $app_names | sed \"s/,/ /g\")\ndo\n  getOutFile $app_name \"$root_dir/bin\"\n  cd \"$root_dir/cmd/$app_name/\"\n  echo \"进入[$(pwd)]目录\"\n  ${goBin} build -v -ldflags \"${ldflags[*]}\"  -o \"${output_file}\"  || exit 1\n  echo \"build $app_name done.\"\ndone"
  },
  {
    "path": "canvas/canvas.go",
    "content": "package canvas\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"image\"\n\t\"image/color\"\n\t\"image/draw\"\n\t\"io\"\n)\n\nconst (\n\tBlockWidth  = 16\n\tBlockHeight = 16\n)\n\ntype VncCanvas struct {\n\tdraw.Image\n\timageBuffs     [2]draw.Image\n\tCursor         draw.Image\n\tCursorMask     [][]bool\n\tCursorBackup   draw.Image\n\tCursorOffset   *image.Point\n\tCursorLocation *image.Point\n\tDrawCursor     bool\n\tChanged        map[string]bool\n}\n\nfunc NewVncCanvas(width, height int) *VncCanvas {\n\twriteImg := NewRGBImage(image.Rect(0, 0, width, height))\n\tcanvas := &VncCanvas{\n\t\tImage: writeImg,\n\t}\n\treturn canvas\n}\n\n// Read 从链接中读取数据\nfunc (that *VncCanvas) Read(buf []byte) (int, error) {\n\treturn 0, nil\n}\n\n// Write 写入数据到链接\nfunc (that *VncCanvas) Write(buf []byte) (int, error) {\n\treturn 0, nil\n}\n\n// Close 关闭会话\nfunc (that *VncCanvas) Close() error {\n\treturn nil\n}\n\nfunc (that *VncCanvas) SetChanged(rect *rfb.Rectangle) {\n\tif that.Changed == nil {\n\t\tthat.Changed = make(map[string]bool)\n\t}\n\tfor x := int(rect.X) / BlockWidth; x*BlockWidth < int(rect.X+rect.Width); x++ {\n\t\tfor y := int(rect.Y) / BlockHeight; y*BlockHeight < int(rect.Y+rect.Height); y++ {\n\t\t\tkey := fmt.Sprintf(\"%d,%d\", x, y)\n\t\t\t//fmt.Println(\"setting block: \", key)\n\t\t\tthat.Changed[key] = true\n\t\t}\n\t}\n}\n\nfunc (that *VncCanvas) Reset(rect *rfb.Rectangle) {\n\tthat.Changed = nil\n}\n\nfunc (that *VncCanvas) RemoveCursor() image.Image {\n\tif that.Cursor == nil || that.CursorLocation == nil {\n\t\treturn that.Image\n\t}\n\tif !that.DrawCursor {\n\t\treturn that.Image\n\t}\n\trect := that.Cursor.Bounds()\n\tloc := that.CursorLocation\n\timg := that.Image\n\tfor y := rect.Min.Y; y < int(rect.Max.Y); y++ {\n\t\tfor x := rect.Min.X; x < int(rect.Max.X); x++ {\n\t\t\t// offset := y*int(rect.Width) + x\n\t\t\t// if bitmask[y*int(scanLine)+x/8]&(1<<uint(7-x%8)) > 0 {\n\t\t\tcol := that.CursorBackup.At(x, y)\n\t\t\t//mask := c.CursorMask.At(x, y).(color.RGBA)\n\t\t\tmask := that.CursorMask[x][y]\n\t\t\t//logger.Info(\"Drawing Cursor: \", x, y, col, mask)\n\t\t\tif mask {\n\t\t\t\t//logger.Info(\"Drawing Cursor for real: \", x, y, col)\n\t\t\t\timg.Set(x+loc.X-that.CursorOffset.X, y+loc.Y-that.CursorOffset.Y, col)\n\t\t\t}\n\t\t\t// \t//logger.Tracef(\"CursorPseudoEncoding.Read: setting pixel: (%d,%d) %v\", x+int(rect.X), y+int(rect.Y), colors[offset])\n\t\t\t// }\n\t\t}\n\t}\n\treturn img\n}\n\nfunc (that *VncCanvas) PaintCursor() image.Image {\n\tif that.Cursor == nil || that.CursorLocation == nil {\n\t\treturn that.Image\n\t}\n\tif !that.DrawCursor {\n\t\treturn that.Image\n\t}\n\trect := that.Cursor.Bounds()\n\tif that.CursorBackup == nil {\n\t\tthat.CursorBackup = image.NewRGBA(that.Cursor.Bounds())\n\t}\n\n\tloc := that.CursorLocation\n\timg := that.Image\n\tfor y := rect.Min.Y; y < int(rect.Max.Y); y++ {\n\t\tfor x := rect.Min.X; x < int(rect.Max.X); x++ {\n\t\t\t// offset := y*int(rect.Width) + x\n\t\t\t// if bitmask[y*int(scanLine)+x/8]&(1<<uint(7-x%8)) > 0 {\n\t\t\tcol := that.Cursor.At(x, y)\n\t\t\t//mask := c.CursorMask.At(x, y).(RGBColor)\n\t\t\tmask := that.CursorMask[x][y]\n\t\t\tbackup := that.Image.At(x+loc.X-that.CursorOffset.X, y+loc.Y-that.CursorOffset.Y)\n\t\t\t//c.CursorBackup.Set(x, y, backup)\n\t\t\t//backup the previous data at this point\n\n\t\t\t//logger.Info(\"Drawing Cursor: \", x, y, col, mask)\n\t\t\tif mask {\n\n\t\t\t\tthat.CursorBackup.Set(x, y, backup)\n\t\t\t\t//logger.Info(\"Drawing Cursor for real: \", x, y, col)\n\t\t\t\timg.Set(x+loc.X-that.CursorOffset.X, y+loc.Y-that.CursorOffset.Y, col)\n\t\t\t}\n\t\t\t// \t//logger.Tracef(\"CursorPseudoEncoding.Read: setting pixel: (%d,%d) %v\", x+int(rect.X), y+int(rect.Y), colors[offset])\n\t\t\t// }\n\t\t}\n\t}\n\treturn img\n}\n\n// FillRect 为指定的矩形区域填充颜色\nfunc (that *VncCanvas) FillRect(rect *image.Rectangle, c color.Color) {\n\tfor x := rect.Min.X; x < rect.Max.X; x++ {\n\t\tfor y := rect.Min.Y; y < rect.Max.Y; y++ {\n\t\t\tthat.Set(x, y, c)\n\t\t}\n\t}\n}\n\n// ReadColor Read unmarshal color from conn\nfunc (that *VncCanvas) ReadColor(c io.Reader, pf *rfb.PixelFormat) (*color.RGBA, error) {\n\tif pf.TrueColor == 0 {\n\t\treturn nil, errors.New(\"support for non true color formats was not implemented\")\n\t}\n\torder := pf.Order()\n\tvar pixel uint32\n\n\tswitch pf.BPP {\n\tcase 8:\n\t\tvar px uint8\n\t\tif err := binary.Read(c, order, &px); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpixel = uint32(px)\n\tcase 16:\n\t\tvar px uint16\n\t\tif err := binary.Read(c, order, &px); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpixel = uint32(px)\n\tcase 32:\n\t\tvar px uint32\n\t\tif err := binary.Read(c, order, &px); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpixel = uint32(px)\n\t}\n\trgb := color.RGBA{\n\t\tR: uint8((pixel >> pf.RedShift) & uint32(pf.RedMax)),\n\t\tG: uint8((pixel >> pf.GreenShift) & uint32(pf.GreenMax)),\n\t\tB: uint8((pixel >> pf.BlueShift) & uint32(pf.BlueMax)),\n\t\tA: 1,\n\t}\n\n\treturn &rgb, nil\n}\n\nfunc (that *VncCanvas) DecodeRaw(reader io.Reader, pf *rfb.PixelFormat, rect *rfb.Rectangle) error {\n\tfor y := 0; y < int(rect.Height); y++ {\n\t\tfor x := 0; x < int(rect.Width); x++ {\n\t\t\tcol, err := that.ReadColor(reader, pf)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tthat.Set(int(rect.X)+x, int(rect.Y)+y, col)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc MakeRect(x, y, width, height int) image.Rectangle {\n\treturn image.Rectangle{Min: image.Point{X: x, Y: y}, Max: image.Point{X: x + width, Y: y + height}}\n}\n\nfunc MakeRectFromVncRect(rect *rfb.Rectangle) image.Rectangle {\n\treturn MakeRect(int(rect.X), int(rect.Y), int(rect.Width), int(rect.Height))\n}\n"
  },
  {
    "path": "canvas/rgb_image.go",
    "content": "package canvas\n\nimport (\n\t\"image\"\n\t\"image/color\"\n)\n\ntype RGBColor struct {\n\tR, G, B uint8\n}\n\nfunc (that RGBColor) RGBA() (r, g, b, a uint32) {\n\treturn uint32(that.R), uint32(that.G), uint32(that.B), 1\n}\n\ntype RGBImage struct {\n\t// Pix holds the image's pixels, in R, G, B, A order. The pixel at\n\t// (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3].\n\tPix []uint8\n\t// Stride is the Pix stride (in bytes) between vertically adjacent pixels.\n\tStride int\n\t// Rect is the image's bounds.\n\tRect image.Rectangle\n}\n\nfunc (that RGBImage) ColorModel() color.Model {\n\treturn nil\n}\n\nfunc (that RGBImage) Bounds() image.Rectangle {\n\treturn that.Rect\n}\n\nfunc (that RGBImage) At(x, y int) color.Color {\n\tcol := that.RGBAt(x, y)\n\treturn color.RGBA{R: col.R, G: col.G, B: col.B, A: 1}\n}\n\nfunc (that *RGBImage) RGBAt(x, y int) *RGBColor {\n\tif !(image.Point{X: x, Y: y}.In(that.Rect)) {\n\t\treturn &RGBColor{}\n\t}\n\ti := that.PixOffset(x, y)\n\treturn &RGBColor{that.Pix[i+0], that.Pix[i+1], that.Pix[i+2]}\n}\n\nfunc (that *RGBImage) PixOffset(x, y int) int {\n\treturn (y-that.Rect.Min.Y)*that.Stride + (x-that.Rect.Min.X)*3\n}\n\nfunc (that RGBImage) Set(x, y int, c color.Color) {\n\tif !(image.Point{X: x, Y: y}.In(that.Rect)) {\n\t\treturn\n\t}\n\ti := that.PixOffset(x, y)\n\tc1 := color.RGBAModel.Convert(c).(color.RGBA)\n\tthat.Pix[i+0] = c1.R\n\tthat.Pix[i+1] = c1.G\n\tthat.Pix[i+2] = c1.B\n}\n\nfunc (that *RGBImage) SetRGB(x, y int, c color.RGBA) {\n\tif !(image.Point{X: x, Y: y}.In(that.Rect)) {\n\t\treturn\n\t}\n\ti := that.PixOffset(x, y)\n\tthat.Pix[i+0] = c.R\n\tthat.Pix[i+1] = c.G\n\tthat.Pix[i+2] = c.B\n}\n\nfunc NewRGBImage(r image.Rectangle) *RGBImage {\n\tw, h := r.Dx(), r.Dy()\n\tbuf := make([]uint8, 3*w*h)\n\treturn &RGBImage{buf, 3 * w, r}\n}\n"
  },
  {
    "path": "cmd/player/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/os/gcfg\"\n\t\"github.com/gogf/gf/v2/os/gfile\"\n\t\"github.com/gogf/gf/v2/text/gstr\"\n\t\"github.com/osgochina/dmicro/easyservice\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"golang.org/x/net/context\"\n\t\"os\"\n)\n\nvar (\n\thelpContent = gstr.TrimLeft(`\nUSAGE\n\t./server [start|stop|quit] [tcpServer|wsServer] [OPTION]\nOPTION\n\t--rbsFile       使用的rbs文件地址  必传\n\t--tcpHost       本地监听的tcp协议地址 默认0.0.0.0\n\t--tcpPort       本地监听的tcp协议端口 默认8989\n\t--proxyPassword 连接到proxy的密码   不传入密码则使用auth none\n\t--wsHost        启动websocket服务的本地地址  默认 0.0.0.0\n\t--wsPort        启动websocket服务的本地端口 默认8988\n\t--wsPath        启动websocket服务的url path 默认'/'\n\t--debug         是否开启debug 默认debug=false\n\t-d,--daemon     使用守护进程模式启动\n\t--pid           设置pid文件的地址，默认是/tmp/[server].pid\n\t-h,--help       获取帮助信息\n\t-v,--version    获取编译版本信息\n\t\nEXAMPLES\n\t/path/to/server \n\t/path/to/server start --env=dev --debug=true --pid=/tmp/server.pid\n\t/path/to/server start -c=config.product.toml\n\t/path/to/server start tcpServer,wsServer --config=config.product.toml\n\t/path/to/server start wsServer  --rbsFile=/path/to/foo.rbs\n                                    --wsHost=0.0.0.0\n                                    --wsPort=8988\n                                    --proxyPassword=12345612\n                                    --debug\n\t/path/to/server start tcpServer --rbsFile=/path/to/foo.rbs\n                                    --tcpHost=0.0.0.0\n                                    --tcpPort=8989\n                                    --proxyPassword=12345612\n                                    --debug\n\t/path/to/server stop\n\t/path/to/server quit\n\t/path/to/server reload\n\t/path/to/server version\n\t/path/to/server help\n`)\n)\n\nfunc main() {\n\teasyservice.Authors = \"ClownFish\"\n\teasyservice.SetHelpContent(helpContent)\n\teasyservice.SetOptions(\n\t\tmap[string]bool{\n\t\t\t\"tcpHost\":       true, //本地监听的tcp协议地址 默认0.0.0.0\n\t\t\t\"tcpPort\":       true, //本地监听的tcp协议端口 默认8989\n\t\t\t\"proxyPassword\": true, //连接到proxy的密码   不传入密码则使用auth none\n\t\t\t\"wsHost\":        true, //启动websocket服务的本地地址  默认 0.0.0.0\n\t\t\t\"wsPort\":        true, //启动websocket服务的本地端口 默认8988\n\t\t\t\"wsPath\":        true, //启动websocket服务的url path 默认'/'\n\t\t\t\"rbsFile\":       true, // 使用的rbs文件地址  必传\n\t\t})\n\n\teasyservice.Setup(func(svr *easyservice.EasyService) {\n\t\t//注册服务停止时要执行法方法\n\t\tsvr.BeforeStop(func(service *easyservice.EasyService) bool {\n\t\t\tfmt.Println(\"Vnc player server stop\")\n\t\t\treturn true\n\t\t})\n\t\tcfg := svr.Config()\n\t\trbsFile := svr.CmdParser().GetOpt(\"rbsFile\", \"\")\n\t\tif len(rbsFile.String()) <= 0 || !gfile.Exists(rbsFile.String()) {\n\t\t\tsvr.Help()\n\t\t\tos.Exit(0)\n\t\t}\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"rbsFile\", rbsFile.String())\n\n\t\tlogger.SetDebug(cfg.MustGet(context.TODO(), \"Debug\").Bool())\n\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"tcpHost\", svr.CmdParser().GetOpt(\"tcpHost\", \"0.0.0.0\").String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"tcpPort\", svr.CmdParser().GetOpt(\"tcpPort\", 8989).Int())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"proxyPassword\", svr.CmdParser().GetOpt(\"proxyPassword\", \"\").String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"wsHost\", svr.CmdParser().GetOpt(\"wsHost\", \"0.0.0.0\").String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"wsPort\", svr.CmdParser().GetOpt(\"wsPort\", 8988).Int())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"wsPath\", svr.CmdParser().GetOpt(\"wsPath\", \"/\").String())\n\n\t\tif svr.SandboxNames().ContainsI(\"tcpserver\") {\n\t\t\tsvr.AddSandBox(NewTcpSandBox(cfg))\n\t\t\treturn\n\t\t}\n\t\tif svr.SandboxNames().ContainsI(\"wsserver\") {\n\t\t\tsvr.AddSandBox(NewWSSandBox(cfg))\n\t\t\treturn\n\t\t}\n\t\tsvr.AddSandBox(NewTcpSandBox(cfg))\n\t\tsvr.AddSandBox(NewWSSandBox(cfg))\n\t})\n}\n"
  },
  {
    "path": "cmd/player/tcpServer.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/os/gcfg\"\n\t\"github.com/gogf/gf/v2/os/glog\"\n\t\"github.com/osgochina/dmicro/drpc\"\n\t\"github.com/osgochina/dmicro/drpc/status\"\n\t\"github.com/osgochina/dmicro/easyservice\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/security\"\n\t\"github.com/vprix/vncproxy/session\"\n\t\"github.com/vprix/vncproxy/vnc\"\n\t\"golang.org/x/net/context\"\n\t\"io\"\n\t\"net\"\n)\n\n// TcpSandBox  Tcp的服务\ntype TcpSandBox struct {\n\tid      int\n\tname    string\n\tcfg     *gcfg.Config\n\tservice *easyservice.EasyService\n\tlis     net.Listener\n\tclosed  chan struct{}\n}\n\n// NewTcpSandBox 创建一个默认的服务沙盒\nfunc NewTcpSandBox(cfg *gcfg.Config) *TcpSandBox {\n\tid := easyservice.GetNextSandBoxId()\n\tsBox := &TcpSandBox{\n\t\tid:     id,\n\t\tname:   fmt.Sprintf(\"tcp_%d\", id),\n\t\tcfg:    cfg,\n\t\tclosed: make(chan struct{}),\n\t}\n\treturn sBox\n}\n\nfunc (that *TcpSandBox) ID() int {\n\treturn that.id\n}\n\nfunc (that *TcpSandBox) Name() string {\n\treturn that.name\n}\n\nfunc (that *TcpSandBox) Setup() error {\n\tvar err error\n\taddr := fmt.Sprintf(\"%s:%d\", that.cfg.MustGet(context.TODO(), \"tcpHost\"), that.cfg.MustGet(context.TODO(), \"tcpPort\"))\n\tthat.lis, err = net.Listen(\"tcp\", addr)\n\tif err != nil {\n\t\tglog.Fatalf(context.TODO(), \"Error listen. %v\", err)\n\t}\n\tfmt.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\"))\n\tsecurityHandlers := []rfb.ISecurityHandler{&security.ServerAuthNone{}}\n\tif len(that.cfg.MustGet(context.TODO(), \"proxyPassword\").Bytes()) > 0 {\n\t\tsecurityHandlers = append(securityHandlers, &security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), \"proxyPassword\").Bytes()})\n\t}\n\tfor {\n\t\tconn, err := that.lis.Accept()\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-that.closed:\n\t\t\t\treturn drpc.ErrListenClosed\n\t\t\tdefault:\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tgo func(c net.Conn) {\n\t\t\tdefer func() {\n\t\t\t\t//捕获错误，并且继续执行\n\t\t\t\tif p := recover(); p != nil {\n\t\t\t\t\terr = fmt.Errorf(\"panic:%v\\n%s\", p, status.PanicStackTrace())\n\t\t\t\t}\n\t\t\t}()\n\t\t\tsvrSession := session.NewServerSession(\n\t\t\t\trfb.OptSecurityHandlers(securityHandlers...),\n\t\t\t\trfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\t\t\treturn c, nil\n\t\t\t\t}),\n\t\t\t)\n\t\t\tplay := vnc.NewPlayer(that.cfg.MustGet(context.TODO(), \"rbsFile\").String(), svrSession)\n\t\t\terr = play.Start()\n\t\t\tif err != nil {\n\t\t\t\tglog.Warning(context.TODO(), err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tglog.Info(context.TODO(), \"play finished\")\n\t\t}(conn)\n\n\t}\n}\n\nfunc (that *TcpSandBox) Shutdown() error {\n\tclose(that.closed)\n\treturn that.lis.Close()\n}\n\nfunc (that *TcpSandBox) Service() *easyservice.EasyService {\n\treturn that.service\n}\n"
  },
  {
    "path": "cmd/player/wsServer.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/frame/g\"\n\t\"github.com/gogf/gf/v2/net/ghttp\"\n\t\"github.com/gogf/gf/v2/os/gcfg\"\n\t\"github.com/gogf/gf/v2/os/glog\"\n\t\"github.com/osgochina/dmicro/easyservice\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/security\"\n\t\"github.com/vprix/vncproxy/session\"\n\t\"github.com/vprix/vncproxy/vnc\"\n\t\"golang.org/x/net/context\"\n\t\"golang.org/x/net/websocket\"\n\t\"io\"\n)\n\n// WSSandBox  Tcp的服务\ntype WSSandBox struct {\n\tid      int\n\tname    string\n\tcfg     *gcfg.Config\n\tservice *easyservice.EasyService\n\tsvr     *ghttp.Server\n}\n\n// NewWSSandBox 创建一个默认的服务沙盒\nfunc NewWSSandBox(cfg *gcfg.Config) *WSSandBox {\n\tid := easyservice.GetNextSandBoxId()\n\tsBox := &WSSandBox{\n\t\tid:   id,\n\t\tname: fmt.Sprintf(\"ws_%d\", id),\n\t\tcfg:  cfg,\n\t}\n\treturn sBox\n}\n\nfunc (that *WSSandBox) ID() int {\n\treturn that.id\n}\n\nfunc (that *WSSandBox) Name() string {\n\treturn that.name\n}\n\nfunc (that *WSSandBox) Setup() error {\n\n\tthat.svr = g.Server()\n\tthat.svr.BindHandler(that.cfg.MustGet(context.TODO(), \"wsPath\", \"/\").String(), func(r *ghttp.Request) {\n\t\th := websocket.Handler(func(conn *websocket.Conn) {\n\t\t\tconn.PayloadType = websocket.BinaryFrame\n\n\t\t\tsecurityHandlers := []rfb.ISecurityHandler{&security.ServerAuthNone{}}\n\t\t\tif len(that.cfg.MustGet(context.TODO(), \"proxyPassword\").Bytes()) > 0 {\n\t\t\t\tsecurityHandlers = []rfb.ISecurityHandler{&security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), \"proxyPassword\").Bytes()}}\n\t\t\t}\n\t\t\tsvrSession := session.NewServerSession(\n\t\t\t\trfb.OptSecurityHandlers(securityHandlers...),\n\t\t\t\trfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\t\t\treturn conn, nil\n\t\t\t\t}),\n\t\t\t)\n\t\t\tplay := vnc.NewPlayer(that.cfg.MustGet(context.TODO(), \"rfbFile\").String(), svrSession)\n\t\t\terr := play.Start()\n\t\t\tif err != nil {\n\t\t\t\tglog.Warning(context.TODO(), err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tglog.Info(context.TODO(), \"play session end\")\n\t\t})\n\t\th.ServeHTTP(r.Response.Writer, r.Request)\n\t})\n\tthat.svr.SetAddr(fmt.Sprintf(\"%s:%d\", that.cfg.MustGet(context.TODO(), \"wsHost\").String(), that.cfg.MustGet(context.TODO(), \"wsPort\").Int()))\n\treturn that.svr.Start()\n}\n\nfunc (that *WSSandBox) Shutdown() error {\n\treturn that.svr.Shutdown()\n}\n\nfunc (that *WSSandBox) Service() *easyservice.EasyService {\n\treturn that.service\n}\n"
  },
  {
    "path": "cmd/proxy/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/os/gcfg\"\n\t\"github.com/gogf/gf/v2/text/gstr\"\n\t\"github.com/osgochina/dmicro/easyservice\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"golang.org/x/net/context\"\n\t\"os\"\n)\n\nvar (\n\thelpContent = gstr.TrimLeft(`\nUSAGE\n\t./proxy [start|stop|quit] [tcpServer|wsServer] [OPTION]\nOPTION\n\t--vncHost       要连接的vnc服务端地址  必传\n\t--vncPort       要连接的vnc服务端端口 必传\n\t--vncPassword   要连接的vnc服务端密码 不传则使用auth none\n\t--tcpHost       本地监听的tcp协议地址 默认0.0.0.0\n\t--tcpPort       本地监听的tcp协议端口 默认8989\n\t--proxyPassword 连接到proxy的密码   不传入密码则使用auth none\n\t--wsHost        启动websocket服务的本地地址  默认 0.0.0.0\n\t--wsPort        启动websocket服务的本地端口 默认8988\n\t--wsPath        启动websocket服务的url path 默认'/'\n\t--debug         是否开启debug 默认debug=false\n\t-d,--daemon     使用守护进程模式启动\n\t--pid           设置pid文件的地址，默认是/tmp/[server].pid\n\t-h,--help       获取帮助信息\n\t-v,--version    获取编译版本信息\n\t\nEXAMPLES\n\t/path/to/proxy \n\t/path/to/proxy start --env=dev --debug=true --pid=/tmp/server.pid\n\t/path/to/proxy start -c=config.product.toml\n\t/path/to/proxy start tcpServer,wsServer --config=config.product.toml\n\t/path/to/proxy start wsServer  --vncHost=192.168.1.2 \n                                    --vncPort=5901\n                                    --vncPassword=vprix\n                                    --wsHost=0.0.0.0\n                                    --wsPort=8988\n                                    --wsPath=/\n                                    --proxyPassword=12345612\n                                    --debug\n\t/path/to/proxy start tcpServer --vncHost=192.168.1.2 \n                                    --vncPort=5901\n                                    --vncPassword=vprix\n                                    --tcpHost=0.0.0.0\n                                    --tcpPort=8989\n                                    --proxyPassword=12345612\n                                    --debug\n\t/path/to/proxy stop\n\t/path/to/proxy quit\n\t/path/to/proxy reload\n\t/path/to/proxy version\n\t/path/to/proxy help\n`)\n)\n\nfunc main() {\n\teasyservice.Authors = \"ClownFish\"\n\teasyservice.SetHelpContent(helpContent)\n\teasyservice.SetOptions(\n\t\tmap[string]bool{\n\t\t\t\"tcpHost\":       true, //本地监听的tcp协议地址 默认0.0.0.0\n\t\t\t\"tcpPort\":       true, //本地监听的tcp协议端口 默认8989\n\t\t\t\"proxyPassword\": true, //连接到proxy的密码   不传入密码则使用auth none\n\t\t\t\"wsHost\":        true, //启动websocket服务的本地地址  默认 0.0.0.0\n\t\t\t\"wsPort\":        true, //启动websocket服务的本地端口 默认8988\n\t\t\t\"wsPath\":        true, //启动websocket服务的url path 默认'/'\n\t\t\t\"vncHost\":       true, // 要连接的vnc服务端地址  必传\n\t\t\t\"vncPort\":       true, // 要连接的vnc服务端端口 必传\n\t\t\t\"vncPassword\":   true, // 要连接的vnc服务端密码 不传则使用auth none\n\t\t})\n\n\teasyservice.Setup(func(svr *easyservice.EasyService) {\n\t\t//注册服务停止时要执行法方法\n\t\tsvr.BeforeStop(func(service *easyservice.EasyService) bool {\n\t\t\tfmt.Println(\"Vnc proxy server stop\")\n\t\t\treturn true\n\t\t})\n\t\tcfg := svr.Config()\n\t\tvncHost := svr.CmdParser().GetOpt(\"vncHost\", \"\")\n\t\tif len(vncHost.String()) <= 0 {\n\t\t\tsvr.Help()\n\t\t\tos.Exit(0)\n\t\t}\n\t\tvncPort := svr.CmdParser().GetOpt(\"vncPort\", 0)\n\t\tif vncPort.Int() <= 0 {\n\t\t\tsvr.Help()\n\t\t\tos.Exit(0)\n\t\t}\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"vncHost\", vncHost.String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"vncPort\", vncPort.Int())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"vncPassword\", svr.CmdParser().GetOpt(\"vncPassword\", \"\").String())\n\n\t\tlogger.SetDebug(cfg.MustGet(context.TODO(), \"Debug\").Bool())\n\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"tcpHost\", svr.CmdParser().GetOpt(\"tcpHost\", \"0.0.0.0\").String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"tcpPort\", svr.CmdParser().GetOpt(\"tcpPort\", 8989).Int())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"proxyPassword\", svr.CmdParser().GetOpt(\"proxyPassword\", \"\").String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"wsHost\", svr.CmdParser().GetOpt(\"wsHost\", \"0.0.0.0\").String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"wsPort\", svr.CmdParser().GetOpt(\"wsPort\", 8988).Int())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"wsPath\", svr.CmdParser().GetOpt(\"wsPath\", \"/\").String())\n\n\t\tif svr.SandboxNames().ContainsI(\"tcpserver\") {\n\t\t\tsvr.AddSandBox(NewTcpSandBox(cfg))\n\t\t\treturn\n\t\t}\n\t\tif svr.SandboxNames().ContainsI(\"wsserver\") {\n\t\t\tsvr.AddSandBox(NewWSSandBox(cfg))\n\t\t\treturn\n\t\t}\n\t\tsvr.AddSandBox(NewTcpSandBox(cfg))\n\t\tsvr.AddSandBox(NewWSSandBox(cfg))\n\t})\n}\n"
  },
  {
    "path": "cmd/proxy/tcpServer.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/container/gmap\"\n\t\"github.com/gogf/gf/v2/os/gcfg\"\n\t\"github.com/gogf/gf/v2/os/glog\"\n\t\"github.com/osgochina/dmicro/drpc\"\n\t\"github.com/osgochina/dmicro/drpc/status\"\n\t\"github.com/osgochina/dmicro/easyservice\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/security\"\n\t\"github.com/vprix/vncproxy/session\"\n\t\"github.com/vprix/vncproxy/vnc\"\n\t\"golang.org/x/net/context\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n// TcpSandBox  Tcp的服务\ntype TcpSandBox struct {\n\tid       int\n\tname     string\n\tcfg      *gcfg.Config\n\tservice  *easyservice.EasyService\n\tlis      net.Listener\n\tclosed   chan struct{}\n\tproxyHub *gmap.StrAnyMap\n}\n\n// NewTcpSandBox 创建一个默认的服务沙盒\nfunc NewTcpSandBox(cfg *gcfg.Config) *TcpSandBox {\n\tid := easyservice.GetNextSandBoxId()\n\tsBox := &TcpSandBox{\n\t\tid:       id,\n\t\tname:     fmt.Sprintf(\"tcp_%d\", id),\n\t\tcfg:      cfg,\n\t\tclosed:   make(chan struct{}),\n\t\tproxyHub: gmap.NewStrAnyMap(true),\n\t}\n\treturn sBox\n}\n\nfunc (that *TcpSandBox) ID() int {\n\treturn that.id\n}\n\nfunc (that *TcpSandBox) Name() string {\n\treturn that.name\n}\n\nfunc (that *TcpSandBox) Setup() error {\n\tvar err error\n\taddr := fmt.Sprintf(\"%s:%d\", that.cfg.MustGet(context.TODO(), \"tcpHost\").String(), that.cfg.MustGet(context.TODO(), \"tcpPort\").Int())\n\tthat.lis, err = net.Listen(\"tcp\", addr)\n\tif err != nil {\n\t\tglog.Fatalf(context.TODO(), \"Error listen. %v\", err)\n\t}\n\tfmt.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\"))\n\tsecurityHandlers := []rfb.ISecurityHandler{\n\t\t&security.ServerAuthNone{},\n\t}\n\tif len(that.cfg.MustGet(context.TODO(), \"proxyPassword\").Bytes()) > 0 {\n\t\tsecurityHandlers = append(securityHandlers, &security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), \"proxyPassword\").Bytes()})\n\t}\n\ttargetCfg := rfb.TargetConfig{\n\t\tHost:     that.cfg.MustGet(context.TODO(), \"vncHost\").String(),\n\t\tPort:     that.cfg.MustGet(context.TODO(), \"vncPort\").Int(),\n\t\tPassword: that.cfg.MustGet(context.TODO(), \"vncPassword\").Bytes(),\n\t}\n\tfor {\n\t\tconn, err := that.lis.Accept()\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-that.closed:\n\t\t\t\treturn drpc.ErrListenClosed\n\t\t\tdefault:\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tgo func(c net.Conn) {\n\t\t\tdefer func() {\n\t\t\t\t//捕获错误，并且继续执行\n\t\t\t\tif p := recover(); p != nil {\n\t\t\t\t\terr = fmt.Errorf(\"panic:%v\\n%s\", p, status.PanicStackTrace())\n\t\t\t\t}\n\t\t\t}()\n\t\t\tsvrSess := session.NewServerSession(\n\t\t\t\trfb.OptDesktopName([]byte(\"Vprix VNC Proxy\")),\n\t\t\t\trfb.OptHeight(768),\n\t\t\t\trfb.OptWidth(1024),\n\t\t\t\trfb.OptSecurityHandlers(securityHandlers...),\n\t\t\t\trfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\t\t\treturn c, nil\n\t\t\t\t}),\n\t\t\t)\n\t\t\ttimeout := 10 * time.Second\n\t\t\tnetwork := \"tcp\"\n\t\t\tcliSess := session.NewClient(\n\t\t\t\trfb.OptSecurityHandlers([]rfb.ISecurityHandler{&security.ClientAuthVNC{Password: targetCfg.Password}}...),\n\t\t\t\trfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\t\t\treturn net.DialTimeout(network, targetCfg.Addr(), timeout)\n\t\t\t\t}),\n\t\t\t)\n\t\t\tp := vnc.NewVncProxy(cliSess, svrSess)\n\t\t\tremoteKey := c.RemoteAddr().String()\n\t\t\tthat.proxyHub.Set(remoteKey, p)\n\t\t\terr = p.Start()\n\t\t\tif err != nil {\n\t\t\t\tglog.Warning(context.TODO(), err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tglog.Info(context.TODO(), \"proxy session closed\")\n\t\t}(conn)\n\n\t}\n}\n\nfunc (that *TcpSandBox) Shutdown() error {\n\tclose(that.closed)\n\treturn that.lis.Close()\n}\n\nfunc (that *TcpSandBox) Service() *easyservice.EasyService {\n\treturn that.service\n}\n"
  },
  {
    "path": "cmd/proxy/wsServer.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/container/gmap\"\n\t\"github.com/gogf/gf/v2/frame/g\"\n\t\"github.com/gogf/gf/v2/net/ghttp\"\n\t\"github.com/gogf/gf/v2/os/gcfg\"\n\t\"github.com/gogf/gf/v2/os/glog\"\n\t\"github.com/osgochina/dmicro/easyservice\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/security\"\n\t\"github.com/vprix/vncproxy/session\"\n\t\"github.com/vprix/vncproxy/vnc\"\n\t\"golang.org/x/net/websocket\"\n\t\"io\"\n\t\"net\"\n)\n\n// WSSandBox  Tcp的服务\ntype WSSandBox struct {\n\tid       int\n\tname     string\n\tcfg      *gcfg.Config\n\tservice  *easyservice.EasyService\n\tsvr      *ghttp.Server\n\tproxyHub *gmap.StrAnyMap\n}\n\n// NewWSSandBox 创建一个默认的服务沙盒\nfunc NewWSSandBox(cfg *gcfg.Config) *WSSandBox {\n\tid := easyservice.GetNextSandBoxId()\n\tsBox := &WSSandBox{\n\t\tid:       id,\n\t\tname:     fmt.Sprintf(\"ws_%d\", id),\n\t\tcfg:      cfg,\n\t\tproxyHub: gmap.NewStrAnyMap(true),\n\t}\n\treturn sBox\n}\n\nfunc (that *WSSandBox) ID() int {\n\treturn that.id\n}\n\nfunc (that *WSSandBox) Name() string {\n\treturn that.name\n}\n\nfunc (that *WSSandBox) Setup() error {\n\n\tthat.svr = g.Server()\n\tthat.svr.BindHandler(that.cfg.MustGet(context.TODO(), \"wsPath\", \"/\").String(), func(r *ghttp.Request) {\n\t\th := websocket.Handler(func(conn *websocket.Conn) {\n\t\t\tconn.PayloadType = websocket.BinaryFrame\n\t\t\tsecurityHandlers := []rfb.ISecurityHandler{\n\t\t\t\t&security.ServerAuthNone{},\n\t\t\t}\n\t\t\tif len(that.cfg.MustGet(context.TODO(), \"proxyPassword\").Bytes()) > 0 {\n\t\t\t\tsecurityHandlers = append(securityHandlers, &security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), \"proxyPassword\").Bytes()})\n\t\t\t}\n\t\t\ttargetCfg := rfb.TargetConfig{\n\t\t\t\tHost:     that.cfg.MustGet(context.TODO(), \"vncHost\").String(),\n\t\t\t\tPort:     that.cfg.MustGet(context.TODO(), \"vncPort\").Int(),\n\t\t\t\tPassword: that.cfg.MustGet(context.TODO(), \"vncPassword\").Bytes(),\n\t\t\t}\n\t\t\tvar err error\n\t\t\tsvrSess := session.NewServerSession(\n\t\t\t\trfb.OptDesktopName([]byte(\"Vprix VNC Proxy\")),\n\t\t\t\trfb.OptHeight(768),\n\t\t\t\trfb.OptWidth(1024),\n\t\t\t\trfb.OptSecurityHandlers(securityHandlers...),\n\t\t\t\trfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\t\t\treturn conn, nil\n\t\t\t\t}),\n\t\t\t)\n\t\t\tcliSess := session.NewClient(\n\t\t\t\trfb.OptSecurityHandlers([]rfb.ISecurityHandler{&security.ClientAuthVNC{Password: targetCfg.Password}}...),\n\t\t\t\trfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\t\t\treturn net.DialTimeout(targetCfg.GetNetwork(), targetCfg.Addr(), targetCfg.GetTimeout())\n\t\t\t\t}),\n\t\t\t)\n\t\t\tp := vnc.NewVncProxy(cliSess, svrSess)\n\t\t\tremoteKey := conn.RemoteAddr().String()\n\t\t\tthat.proxyHub.Set(remoteKey, p)\n\t\t\terr = p.Start()\n\t\t\tif err != nil {\n\t\t\t\tglog.Warning(context.TODO(), err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tglog.Info(context.TODO(), \"proxy session end\")\n\t\t})\n\t\th.ServeHTTP(r.Response.Writer, r.Request)\n\t})\n\tthat.svr.SetAddr(fmt.Sprintf(\"%s:%d\", that.cfg.MustGet(context.TODO(), \"wsHost\").String(), that.cfg.MustGet(context.TODO(), \"wsPort\").Int()))\n\treturn that.svr.Start()\n}\n\nfunc (that *WSSandBox) Shutdown() error {\n\treturn that.svr.Shutdown()\n}\n\nfunc (that *WSSandBox) Service() *easyservice.EasyService {\n\treturn that.service\n}\n"
  },
  {
    "path": "cmd/recorder/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/os/gcfg\"\n\t\"github.com/gogf/gf/v2/text/gstr\"\n\t\"github.com/osgochina/dmicro/easyservice\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"golang.org/x/net/context\"\n\t\"os\"\n)\n\nvar (\n\thelpContent = gstr.TrimLeft(`\nUSAGE\n\t./recorder [start|stop|quit]  [OPTION]\nOPTION\n\t--rbsFile       使用的rbs文件地址  必传\n\t--vncHost       要连接的vnc服务端地址  必传\n\t--vncPort       要连接的vnc服务端端口 必传\n\t--vncPassword   要连接的vnc服务端密码 不传则使用auth none\n\t--debug         是否开启debug 默认debug=false\n\t-d,--daemon     使用守护进程模式启动\n\t--pid           设置pid文件的地址，默认是/tmp/[server].pid\n\t-h,--help       获取帮助信息\n\t-v,--version    获取编译版本信息\n\t\nEXAMPLES\n\t/path/to/recorder \n\t/path/to/recorder start --env=dev --debug=true --pid=/tmp/server.pid\n\t/path/to/recorder start -c=config.product.toml\n\t/path/to/recorder start --config=config.product.toml\n\t/path/to/recorder start --rbsFile=/path/to/foo.rbs\n\t\t\t\t\t\t\t--vncHost=192.168.1.2 \n\t\t\t\t\t\t\t--vncPort=5901\n\t\t\t\t\t\t\t--vncPassword=vprix\n\t\t\t\t\t\t\t--debug\n\t/path/to/server stop\n\t/path/to/server quit\n\t/path/to/server reload\n\t/path/to/server version\n\t/path/to/server help\n`)\n)\n\nfunc main() {\n\teasyservice.Authors = \"ClownFish\"\n\teasyservice.SetHelpContent(helpContent)\n\teasyservice.SetOptions(\n\t\tmap[string]bool{\n\t\t\t\"rbsFile\":     true, // 使用的rbs文件地址  必传\n\t\t\t\"vncHost\":     true, // 要连接的vnc服务端地址  必传\n\t\t\t\"vncPort\":     true, // 要连接的vnc服务端端口 必传\n\t\t\t\"vncPassword\": true, // 要连接的vnc服务端密码 不传则使用auth none\n\t\t})\n\teasyservice.Setup(func(svr *easyservice.EasyService) {\n\t\t//注册服务停止时要执行法方法\n\t\tsvr.BeforeStop(func(service *easyservice.EasyService) bool {\n\t\t\tfmt.Println(\"Vnc player server stop\")\n\t\t\treturn true\n\t\t})\n\t\tcfg := svr.Config()\n\t\trbsFile := svr.CmdParser().GetOpt(\"rbsFile\", \"\")\n\t\tif len(rbsFile.String()) <= 0 {\n\t\t\tsvr.Help()\n\t\t\tos.Exit(0)\n\t\t}\n\t\tvncHost := svr.CmdParser().GetOpt(\"vncHost\", \"\")\n\t\tif len(vncHost.String()) <= 0 {\n\t\t\tsvr.Help()\n\t\t\tos.Exit(0)\n\t\t}\n\t\tvncPort := svr.CmdParser().GetOpt(\"vncPort\", 0)\n\t\tif vncPort.Int() <= 0 {\n\t\t\tsvr.Help()\n\t\t\tos.Exit(0)\n\t\t}\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"rbsFile\", rbsFile.String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"vncHost\", vncHost.String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"vncPort\", vncPort.Int())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"vncPassword\", svr.CmdParser().GetOpt(\"vncPassword\", \"\"))\n\n\t\tlogger.SetDebug(cfg.MustGet(context.TODO(), \"Debug\").Bool())\n\n\t\tsvr.AddSandBox(NewRecorderSandBox(cfg))\n\t})\n\n}\n"
  },
  {
    "path": "cmd/recorder/recorder.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/os/gcfg\"\n\t\"github.com/gogf/gf/v2/os/gfile\"\n\t\"github.com/gogf/gf/v2/os/gtime\"\n\t\"github.com/osgochina/dmicro/easyservice\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/encodings\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/security\"\n\t\"github.com/vprix/vncproxy/session\"\n\t\"github.com/vprix/vncproxy/vnc\"\n\t\"golang.org/x/net/context\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n)\n\n// RecorderSandBox  记录服务\ntype RecorderSandBox struct {\n\tid       int\n\tname     string\n\tcfg      *gcfg.Config\n\tservice  *easyservice.EasyService\n\trecorder *vnc.Recorder\n\tclosed   chan struct{}\n}\n\n// NewRecorderSandBox 创建一个默认的服务沙盒\nfunc NewRecorderSandBox(cfg *gcfg.Config) *RecorderSandBox {\n\tid := easyservice.GetNextSandBoxId()\n\tsBox := &RecorderSandBox{\n\t\tid:     id,\n\t\tname:   fmt.Sprintf(\"tcp_%d\", id),\n\t\tcfg:    cfg,\n\t\tclosed: make(chan struct{}),\n\t}\n\treturn sBox\n}\n\nfunc (that *RecorderSandBox) ID() int {\n\treturn that.id\n}\n\nfunc (that *RecorderSandBox) Name() string {\n\treturn that.name\n}\n\nfunc (that *RecorderSandBox) Setup() error {\n\tsaveFilePath := that.cfg.MustGet(context.TODO(), \"rbsFile\").String()\n\ttargetCfg := rfb.TargetConfig{\n\t\tNetwork:  \"tcp\",\n\t\tHost:     that.cfg.MustGet(context.TODO(), \"vncHost\").String(),\n\t\tPort:     that.cfg.MustGet(context.TODO(), \"vncPort\").Int(),\n\t\tPassword: that.cfg.MustGet(context.TODO(), \"vncPassword\").Bytes(),\n\t\tTimeout:  10 * time.Second,\n\t}\n\tvar securityHandlers = []rfb.ISecurityHandler{\n\t\t&security.ClientAuthNone{},\n\t}\n\tif len(targetCfg.Password) > 0 {\n\t\tsecurityHandlers = []rfb.ISecurityHandler{\n\t\t\t&security.ClientAuthVNC{Password: targetCfg.Password},\n\t\t}\n\t}\n\t// 创建会话\n\trecorderSess := session.NewRecorder(\n\t\trfb.OptEncodings(encodings.DefaultEncodings...),\n\t\trfb.OptMessages(messages.DefaultServerMessages...),\n\t\trfb.OptPixelFormat(rfb.PixelFormat32bit),\n\t\trfb.OptGetConn(func(iSession rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\tif gfile.Exists(saveFilePath) {\n\t\t\t\tsaveFilePath = fmt.Sprintf(\"%s%s%s_%d%s\",\n\t\t\t\t\tgfile.Dir(saveFilePath),\n\t\t\t\t\tgfile.Separator,\n\t\t\t\t\tgfile.Name(gfile.Basename(saveFilePath)),\n\t\t\t\t\tgtime.Now().Unix(),\n\t\t\t\t\tgfile.Ext(gfile.Basename(saveFilePath)),\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn gfile.OpenFile(saveFilePath, os.O_RDWR|os.O_CREATE, 0644)\n\t\t}),\n\t)\n\tcliSession := session.NewClient(\n\t\trfb.OptEncodings(encodings.DefaultEncodings...),\n\t\trfb.OptMessages(messages.DefaultServerMessages...),\n\t\trfb.OptPixelFormat(rfb.PixelFormat32bit),\n\t\trfb.OptGetConn(func(iSession rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\treturn net.DialTimeout(targetCfg.Network, targetCfg.Addr(), targetCfg.Timeout)\n\t\t}),\n\t\trfb.OptSecurityHandlers(securityHandlers...),\n\t)\n\tthat.recorder = vnc.NewRecorder(recorderSess, cliSession)\n\terr := that.recorder.Start()\n\tif err != nil {\n\t\tlogger.Fatal(context.TODO(), err)\n\t}\n\treturn err\n}\n\nfunc (that *RecorderSandBox) Shutdown() error {\n\tclose(that.closed)\n\tthat.recorder.Close()\n\treturn nil\n}\n\nfunc (that *RecorderSandBox) Service() *easyservice.EasyService {\n\treturn that.service\n}\n"
  },
  {
    "path": "cmd/screenshot/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/os/gcfg\"\n\t\"github.com/gogf/gf/v2/os/gfile\"\n\t\"github.com/gogf/gf/v2/text/gstr\"\n\t\"github.com/osgochina/dmicro/easyservice\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/vnc\"\n\t\"image/draw\"\n\t\"image/jpeg\"\n\t\"os\"\n\t\"time\"\n)\n\nvar (\n\thelpContent = gstr.TrimLeft(`\nUSAGE\n\t./server [start|stop|quit] [OPTION]\nOPTION\n\t--imageFile     要生成的截图地址,暂时只支持jpeg格式  必传\n\t--vncHost       要连接的vnc服务端地址  必传\n\t--vncPort       要连接的vnc服务端端口 必传\n\t--vncPassword   要连接的vnc服务端密码 不传则使用auth none\n\t--debug         是否开启debug 默认debug=false\n\t-h,--help       获取帮助信息\n\t-v,--version    获取编译版本信息\n\t\nEXAMPLES\n\t/path/to/server \n\t/path/to/server start --env=dev --debug=true\n\t/path/to/server start -c=config.product.toml\n\t/path/to/server start  --config=config.product.toml\n\t/path/to/server start  --imageFile=/path/to/foo.jpeg\n                                    --vncHost=192.168.1.2 \n                                    --vncPort=5901\n                                    --vncPassword=vprix\n                                    --debug\n\t/path/to/server version\n\t/path/to/server help\n`)\n)\n\nfunc main() {\n\teasyservice.Authors = \"ClownFish\"\n\teasyservice.SetHelpContent(helpContent)\n\teasyservice.SetOptions(\n\t\tmap[string]bool{\n\t\t\t\"imageFile\":   true, // 要生成的截图地址,暂时只支持jpeg格式  必传\n\t\t\t\"vncHost\":     true, // 要连接的vnc服务端地址  必传\n\t\t\t\"vncPort\":     true, // 要连接的vnc服务端端口 必传\n\t\t\t\"vncPassword\": true, // 要连接的vnc服务端密码 不传则使用auth none\n\t\t})\n\n\teasyservice.Setup(func(svr *easyservice.EasyService) {\n\t\t//注册服务停止时要执行法方法\n\t\tsvr.BeforeStop(func(service *easyservice.EasyService) bool {\n\t\t\tfmt.Println(\"Vnc player server stop\")\n\t\t\treturn true\n\t\t})\n\t\tcfg := svr.Config()\n\t\trbsFile := svr.CmdParser().GetOpt(\"imageFile\", \"\")\n\t\tif len(rbsFile.String()) <= 0 {\n\t\t\tsvr.Help()\n\t\t\tos.Exit(0)\n\t\t}\n\t\tvncHost := svr.CmdParser().GetOpt(\"vncHost\", \"\")\n\t\tif len(vncHost.String()) <= 0 {\n\t\t\tsvr.Help()\n\t\t\tos.Exit(0)\n\t\t}\n\t\tvncPort := svr.CmdParser().GetOpt(\"vncPort\", 0)\n\t\tif vncPort.Int() <= 0 {\n\t\t\tsvr.Help()\n\t\t\tos.Exit(0)\n\t\t}\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"rbsFile\", rbsFile.String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"vncHost\", vncHost.String())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"vncPort\", vncPort.Int())\n\t\t_ = cfg.GetAdapter().(*gcfg.AdapterFile).Set(\"vncPassword\", svr.CmdParser().GetOpt(\"vncPassword\", \"\").String())\n\n\t\tlogger.SetDebug(cfg.MustGet(context.TODO(), \"Debug\").Bool())\n\n\t\tv := vnc.NewScreenshot(\n\t\t\trfb.TargetConfig{\n\t\t\t\tNetwork:  \"tcp\",\n\t\t\t\tHost:     vncHost.String(),\n\t\t\t\tPort:     vncPort.Int(),\n\t\t\t\tPassword: svr.CmdParser().GetOpt(\"vncPassword\", \"\").Bytes(),\n\t\t\t\tTimeout:  5 * time.Second,\n\t\t\t},\n\t\t)\n\t\timg, err := v.GetImage()\n\t\tif err != nil {\n\t\t\tlogger.Fatal(context.TODO(), err)\n\t\t}\n\n\t\tj := &bytes.Buffer{}\n\t\terr = jpeg.Encode(j, img.(draw.Image), &jpeg.Options{Quality: 100})\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t\terr = gfile.PutBytes(rbsFile.String(), j.Bytes())\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t\tos.Exit(0)\n\t})\n}\n"
  },
  {
    "path": "cmd/video/main.go",
    "content": "package main\n\nfunc main() {\n\t// 暂未实现\n\t//v := vnc.NewVideo(nil,\n\t//\trfb.TargetConfig{\n\t//\t\tNetwork:  \"tcp\",\n\t//\t\tHost:     \"127.0.0.1\",\n\t//\t\tPort:     5901,\n\t//\t\tPassword: []byte(\"@abc1234\"),\n\t//\t\tTimeout:  10 * time.Second,\n\t//\t},\n\t//)\n\t//go func() {\n\t//\terr := v.Start()\n\t//\tif err != nil {\n\t//\t\tlogger.Fatal(err)\n\t//\t}\n\t//}()\n\t//for {\n\t//\terr := <-v.Error()\n\t//\tlogger.Error(err)\n\t//}\n\n}\n"
  },
  {
    "path": "docs/.keep",
    "content": ""
  },
  {
    "path": "docs/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/README.md",
    "content": "# 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)\n## VncProxy简介\n\n`VncProxy` 是使用`golang`实现的rfb协议解析库，支持rfb协议解析，在其上实现了很多好用的功能。\n\n* 全协议支持的vnc proxy。\n  * 支持Tcp代理\n  * 支持Websocket代理\n* 屏幕录像，保存为`RBS`文件\n* 重播服务器，支持vnc客户端链接，播放`RBS`文件。\n* 支持实时录制视频\n* 支持通过`RBS`文件录制视频。\n* 支持屏幕截图\n\n## 支持的编码格式\n\n- [x] Raw\n- [x] CopyRect\n- [x] CoRRE\n- [x] rre\n- [x] Hextile\n- [x] Tight\n- [x] TightPng\n- [x] ZLib\n- [x] Zrle\n- [x] CursorPseudo\n- [x] CursorWithAlphaPseudo\n- [x] DesktopNamePseudo\n- [x] DesktopSizePseudo\n- [x] ExtendedDesktopSizePseudo\n- [x] LedStatePseudo\n- [x] CursorPosPseudo\n- [x] XCursorPseudo\n- [ ] jpeg\n- [ ] jrle\n- [ ] trle\n\n## 组件说明\n\n### Proxy\n\n1. 启动`server`接受`vnc viewer`的链接.\n2. 启动`client`连接到指定的`vnc server`.\n3. 为`vnc viewer`和`vnc server`之间建立起消息转发通道。\n4. 因为`rfb`协议被完全解析，可以针对通信的消息进行转发处理，产生了后续的功能。\n\n### Recorder\n\n1. 启动`client`连接到指定的`vnc server`.\n2. 发送帧缓冲区更新消息`FramebufferUpdateRequest`到`vnc server`。\n3. 处理`vnc server`回复的界面更新消息`FramebufferUpdate`。\n4. 把这一过程以`rbs`文件格式记录下来。\n\n### Player\n\n1. 启动`server`接受`vnc viewer`的链接.\n2. 读取`rbs`文件，并按格式生成`FramebufferUpdate`消息发送给`vnc viewer`。\n3. `vnc viewer`的界面就会回放动作。\n\n### Video\n\n1. 支持`Proxy`,`Recorder`和`rbs`文件作为输入源。\n2. 把`FramebufferUpdate`消息转换为视频文件。\n\n### Screenshot\n\n1. 支持`Proxy`,`Recorder`和`rbs`文件作为输入源。\n2. 把当前的界面视图转换为图片文件。\n\n## 使用说明\n\n`vncProxy`项目有多种应用场景。\n可以作为单独的应用程序编译，也可以作为库被其他应用程序引用。\n接下来，分别介绍各种场景下的使用方式。\n### 编译\n\n```shell\n# 使用方式:\n# build.sh [-s app_name] [-v version] [-g go_bin]\n# app_name 需要编译的应用名称\n#          选项: proxy,player,recorder,video,screenshot.\n#          默认是所有应用,多个应用可以逗号分割.\n# version  编译后的文件版本号,默认为当前git的commit id.\n# go_bin   使用的golang程序\n\n# 编译所有应用\n$ ./build \n\n# 编译proxy\n$ ./build -s proxy -v v0.1.0\n\n# 编译player,recorder\n$ ./build -s player,recorder -v v0.1.0\n```\n\n编译后的二进制文件在`./bin/`目录\n\n### Proxy\n\n代码路径在`./cmd/proxy`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n\n#### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./proxy --help\n\n# 查看版本信息\n$ ./proxy version\n\n```\n\n#### 启动tcp服务\n```shell\n\n# 启动tcp server接受vnc viewer的连接\n# vncHost  vnc服务器host\n# vncPort  vnc服务器port\n# vncPassword  vnc服务器密码\n# tcpHost  本地监听的地址\n# tcpPort  本地监听的端口\n# proxyPassword  vnc连接的密码\n# debug  使用debug模式启动服务\n\n$ ./proxy start tcpServer --vncHost=192.168.1.2 \\     \n                          --vncPort=5901 \\           \n                          --vncPassword=vprix \\       \n                          --tcpHost=0.0.0.0 \\        \n                          --tcpPort=8989 \\           \n                          --proxyPassword=12345612 \\  \n                          --debug                    \n```\n\n#### 启动WebSocket服务\n```shell\n\n# 启动ws server接受novnc的连接\n# vncHost  vnc服务器host\n# vncPort  vnc服务器port\n# vncPassword  vnc服务器密码\n# wsHost  本地监听的地址\n# wsPort  本地监听的端口\n# wsPath  websocket连接的地址\n# proxyPassword  vnc连接的密码\n# debug  使用debug模式启动服务\n\n$ ./proxy start wsServer  --vncHost=192.168.1.2 \\      \n                          --vncPort=5901         \\     \n                          --vncPassword=vprix \\       \n                          --wsHost=0.0.0.0 \\          \n                          --wsPort=8988    \\           \n                          --wsPath=/websockify \\         \n                          --proxyPassword=12345612 \\    \n                          --debug              \n```\n\n### Recorder\n\n代码路径在`./cmd/recorder`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n#### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./recorder --help\n\n# 查看版本信息\n$ ./recorder version\n\n```\n\n#### 启动Recorder服务\n```shell\n\n# rbsFile  要保存的rbs文件路径(必填)\n# vncHost  vnc服务器host\n# vncPort  vnc服务器port\n# vncPassword  vnc服务器密码\n# debug  使用debug模式启动服务\n\n$ ./recorder start --rbsFile=/path/to/foo.rbs\n\t\t\t\t\t\t\t--vncHost=192.168.1.2 \n\t\t\t\t\t\t\t--vncPort=5901\n\t\t\t\t\t\t\t--vncPassword=vprix\n\t\t\t\t\t\t\t--debug             \n```\n\n### Player\n\n代码路径在`./cmd/player`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n#### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./player --help\n\n# 查看版本信息\n$ ./player version\n```\n\n#### 启动Player Tcp服务\n```shell\n\n# rbsFile  要保存的rbs文件路径(必填)\n# tcpHost   本地监听的tcp协议地址 默认0.0.0.0\n# tcpPort  本地监听的tcp协议端口 默认8989\n# proxyPassword  连接到proxy的密码   不传入密码则使用auth none\n# debug  使用debug模式启动服务\n\n$ ./player start tcpServer  --rbsFile=/path/to/foo.rbs\n                            --tcpHost=0.0.0.0\n                            --tcpPort=8989\n                            --proxyPassword=12345612\n                            --debug             \n```\n\n#### 启动Player WS服务\n```shell\n\n# rbsFile  要保存的rbs文件路径(必填)\n# wsHost   启动websocket服务的本地地址  默认 0.0.0.0\n# wsPort   启动websocket服务的本地端口 默认8988\n# wsPath   启动websocket服务的url path 默认'/'\n# proxyPassword  连接到proxy的密码   不传入密码则使用auth none\n# debug  使用debug模式启动服务\n\n$ ./player start wsServer --rbsFile=/path/to/foo.rbs\n                          --wsHost=0.0.0.0\n                          --wsPort=8989\n                          --wsPath=/\n                          --proxyPassword=12345612\n                          --debug             \n```\n### Screenshot\n代码路径在`./cmd/screenshot`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n#### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./screenshot --help\n\n# 查看版本信息\n$ ./screenshot version\n```\n\n#### 启动Screenshot 获取vnc服务器的屏幕截图\n```shell\n\n# imageFile  要生成的截图地址,暂时只支持jpeg格式(必填)\n# vncHost   要连接的vnc服务端地址(必填)\n# vncPort   要连接的vnc服务端端口(必填)\n# vncPassword  要连接的vnc服务端密码，不传则使用auth none\n\n$ ./screenshot --imageFile=./screen.jpeg --vncHost=127.0.0.1 --vncPort=5900 --vncPassword=12345612       \n```\n\n## 项目参考\n\n本项目参考了以下项目完成。\n* [vncproxy](https://github.com/amitbet/vncproxy)\n* [vnc2video](https://github.com/amitbet/vnc2video)\n* [rfbproto](https://github.com/rfbproto/rfbproto)\n\n## 交流\n\n我在做这个项目的过程中碰到了很多问题，查遍了互联网，缺少中文资料，大部分信息都是雷同的。\n所以我萌生了开源的想法，帮助更多有需要的人。\n\n我建立了一个可供交流的微信群，以便大家在使用的过程中碰到疑问，能有解答的地方。\n当然，如果你对vnc有兴趣，也可以加我微信，多多交流。\n欢迎各位贡献代码。\n\n![微信二维码](/docs/images/5bb8dbe702ce04b0bdde8c26583b152.jpg)\n\n\n\n"
  },
  {
    "path": "docs/changelog.md",
    "content": ""
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>VncProxy - RFB协议 - 使用文档</title>\n  <link rel=\"shortcut icon\" href=\"favicon.ico\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n  <meta name=\"description\" content=\"VNC Proxy 远程桌面代理,rfb协议解析\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, minimum-scale=1.0\">\n  <link rel=\"stylesheet\" href=\"//cdn.bootcdn.net/ajax/libs/docsify/4.12.1/themes/vue.min.css\">\n  <style>\n    .app-name img{\n      width:50px;\n      height:60px\n    }\n  </style>\n</head>\n<body>\n  <div id=\"app\"></div>\n  <script>\n    window.$docsify = {\n      name: 'VNC Proxy',\n      loadSidebar: 'summary.md',\n      alias: {\n        '/.*/summary.md': '/summary.md'\n      },\n      themeColor: '#3F71B5',\n      auto2top: true,\n      subMaxLevel: 2,\n      topMargin: \"20\",\n      repo: 'https://github.com/vprix/vncproxy',\n      search: \"auto\",\n      logo: '/logo.svg',\n    }\n  </script>\n  <!-- Docsify v4 -->\n  <script src=\"//cdn.bootcdn.net/ajax/libs/docsify/4.12.1/docsify.min.js\"></script>\n  <!--搜索插件-->\n  <script src=\"//cdn.bootcdn.net/ajax/libs/docsify/4.12.1/plugins/search.min.js\"></script>\n  <!--golang的语法高亮-->\n  <script src=\"//cdn.bootcdn.net/ajax/libs/prism/1.24.1/components/prism-go.min.js\"></script>\n  <!--图片缩放 - Zoom image-->\n  <script src=\"//cdn.bootcdn.net/ajax/libs/docsify/4.12.1/plugins/zoom-image.min.js\"></script>\n\n  <!--美人鱼图-->\n  <script src=\"//unpkg.com/mermaid/dist/mermaid.min.js\"></script>\n  <script src=\"//unpkg.com/docsify-mermaid@latest/dist/docsify-mermaid.js\"></script>\n  <script>mermaid.initialize({ startOnLoad: true });</script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/overview.md",
    "content": ""
  },
  {
    "path": "docs/player/.keep",
    "content": ""
  },
  {
    "path": "docs/player/README.md",
    "content": "## Player\n\n代码路径在`./cmd/player`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n\n### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./player --help\n\n# 查看版本信息\n$ ./player version\n```\n\n### 启动Player Tcp服务\n\n```shell\n\n# rbsFile  要保存的rbs文件路径(必填)\n# tcpHost   本地监听的tcp协议地址 默认0.0.0.0\n# tcpPort  本地监听的tcp协议端口 默认8989\n# proxyPassword  连接到proxy的密码   不传入密码则使用auth none\n# debug  使用debug模式启动服务\n\n$ ./player start tcpServer  --rbsFile=/path/to/foo.rbs\n                            --tcpHost=0.0.0.0\n                            --tcpPort=8989\n                            --proxyPassword=12345612\n                            --debug             \n```\n\n### 启动Player WS服务\n\n```shell\n\n# rbsFile  要保存的rbs文件路径(必填)\n# wsHost   启动websocket服务的本地地址  默认 0.0.0.0\n# wsPort   启动websocket服务的本地端口 默认8988\n# wsPath   启动websocket服务的url path 默认'/'\n# proxyPassword  连接到proxy的密码   不传入密码则使用auth none\n# debug  使用debug模式启动服务\n\n$ ./player start wsServer --rbsFile=/path/to/foo.rbs\n                          --wsHost=0.0.0.0\n                          --wsPort=8989\n                          --wsPath=/\n                          --proxyPassword=12345612\n                          --debug             \n```"
  },
  {
    "path": "docs/proxy/.keep",
    "content": ""
  },
  {
    "path": "docs/proxy/README.md",
    "content": "## Proxy\n\n代码路径在`./cmd/proxy`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n\n### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./proxy --help\n\n# 查看版本信息\n$ ./proxy version\n\n```\n### 启动tcp服务\n\n```shell\n\n# 启动tcp server接受vnc viewer的连接\n# vncHost  vnc服务器host\n# vncPort  vnc服务器port\n# vncPassword  vnc服务器密码\n# tcpHost  本地监听的地址\n# tcpPort  本地监听的端口\n# proxyPassword  vnc连接的密码\n# debug  使用debug模式启动服务\n\n$ ./proxy start tcpServer --vncHost=192.168.1.2 \\     \n                          --vncPort=5901 \\           \n                          --vncPassword=vprix \\       \n                          --tcpHost=0.0.0.0 \\        \n                          --tcpPort=8989 \\           \n                          --proxyPassword=12345612 \\  \n                          --debug                    \n```\n### 启动WebSocket服务\n\n```shell\n\n# 启动ws server接受novnc的连接\n# vncHost  vnc服务器host\n# vncPort  vnc服务器port\n# vncPassword  vnc服务器密码\n# wsHost  本地监听的地址\n# wsPort  本地监听的端口\n# wsPath  websocket连接的地址\n# proxyPassword  vnc连接的密码\n# debug  使用debug模式启动服务\n\n$ ./proxy start wsServer  --vncHost=192.168.1.2 \\      \n                          --vncPort=5901         \\     \n                          --vncPassword=vprix \\       \n                          --wsHost=0.0.0.0 \\          \n                          --wsPort=8988    \\           \n                          --wsPath=/websockify \\         \n                          --proxyPassword=12345612 \\    \n                          --debug              \n```"
  },
  {
    "path": "docs/questions.md",
    "content": ""
  },
  {
    "path": "docs/recorder/.keep",
    "content": ""
  },
  {
    "path": "docs/recorder/README.md",
    "content": "## Recorder\n\n代码路径在`./cmd/recorder`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n\n### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./recorder --help\n\n# 查看版本信息\n$ ./recorder version\n\n```\n### 启动Recorder服务\n\n```shell\n\n# rbsFile  要保存的rbs文件路径(必填)\n# vncHost  vnc服务器host\n# vncPort  vnc服务器port\n# vncPassword  vnc服务器密码\n# debug  使用debug模式启动服务\n\n$ ./recorder start --rbsFile=/path/to/foo.rbs\n\t\t\t\t\t\t\t--vncHost=192.168.1.2 \n\t\t\t\t\t\t\t--vncPort=5901\n\t\t\t\t\t\t\t--vncPassword=vprix\n\t\t\t\t\t\t\t--debug             \n```"
  },
  {
    "path": "docs/rfc6143/GLOSSORY.md",
    "content": "# 帧缓冲\n\n帧缓冲器也称为帧缓冲或者显存，是用来存储渲染数据的地方。帧缓冲的每一个存储单位对应一个像素，它是屏幕显示画面的直接映象，又称为位映射图(Bit Map)。\n\n![帧缓冲器](../images/rfc6143-glassory-1.png)\n\n# 调色板\n\n传统的帧缓冲器支持的色彩模式很广泛。受限于昂贵的内存，大多数早期的帧缓冲器使用的是1位、2位、4位或 8位的色深。小的色深导致不能产生完整的色彩范围。其解决方法是为帧缓冲器增加一个查找表（lookup table，LUT），把帧缓冲器中存储的每个“颜色”作为一个索引。这就是所谓的索引色（indexed color）模式。\n\n![调色板](../images/rfc6143-glassory-2.png)\n\n# 游程编码\n\n游程编码（run-length encoding，RLE）是一种比较简单的压缩算法，其基本思想是将重复且连续出现多次的字符使用（连续出现次数，某个字符）来描述。\n\n举例来说，字符串\"AAAABBBCCDEEEE\"，由4个A、3个B、2个C、1个D、4个E组成，游程编码将其压缩为4A3B2C1D4E，由14个字符转成10个字符，压缩比 71.4%。\n\n游程编码的优点是将重复性高的数据压缩成小单位；若数据出现频率不高，压缩结果可能比原始数据大。例如：\"ABCDE\"，压缩结果为\"1A1B1C1D1E\"，由5个字符转成10个字符，压缩比 200%。\n\n![游程编码](../images/rfc6143-glassory-3.png)\n"
  },
  {
    "path": "docs/rfc6143/README.md",
    "content": "# 远程帧缓冲协议\n\n该文档原著为：https://github.com/vincenthcui/rfc6143\n\n## RFB 协议\n\nRFB (Remote Framebuffer Protocol) 远程帧缓冲协议，是一种允许用户通过网络连接控制远端计算机的七层网络协议。\n在 RFB 协议中，用户通过本地鼠标、键盘输入，经由远端计算机计算后，将图形用户界面（GUI）回传本地进行输出。\n\n![RFB 协议通信](../images/rfc6143-readme-1.png)\n\n### 协议特点\n\n协议设计有以下几个特点：\n\n- 瘦客户端。客户端职责简单清晰，无状态\n- 运行在弱网络环境下\n- 跨操作系统兼容性\n\n## 协议版本\n\nRFB 协议有三个公开版本，分别是 3.3、3.7和3.8，3.8 是稳定版本。\n\n|         版本          |    发布时间    |   协议差异    |\n|:-------------------:|:----------:|:---------:|\n|     Version 3.3     |  1998-01   |  服务器单向认证  |\n|     Version 3.7     | 2003-8-12  | 关闭连接时返回原因 |\n| Version 3.8 (Final) | 2010-11-26 |     -     |\n\n三个版本只在协议的握手阶段和初始化阶段存在差异，在数据流交换阶段保持一致。\n\n### 协议的拓展\n\n第三方 VNC 服务端和客户端拓展了 3.8 版本协议，提供更多的认证方式，优化传输效率。\n\n- Tight\n- RealVNC\n- Ultra\n- VMWare\n\n## RFB 的发展历史\n\n- 2002 年，AT&T 关闭其位于英国剑桥的 Olivetti 研究实验室。\n- 2002 年，VNC 技术发明者 Tristan Richardson 合伙成立 RealVNC 公司，向商业公司提供企业级远程访问软件。\n- 2003年8月12日，Richardson 公开 RFB 协议的 3.7 版本。\n- 2010年11月26日，发布稳定协议版本 v3.8。\n\n> RFB 是 IETF 公开的开源通信协议\n>\n> RFB® 和 VNC® 是 RealVNC 公司的注册商标。\n\n## 公开协议版本及资料\n\n- [RFC 6143: The Remote Framebuffer Protocol (describes Version 3.8)](https://tools.ietf.org/html/rfc6143)\n- [The RFB Protocol - Community Version](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst)\n- [The RFB Protocol - Version 3.8 (2010-11-26)](https://web.archive.org/web/20160410055332/http://www.realvnc.com/docs/rfbproto.pdf)\n- [The RFB Protocol - Version 3.7 (2003-08-12)](https://web.archive.org/web/20040325204925/http://www.realvnc.com/docs/rfbproto.pdf)\n"
  },
  {
    "path": "docs/rfc6143/handshake/README.md",
    "content": "# 握手\n\nRFB 协议有四个阶段：\n\n```mermaid\ngraph LR\n    protocol[\"协议握手\"]\n    auth[\"认证\"]\n    initial[\"初始化\"]\n    transfer(\"数据交互\")\n    subgraph 握手\n        protocol --> auth\n        auth --> initial\n    end\n    initial --> transfer\n    transfer --> transfer\n```\n\n- [协议握手](/rfc6143/handshake/protocol-version.md)：对协议版本达成共识\n- [认证](/rfc6143/handshake/security-type.md)：认证客户端身份\n- [初始化](/rfc6143/handshake/initial.md)：交换像素格式等背景数据\n- [数据交互](/rfc6143/transfer/README.md)：传输交互事件，更新图像帧\n"
  },
  {
    "path": "docs/rfc6143/handshake/initial.md",
    "content": "# 初始化\n\n收 SecurityResult 后，客户端应当发送 [ClientInit](#客户端初始化) 数据包，收到后，服务端发送 [ServerInit](#服务端初始化) 包。\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server\n    Server->>Client: SecurityResult(success)\n    Client->>Server: ClientInit\n    Server->>Client: ServerInit\n```\n\n## 客户端初始化\n\n客户端初始化需要声明是否的共享屏幕。\n\n```\n+--------------+--------------+-------------+\n| No. of bytes | Type [Value] | Description |\n+--------------+--------------+-------------+\n| 1            | U8           | shared-flag |\n+--------------+--------------+-------------+\n```\n\n- shared-flag: 是否与其他客户端共享连接。如果是 1，允许服务端保持/加入其他客户端的连接。如果是0，服务端应该主动断开与其他客户端的连接。\n\n## 服务端初始化\n\n收到 ClientInit 消息后，服务端发送 ServerInit 消息，声明帧缓冲区大小、像素格式以及桌面的名称。\n\n```\n  +--------------+--------------+------------------------------+\n  | No. of bytes | Type [Value] | Description                  |\n  +--------------+--------------+------------------------------+\n  | 2            | U16          | framebuffer-width in pixels  |\n  | 2            | U16          | framebuffer-height in pixels |\n  | 16           | PIXEL_FORMAT | server-pixel-format          |\n  | 4            | U32          | name-length                  |\n  | name-length  | U8 array     | name-string                  |\n  +--------------+--------------+------------------------------+\n```\n\n- framebuffer-width in pixels: 屏幕宽度\n- framebuffer-height in pixels: 屏幕高度\n- server-pixel-format: 服务器默认像素格式\n- name-length/name-string: 桌面的名字\n\n如果客户端无法响应服务端指定的 pixel-format，可以主动发起 [SetPixelFormat](/display/pixel-format.md#SetPixelFormat) 重新设置。\n"
  },
  {
    "path": "docs/rfc6143/handshake/protocol-version.md",
    "content": "\n# 协议握手\n\n协议握手对客户端和服务端通信过程使用的协议版本达成共识，有可能的情况下，对不同协议版本实现向前兼容。\n\n## 握手过程\n\n建立 TCP 连接后，服务器首先向客户端发送版本 X，收到 X 后，客户端向服务器发送不高于 X 的版本 Y。\n\nRFB 有三个公开可选版本 3.3/3.7/3.8。\n\n部分客户端或浏览器变种可能会发送其他的协议版本，统一将非标协议认定为 3.3（协议认为未公开协议版本没有实现3.7/3.8 中引入的特殊握手流程）。\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server\n    Server->>Client: ProtocolVersion\n    Client->>Server: ProtocolVersion\n```\n\n## 协议报文\n\n协议消息由标识符、主次版本组成，其结构体如下：\n\n```\n  +--------------+--------------+--------------+\n  | No. of bytes | Type [Value] | Description  |\n  +--------------+--------------+--------------+\n  | 3            | U8 array     | protocol     |\n  | 1            | U8 [32]      | blank        |\n  | 3            | U8 array     | major version|\n  | 1            | U8 [42]      | pot          |\n  | 3            | U8 array     | minor version|\n  +--------------+--------------+--------------+\n```\n\n对于 3.8 版本协议，其发送协议头部如下：\n\n```\nRFB 003.008\\n (hex 52 46 42 20 30 30 33 2e 30 30 38 0a)\n```\n"
  },
  {
    "path": "docs/rfc6143/handshake/security-type.md",
    "content": "# 安全握手\n\n协商好协议版本后，客户端和服务端进行安全握手，就认证方式达成共识，并完成认证过程。\n\n## 握手过程\n\n认证的流程图如下：\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server\n    \n    alt incompatible\n    Server->>Client: SecurityTypes(Empty)\n    Server->>Client: FailReason\n    Server-XClient: Close\n    end\n    \n    Server->>Client: SecurityTypes\n    Client->>Server: SecurityType\n\n    alt None\n    else VNC Auth\n    Server->>Client: Challenge\n    Client->>Server: ChallengeResponse\n    end\n\n    Server->>Client: SecurityResult\n    alt failed\n    Server->>Client: FailReason\n    Server-XClient: Close\n    end\n```\n\n- 服务器向客户端列举支持的加密方式，客户端挑选支持的认证方式，告知服务端。\n- 根据认证方式，完成认证\n- 服务端返回认证结果，完成安全握手\n\n## 协议报文\n\n### security-types\n\n```\n   +--------------------------+-------------+--------------------------+\n   | No. of bytes             | Type        | Description              |\n   |                          | [Value]     |                          |\n   +--------------------------+-------------+--------------------------+\n   | 1                        | U8          | number-of-security-types |\n   | number-of-security-types | U8 array    | security-types           |\n   +--------------------------+-------------+--------------------------+\n```\n\n- number-of-security-types: 认证方式数量\n- security-types: 认证方式标识符\n\n协议定义的标识符有三种，剩下的由协议厂家进行拓展：\n\n```\n  +--------+--------------------+\n  | Number | Name               |\n  +--------+--------------------+\n  | 0      | Invalid            |\n  | 1      | None               |\n  | 2      | VNC Authentication |\n  +--------+--------------------+\n```\n\n### security-type\n\n客户端以单字节报文告知选择的认证方式\n\n```\n  +--------------+--------------+---------------+\n  | No. of bytes | Type [Value] | Description   |\n  +--------------+--------------+---------------+\n  | 1            | U8           | security-type |\n  +--------------+--------------+---------------+\n```\n\n- security-type: 达成共识的协议标识符\n\n### fail-reason\n\n当错误发送时，例如，服务端不兼容客户端版本，服务端会发送空的 SecurityTypes，发送消息说明错误原因后，关闭连接。\n\nFailReason 报文如下：\n\n```\n +---------------+--------------+---------------+\n | No. of bytes  | Type [Value] | Description   |\n +---------------+--------------+---------------+\n | 4             | U32          | reason-length |\n | reason-length | U8 array     | reason-string |\n +---------------+--------------+---------------+\n```\n\n- reason-length: 原因长度\n- reason-string: 错误原因\n\n### VNC Auth\n\nVNC Auth 过程，服务器首先发送 16 字节的随机字符串，作为 `challenge`。客户端用密码通过 `DES` 算法对 `challenge` 进行加密，将加密后的 16 字节结果告知服务端。\n\n> TODO: 加密算法详解\n\n有几点需要注意：\n\n- 为格式化密钥，密码会被删节/补齐为 8 字符\n- VNC Auth 是弱加密，不能用于不可信网络\n- 为了更安全访问，可以通过 IPsec/SSH 加密链路\n\n#### Challenge\n\n```\n+--------------+--------------+-------------+\n| No. of bytes | Type [Value] | Description |\n+--------------+--------------+-------------+\n| 16           | U8           | challenge   |\n+--------------+--------------+-------------+\n```\n\n#### ChallengeResponse\n\n```\n+--------------+--------------+-------------+\n| No. of bytes | Type [Value] | Description |\n+--------------+--------------+-------------+\n| 16           | U8           | response    |\n+--------------+--------------+-------------+\n```\n\n### SecurityResult\n\n客户端选择认证方式后，服务端返回 SecurityResult 告知认证结果，哪怕使用 `None` 认证。\n\n如果认证失败，服务器发送 `failed` 和 fail-reason 报文，主动关闭连接。\n\n```\n+--------------+--------------+-------------+\n| No. of bytes | Type [Value] | Description |\n+--------------+--------------+-------------+\n| 4            | U32          | status:     |\n|              | 0            | OK          |\n|              | 1            | failed      |\n+--------------+--------------+-------------+\n```\n"
  },
  {
    "path": "docs/rfc6143/transfer/README.md",
    "content": "# 数据交互\n\n数据交互阶段，客户端向服务端发送鼠标、键盘事件，或请求更新图像。\n\n```mermaid\ngraph LR\n    A(\"鼠标事件\")\n    B(\"键盘事件\")\n    C(\"更新图像\")\n```\n"
  },
  {
    "path": "docs/rfc6143/transfer/display.md",
    "content": "# 显示协议\n\n客户端可能处于弱网络环境，或只有较低性能的渲染设备。如果服务端不加限制的向客户端发送像素画面，很容易造成客户端卡死或网络堵塞。\n\n在 RFB 协议中，当且仅当客户端主动请求显示数据时，服务端才会将 [FramebufferUpdate](#FramebufferUpdate) 发往客户端。响应 [FramebufferUpdateRequest](#FramebufferUpdateRequest) 往往需要返回多条 FramebufferUpdate。\n\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server\n    Client->>Server: FramebufferUpdateRequest\n    loop no change\n      Client->>Server: FramebufferUpdateRequest\n    \n    end\n    Server->>Client: FramebufferUpdate\n    Server->>Client: FramebufferUpdate\n    Server->>Client: FramebufferUpdate\n```\n\n## FramebufferUpdateRequest\n\nFramebufferUpdateRequest 告知服务端，客户端希望得到指定区域的内容。\n\n```\n+--------------+--------------+--------------+\n| No. of bytes | Type [Value] | Description  |\n+--------------+--------------+--------------+\n| 1            | U8 [3]       | message-type |\n| 1            | U8           | incremental  |\n| 2            | U16          | x-position   |\n| 2            | U16          | y-position   |\n| 2            | U16          | width        |\n| 2            | U16          | height       |\n+--------------+--------------+--------------+\n```\n\n- message-type: 消息类型，固定 `3`\n- incremental: 是否是增量请求。\n- x-position/y-position: 区域的起始坐标\n- width/height: 区域的长度和宽度\n\nincremental 通常为非 0 值，服务器只需要发有变化的图像信息。当客户端丢失了缓存的帧缓冲信息，或者刚建立连接，需要完整的图像信息时，将 incremental 置为 0，获取全量信息。\n\n## FramebufferUpdate\n\nFramebufferUpdate 由一组矩形图像(rectangles of pixel)组成，客户端收到 FramebufferUpdate 消息后，将消息内的矩形填充到帧缓冲对应区域，完成图像展示。\n\n```\n+--------------+--------------+----------------------+\n| No. of bytes | Type [Value] | Description          |\n+--------------+--------------+----------------------+\n| 1            | U8 [0]       | message-type         |\n| 1            |              | padding              |\n| 2            | U16          | number-of-rectangles |\n+--------------+--------------+----------------------+\n```\n\n- message-type: 消息类型，固定 0\n- number-of-rectangles: 矩形的数量\n\n### FramebufferUpdateRectangle\n\nFramebufferUpdate 携带 `number-of-rectangles` 数量的矩形信息，每个矩形都有头部信息\n\n```\n+--------------+--------------+---------------+\n| No. of bytes | Type [Value] | Description   |\n+--------------+--------------+---------------+\n| 2            | U16          | x-position    |\n| 2            | U16          | y-position    |\n| 2            | U16          | width         |\n| 2            | U16          | height        |\n| 4            | S32          | encoding-type |\n+--------------+--------------+---------------+\n```\n\n- x-position/y-position: 矩形起始坐标\n- width/height: 矩形宽度和高度\n- encoding-type: 编码类型\n"
  },
  {
    "path": "docs/rfc6143/transfer/encoding/README.md",
    "content": "# 编码\n\n”编码“ 是像素数据的表达方式。在发往客户端前，服务器经常将发送的图形数据进行编码、压缩，提高图像传输效率。\n\n```\n+--------+-----------------------------+\n| Number | Name                        |\n+--------+-----------------------------+\n| 0      | Raw                         |\n| 1      | CopyRect                    |\n| 2      | RRE                         |\n| 5      | Hextile                     |\n| 15     | TRLE                        |\n| 16     | ZRLE                        |\n| -239   | Cursor pseudo-encoding      |\n| -223   | DesktopSize pseudo-encoding |\n+--------+-----------------------------+\n```\n\n- [Raw](/rfc6143/transfer/encoding/raw.md): 原始位图编码，即不编码\n- [CopyRect](/rfc6143/transfer/encoding/copy-rect.md): 从帧缓冲复制\n- [RRE](/rfc6143/transfer/encoding/rise-and-run-length.md): rise-and-run-length 二维游程编码\n- Hextile: RRE 的变种，图块游程编码\n- [TRLE](/rfc6143/transfer/encoding/tiled-run-length.md): 图块游程编码\n- [ZRLE](/rfc6143/transfer/encoding/zlib-run-length.md): Zlib Run-Length Encoding，zlib 压缩的游程编码\n- Cursor pseudo-encoding: 鼠标指针伪编码\n- DesktopSize pseudo-encoding: 桌面分辨率伪编码\n"
  },
  {
    "path": "docs/rfc6143/transfer/encoding/copy-rect.md",
    "content": "# 镜像编码 Copy Rect\n\nCopyRect 指示客户端，从已有帧缓冲区域复制到新区域。这种编码常用于窗口拖动、页面滚动等场景。\n\n报文只说明起始坐标，区域的长度和宽度由 [FramebufferUpdateRectangle](/rfc6143/transfer/display.md#FramebufferUpdateRectangle) 指定。\n\n```\n+--------------+--------------+----------------+\n| No. of bytes | Type [Value] | Description    |\n+--------------+--------------+----------------+\n| 2            | U16          | src-x-position |\n| 2            | U16          | src-y-position |\n+--------------+--------------+----------------+\n```\n\n- src-x-position/src-y-position: 源图像的起点坐标\n"
  },
  {
    "path": "docs/rfc6143/transfer/encoding/raw.md",
    "content": "# Raw 原始编码\n\nRaw 是最简单也是最原始的编码，直接向客户端传递位图信息，不进行编码优化。\n在 Raw 格式中，像素按从左到右，从上到下的顺序排列成一维数组。\n\n> 协议要求客户端必须支持 Raw 类型编码。\n> \n> 协议要求服务器必须传输 Raw 类型编码，除非客户端另有要求。\n\n```\n+----------------------------+--------------+-------------+\n| No. of bytes               | Type [Value] | Description |\n+----------------------------+--------------+-------------+\n| width*height*bytesPerPixel | PIXEL array  | pixels      |\n+----------------------------+--------------+-------------+\n```\n\n- pixels: 像素数组\n"
  },
  {
    "path": "docs/rfc6143/transfer/encoding/rise-and-run-length.md",
    "content": "# 上升和游程编码 Rise-and-run-length\n\nRRE 是游程编码的二维变种。基本思想是将大的矩形拆分为子矩形，每个子矩形有单值的像素组成，所有小矩形的并集构成原始矩形区域。\n\n编码由背景像素值 Vb 、计数 N ，以及 N 个子矩形列表组成。子矩形由元组 <v,x,y,w,h> 表示，其中 v 是像素值（v != Vb），x/y/w/h 表示子矩形相对主矩形的坐标，和大小。\n\n绘制时，客户端先以背景像素值填充矩形，再绘制每个子矩形，叠加出原始图像。\n\n```\n+---------------+--------------+-------------------------+\n| No. of bytes  | Type [Value] | Description             |\n+---------------+--------------+-------------------------+\n| 4             | U32          | number-of-subrectangles |\n| bytesPerPixel | PIXEL        | background-pixel-value  |\n+---------------+--------------+-------------------------+\n```\n\n- number-of-subrectangles: 子矩形数量\n- background-pixel-value: 矩形背景色\n\n对于子矩形\n\n```\n+---------------+--------------+---------------------+\n| No. of bytes  | Type [Value] | Description         |\n+---------------+--------------+---------------------+\n| bytesPerPixel | PIXEL        | subrect-pixel-value |\n| 2             | U16          | x-position          |\n| 2             | U16          | y-position          |\n| 2             | U16          | width               |\n| 2             | U16          | height              |\n+---------------+--------------+---------------------+\n```\n\n- subrect-pixel-value: 子矩形色值\n- x-position/y-position: 与背景矩行的**相对位置**\n- width/height: 子矩形宽度和高度\n"
  },
  {
    "path": "docs/rfc6143/transfer/encoding/set-encoding.md",
    "content": "# 设置编码\n\n客户端用 SetEncoding 消息告知服务端，接受哪些像素[编码](/rfc6143/transfer/encoding/README.md)。\n\n除了用于解析像素的编码外，客户端可以发送伪编码，向服务端请求拓展功能。如果服务端不识别此编码，可以直接忽略。客户端在未收到服务端明确的”支持“回复前，应当默认服务端不支持伪编码。\n\n数据结构如下：\n\n```\n+-----------------------+--------------+---------------------+\n| No. of bytes          | Type [Value] | Description         |\n+-----------------------+--------------+---------------------+\n| 1                     | U8 [2]       | message-type        |\n| 1                     |              | padding             |\n| 2                     | U16          | number-of-encodings |\n| 4*number-of-encodings | S32 array    | encoding-types      |\n+-----------------------+--------------+---------------------+\n```\n\n- message-type: 消息类型，固定为 `2`\n- number-of-encodings: 编码数量\n- encoding-types: [编码标识符](/rfc6143/transfer/encoding/README.md)\n"
  },
  {
    "path": "docs/rfc6143/transfer/encoding/tight-png.md",
    "content": "# 严格Png编码 Tight Encoding\n\n严格编码为像素数据提供了有效的压缩。为了减少执行的复杂性，任何Tight编码的矩形的宽度不能超过2048像素。\n如果需要一个更宽的矩形，必须将其分成几个矩形，每个矩形都要单独编码。\n\n每个Tight-encoded矩形的第一个字节是一个压缩控制字节。\n\n```\n+--------------+--------------+----------------+\n| No. of bytes | Type [Value] | Description    |\n+--------------+--------------+----------------+\n| 1            | U8          | 压缩控制字节      |\n+--------------+--------------+----------------+\n```\n\n压缩控制字节中最低有效位四位标识了客户端在解码矩形之前应该重置哪些zlib压缩流。\n每个位都是独立的，对应于一个应该被重置的单独的zlib流。\n\n```\n+-----+----------------+\n| Bit | Description    | \n+-----+----------------+\n| 0   | Reset stream 0 | \n| 1   | Reset stream 1 | \n| 2   | Reset stream 2 | \n| 3   | Reset stream 3 | \n+-----+----------------+\n```\n\n在Tight编码中，支持三种可能的压缩方法之一。它们是`BasicCompression`, `FillCompression`和`JpegCompression`。\n\n如果`压缩控制字节`的第7位（最高位）是0，那么压缩类型是BasicCompression。\n在这种情况下，压缩控制的第7-4位（最重要的4位）应该被解释为如下：\n\n```\n+--------------+--------------+----------------+\n| Bits         | Binary value | Description    |\n+--------------+--------------+----------------+\n| 5-4          | 00          | Use stream 0    |\n|              | 01          | Use stream 1    |\n|              | 10          | Use stream 2    |\n|              | 11          | Use stream 3    |\n| 6            | 0           | ---             |\n|              | 1           | read-filter-id  |\n| 7            | 0           | BasicCompression|\n+--------------+--------------+----------------+\n```\n\n否则，如果`压缩控制字节`的第7位设置为1，则压缩方法是`FillCompression`或`JpegCompression`，取决于同一字节的其他位：\n\n```\n+--------------+--------------+----------------+\n| Bits         | Binary value | Description    |\n+--------------+--------------+----------------+\n| 7-4          | 1000         | FillCompression|\n|              | 1001         | JpegCompression|\n|              | any other    | Invalid        |\n+--------------+--------------+----------------+\n```\n\n> 注意：`JpegCompression` 只能在像素编码为 16 或 32 并且客户端已使用 ` JPEG Quality Level Pseudo-encoding`。\n\nTight 编码使用一种新型 `TPIXEL`（Tight 像素）。\n这与商定的像素格式的 PIXEL 相同，除了真彩色标志非零、每像素位数为 32、深度为 24 以及构成红色、绿色和蓝色强度的所有位正好是 8 位宽。\n在这种情况下，`TPIXEL` 只有 3 个字节长，其中第一个字节是红色分量，第二个字节是绿色分量，第三个字节是像素颜色值的蓝色分量。\n\n> 压缩控制字节之后的数据取决于压缩方法。\n\n- FillCompression\n\n    如果压缩类型为 `FillCompression`，则后面是唯一的像素值，采用 `TPIXEL` 格式。此值适用于矩形的所有像素。\n- JpegCompression\n\n  如果压缩类型为 `JpegCompression`，则以下数据流如下所示：\n\n    ```\n    +--------------+--------------+----------------+\n    | No. of bytes | Type      | Description       |\n    +--------------+-----------+-------------------+\n    | 1-3          |           | jpeg数据的长度     |\n    | length       | U8 array  | jpeg-data         |\n    +--------------+-----------+-------------------+\n    ```\n  根据以下方案，长度动态压缩表示为一个、两个或三个字节：\n\n  ```\n  +----------------------------+---------------------------+\n  | Value                      | Description               |\n  +----------------------------+---------------------------+\n  | 0xxxxxxx\t               | for values 0..127         |\n  | 1xxxxxxx 0yyyyyyy          | for values 128..16383     |\n  | 1xxxxxxx 1yyyyyyy zzzzzzzz | for values 16384..4194303 |\n  +----------------------------+---------------------------+\n  ```\n  这里每个字符表示一位，`xxxxxxx` 是值的最低 7 位（位 0-6），`yyyyyyy` 是 7-13 位，`zzzzzzzz` 是最高有效 8 位（14-21 位）。例如，十进制值 10000 应表示为两个字节：二进制 10010000 01001110，或十六进制 90 4E。\n  > jpeg-data 是 JFIF 流\n\n- BasicCompression\n\n  如果压缩类型是 `BasicCompression` 并且压缩控制字节的第 6 位（read-filter-id 位）设置为 1，\n  则下一个（第二个）字节指定 `filter-id`，它告诉解码器使用什么过滤器类型编码器在压缩之前对像素数据进行预处理。 \n  `filter-id` 字节可以是以下之一：\n\n  ```\n    +--------------+-------+-----------+-------------------------+\n    | No. of bytes | Type  | [Value]   | Description             |\n    +--------------+-------+-----------+-------------------------+\n    | 1            |  U8   |           | filter-id               |\n    |              |       | 0         | CopyFilter (no filter)  |\n    |              |       | 1         | PaletteFilter           |\n    |              |       | 2         | GradientFilter          |\n    +--------------+-------+-----------+-------------------------+\n  ```\n  如果压缩控制字节的第 6 位设置为 0（无过滤器 ID 字节），则使用 `CopyFilter`。\n  \n  - `CopyFilter`\n\n     当 `CopyFilter` 处于活动状态时，将压缩 `TPIXEL` 格式的原始像素值。有关压缩的详细信息，请参见下文。\n  - `PaletteFilter`\n   \n     `PaletteFilter` 将真彩色像素数据转换为索引颜色和可由 2..256 种颜色组成的调色板。\n     如果颜色数为 2，则每个像素用 1 位编码，否则 8 位用于编码一个像素。\n     1位编码的执行方式是最高有效位对应于最左边的像素，并且每行像素都与字节边界对齐。\n     使用 `PaletteFilter` 时，调色板在像素数据之前发送。调色板以一个无符号字节开始，其值是调色板中的颜色数减 1（即 1 表示 2 种颜色，255 表示调色板中的 256 种颜色）。\n     然后是调色板本身，它由 `TPIXEL` 格式的像素值组成。\n  \n  - `GradientFilter`\n    \n    GradientFilter 使用一种简单的算法对像素数据进行预处理，该算法将每个颜色分量转换为“预测”强度和实际强度之间的差异。这种技术不会影响未压缩的数据大小，但有助于更好地压缩类似照片的图像。将强度转换为差异的伪代码如下： \n    ```\n    P[i,j] := V[i-1,j] + V[i,j-1] - V[i-1,j-1];\n    if (P[i,j] < 0) then P[i,j] := 0;\n    if (P[i,j] > MAX) then P[i,j] := MAX;\n    D[i,j] := V[i,j] - P[i,j];\n    ```\n    这里 `V[i,j]` 是坐标 `(i,j)` 处像素的颜色分量的强度。对于当前矩形之外的像素，假设 `V[i,j]` 为零（这与 `P[i,0]` 和 `P[0,j]` 相关）。 \n    MAX 是颜色分量的最大强度值。\n    > 注意：`GradientFilter` 只能在 bits-per-pixel 为 16 或 32 时使用。\n\n  使用上述三个过滤器之一过滤像素数据后，使用 `zlib` 库对其进行压缩。\n  但是，如果在应用过滤器之后但在压缩之前的数据大小小于 12，则数据按原样发送，未压缩。\n  可以使用四个单独的 zlib 流（0..3），解码器应该从压缩控制字节中读取实际的流 id。\n\n  如果不使用压缩，则按原样发送像素数据，否则数据流如下所示：\n\n  ```\n    +--------------+--------------+----------------+\n    | No. of bytes | Type      | Description       |\n    +--------------+-----------+-------------------+\n    | 1-3          |           | zlib数据的长度     |\n    | length       | U8 array  | zlibData         |\n    +--------------+-----------+-------------------+\n    ```\n  长度紧凑地表示为一个、两个或三个字节，就像在 JpegCompression 方法中一样（见上文）。\n\n如果压缩控制字节中的某些位 0、1、2 和 3 设置为 1，则解码器必须在解码矩形之前重置 `zlib` 流。请注意，即使压缩类型为解码器，解码器也必须重置指示的 zlib 流是 `FillCompression` 或 `JpegCompression`。\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/rfc6143/transfer/encoding/tiled-run-length.md",
    "content": "# 图块游程编码 TRLE\n\nTRLE（Tiled Run-Length Encoding），使用图块压缩技术、调色板技术以及游程编码，对传输的桌面图像进行压缩。\n\n在图块压缩技术中，传输图像被分为 N 个 16x16  的小图块，图块按从左到右，从上到下的顺序排列成一位数组。如果传输图像的尺寸不是 16 的整数倍，最后一列/最后一行图块的宽度/高度应为实际宽度/实际高度（小于16）。\n\nTRLE 使用 CPIXEL(compressed pixel) 表示像素。CPIXEL 同样由 [PIXEL_FORMAT](/rfc6143/transfer/pixel-format.md) 定义。\n在特殊情况下，CPIXEL 使用更紧凑的结构表示像素。\n假如 PIXEL_FORMAT 中 true-color-flag 是非零值，bits-per-pixel 是 32，depth 是 24 或更小，只会用低 3 bit 或者高 3 bit 来表达红、绿、蓝。\n\n## 子编码\n\n图块使用头部单字节表示图块的子编码类型。\n子编码类型的最高比特表示当前图块是否使用游程编码。\n低 7-bit 指示调色板的大小。\n\n```\n+---------------+--------------+------------------+\n| No. of bits   | Type [Value] | Description      |\n+------------------------------+------------------+\n| 1             | bit          | run-length-flag  |\n| 2-8           | bit          | palette-length   |\n+------------------------------+------------------+\n```\n\nTRLE 对自编码的值有单独定义\n\n| 十六进制        | 十进制       | 含义                            |\n|-------------|-----------|-------------------------------|\n| 0x00        | 0         | [Raw 模式](#raw-模式)，不使用游程和调色板   |\n| 0x01        | 1         | [纯色模式](#纯色模式)，图块使用同一种颜色       |\n| 0x02 - 0x10 | 2 - 16    | [打包像素调色板模式](#打包像素调色板模式)       |\n| 0x11 - 0x7E | 17 - 126  | 保留                            |\n| 0x7F        | 127       | [打包像素调色板模式](#打包像素调色板模式)，复用调色板 |\n| 0x80        | 128       | [RLE 模式](#rle-模式)             |\n| 0x81        | 129       | [调色板游程编码](#调色板游程编码)，复用调色板     |\n| 0x82 - 0xFF | 130 - 255 | [调色板游程编码](#调色板游程编码)           |\n\n\n\n### Raw 模式\n\nRaw 模式直接传输像素值，像素在图块中按从左到右、从上到下排列。不使用游程编码，不使用调色板。\n\n```\n+-----------------------------+----------------+--------------+\n| No. of bytes                | Type [Value]   | Description  |\n+-----------------------------+----------------+--------------+\n|                           1 | SubEncoding[0] | sub-encoding |\n| width*height*BytesPerCPixel | CPIXEL array   | pixels       |\n+-----------------------------+----------------+--------------+\n```\n\n### 纯色模式\n\n图块使用同一种颜色。\n\n```\n+----------------+----------------+--------------+\n| No. of bytes   | Type [Value]   | Description  |\n+----------------+----------------+--------------+\n|              1 | SubEncoding[1] | sub-encoding |\n| bytesPerCPixel | CPIXEL         | pixelValue   |\n+----------------+----------------+--------------+\n```\n\n### 打包像素调色板模式\n\n打包像素调色板模式。使用调色板定义颜色，使用颜色在调色板中的偏移量表达颜色。由于调色板较小，可以将多个像素的色值打包到一个字节中存储，进一步压缩体积。\n\n每个像素使用的 bit 数由调色板大小确定，N=log2 M。\n如果像素的数量不是 2/4/8 的倍数，需要添加填充位，对齐字节。\n\n| 调色板大小 | 像素比特数 | 打包后字节数                      |\n|:-----:|:-----:|-----------------------------|\n|  =2   |   1   | m = (width+7) // 8 *height  |\n|  <=4  |   2   | m = (width+3) // 4 * height |\n| <=16  |   4   | m = (width+1) // 2 * height |\n\n```\n+----------------------------+--------------+--------------+\n| No. of bytes               | Type [Value] | Description  |\n+----------------------------+--------------+--------------+\n|                          1 | SubEncoding  | sub-encoding |\n| paletteSize*bytesPerCPixel | CPIXEL array | palette      |\n| m                          | U8 array     | packedPixels |\n+----------------------------+--------------+--------------+\n```\n\n### RLE 模式\n\nRLE 模式，不使用调色板，使用游程编码。在计算游程时，允许跨行计算。\n长度由一个或多个字节表示，到第一个不是 255（0xFF）停止。\n\n```\n+-------------------------+------------------+-----------------------+\n| No. of bytes            | Type [Value]     | Description           |\n+-------------------------+------------------+-----------------------+\n|                       1 | SubEncoding[128] | sub-encoding          |\n| bytesPerCPixel          | CPIXEL           | pixelValue            |\n| div(runLength - 1, 255) | U8 array         | 255                   |\n| 1                       | U8               | (runLength-1) mod 255 |\n+-------------------------+------------------+-----------------------+\n```\n\n例如：\n\n| 游程长度 |   字节表示   |\n|:----:|:--------:|\n|  1   |   0x00   |\n| 255  |   0xFE   |\n| 256  |  0xFF00  |\n| 257  |  0xFF01  |\n| 510  |  0xFFFE  |\n| 511  | 0xFFFF00 |\n\n\n### 调色板游程编码\n\n调色板游程编码。使用调色板，游程部分类似[RLE 模式](#rle-模式)，将像素放入数组后，使用像素加游程的方式表示整个色块，像素用调色板偏移量表示。\n\n```\n+----------------------------+--------------+--------------+\n| No. of bytes               | Type [Value] | Description  |\n+----------------------------+--------------+--------------+\n|                          1 | SubEncoding  | sub-encoding |\n| paletteSize*bytesPerCPixel | CPIXEL array | palette      |\n+----------------------------+--------------+--------------+\n```\n\n\n- 长度为1的游程，仅由调色板索引表示\n\n```\n+--------------+--------------+--------------+\n| No. of bytes | Type [Value] | Description  |\n+--------------+--------------+--------------+\n| 1            | U8           | paletteIndex |\n+--------------+--------------+--------------+\n```\n\n- 长度大于1的游程，由调色板索引+128和游程长度表示\n\n```\n+-------------------------+--------------+-----------------------+\n| No. of bytes            | Type [Value] | Description           |\n+-------------------------+--------------+-----------------------+\n| 1                       | U8           | paletteIndex + 128    |\n| div(runLength - 1, 255) | U8 array     | 255                   |\n| 1                       | U8           | (runLength-1) mod 255 |\n+-------------------------+--------------+-----------------------+\n```\n"
  },
  {
    "path": "docs/rfc6143/transfer/encoding/zlib-run-length.md",
    "content": "# zlib 压缩游程编码\n\nZRLE 融合游程编码、Zlib 压缩算法、图块压缩算法对传输图像进行压缩。\n\nZRLE 使用 Zlib 进行编码和解码，要求流数据严格有序。\n\n```\n+--------------+--------------+-------------+\n| No. of bytes | Type [Value] | Description |\n+--------------+--------------+-------------+\n| 4            | U32          | length      |\n| length       | U8 array     | zlibData    |\n+--------------+--------------+-------------+\n```\n\n- length: 流(stream)长度\n- zlibData: 流数据\n\nZlibData 解压后，是从左到右，从上到下排列的图块数据。图块大小是 64x64，其他跟 TRLE 一致。对于长度和宽度不足的图形，其最后一排和最后一列的宽/高是实际尺寸，不做补齐。\n"
  },
  {
    "path": "docs/rfc6143/transfer/input/README.md",
    "content": "# 输入协议\n\nRFB 协议支持鼠标和键盘两种输入设备，同时支持剪贴板进行远程复制粘贴。\n"
  },
  {
    "path": "docs/rfc6143/transfer/input/clipboard.md",
    "content": "# 剪贴板\n\n复制粘贴是双向事件，可以由客户端向服务端复制，也可以由服务端向客户端复制。\n\n## 协议报文\n\n复制剪贴板的报文如下：\n\n```\n  +--------------+--------------+--------------+\n  | No. of bytes | Type [Value] | Description  |\n  +--------------+--------------+--------------+\n  | 1            | U8 [3/6]     | message-type |\n  | 3            |              | padding      |\n  | 4            | U32          | length       |\n  | length       | U8 array     | text         |\n  +--------------+--------------+--------------+\n```\n\n- message-type: 消息类型，客户端是 `0x6`，服务端是 `0x3`\n- length: 文本长度\n- text: 复制粘贴的文本，长度由 length 限制\n\n协议有几点限制\n\n- 只支持 ISO 8859-1 (Latin-1) 字符集\n- 使用单独换行符 `0x0a`，不应该使用回车符 `0x0d`\n\n## 拓展剪贴板伪协议\n\n> RFB 3.8 协议限制，剪贴板只能传输 Latin-1 字符集。\n> 2016年，Cendio Ossman 将 [Extended Clipboard Pseudo-Encoding](https://github.com/rfbproto/rfbproto/commit/08018f655acd52970680b34021159924357efb5d) 合入协议主分支，支持在剪贴板消息中传输 unicode 字符集。\n> UltraVNC/TigerVNC/RealVNC 服务端都支持此拓展协议，x11vnc 尚未提供支持（2021/8/11）。\n\n拓展剪贴板伪协议需要客户端和服务端软件同时支持。报文拓展了 `ServerCutText` 和 `ClientCutText`， 如下：\n\n```\n  +--------------+--------------+--------------+\n  | No. of bytes | Type [Value] | Description  |\n  +--------------+--------------+--------------+\n  | 1            | U8 [3/6]     | message-type |\n  | 3            |              | padding      |\n  | 4            | S32          | length       |\n  | 4            | U32          | text-type    |\n  | length-4     | U8 array     | text         |\n  +--------------+--------------+--------------+\n```\n\n- length: 数据由 U32 改为 S32。首bit是标志位，0 表示传递原始 Latin-1 消息，1 表示传递拓展信息。abs(length) 是实际的消息长度。\n- text-type: 消息头部，指示消息类型\n- text: 消息内容\n\n### 消息类型\n\n消息用 4 字节的 text-type 作为头部。\n\n```\n  +--------------+--------------+--------------+\n  | No. of bytes | Type [Value] | Description  |\n  +--------------+--------------+--------------+\n  | 1            | U32          | message-type |\n  +--------------+--------------+--------------+\n```\n\ntext-type 分为指令（`action`）和格式（`formats`）两类。指令传输操作命令，格式传输剪贴板内容。\n\ntext-type 标记的含义如下：\n\n| Bit\t| Name | Description |\n|-|-|---|\n| 0\t| [text](#文本内容) | 文本内容 |\n| 1| rtf | 微软富文本格式 |\n| 2\t| html | 微软 HTML 格式 |\n| 3\t| dib | Microsoft Device Independent Bitmap |\n| 4\t| files | 文件，暂未实现 |\n| 5-15| fotmats 保留位 |\n| 16-23\t| 保留 |\n| 24\t| [caps](#能力声明) | 指示支持的 text-type 和最大长度 |\n| 25\t| request | 强制对端传递剪贴板内容 |\n| 26\t| peek | 强制对端提供支持的 text-type |\n| 27\t| notify | peek 回包，返回支持的 text-type |\n| 28\t| [provide](#粘贴板内容) | request 回包，返回粘贴板内容 |\n| 29-31\t| actions 保留位 | |\n\n### 文本内容 text\n\n纯文本，unicode 编码的无格式文本。以 `\\r\\n` 作为行的结尾，原始协议的换行符是 `\\n`。\n\n文本应该以 `\\0` 结尾，即使在 ClientCutText/ServerCutText 中都声明了文本长度。\n\n### 能力声明 caps\n\nCaps 指示期望收到的文本类型，发送的结构是长度数组。数组大小跟格式数量相等（0-15），数组的每个条目，指示格式支持的最大长度。\n\n#### 数据结构\n\n```\n  +--------------+--------------+--------------+\n  | No. of bytes | Type [Value] | Description  |\n  +--------------+--------------+--------------+\n  | formats*4    | U32 array    | sizes        |\n  +--------------+--------------+--------------+\n```\n\n例如：\n\n```\n[1024,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]\n```\n\n表示只接受 1024 byte 以内的纯文本信息。\n\n#### 行为约束\n\n服务端收到支持拓展剪贴板协议的 SetEncodings 报文时，必须主动发送类型为 caps 的 ServerCutText 消息。\n\n客户端收到 caps 消息时，应该发送类型为 caps 的 ClientCutText 消息作为回应。\n否则，客户端默认会接受 text/rtf/html/request/notify/provide 消息，其中 text 默认长度为 20 Mib，其他为 0 字节。\n\n当最大长度限制为 0 时，认为长度没有限制，如果内容长度大于声明的长度限制，则剪贴板变动的消息不会被发送。建议将所有的 caps 设置为 0，以便接受所有的剪贴板消息变动。\n\n> 某些实现的默认行为与协议描述不一致，例如：\n> - dib 也是默认支持的格式\n> - text 的默认限制是 10Mid\n> - rft/html 默认限制为 2Mib\n> - dib 默认限制为 0 字节\n> - 客户端忽略 caps 消息建议的格式和长度限制\n\n在发送 caps 之前，只能发送指令，不能发送格式和内容。\n\n### 粘贴板内容\n\n粘贴板内容的 text-type 是 provide。在剪贴板变化，或对端发送 request 后发送。\n在 text-type 后面，是 Zlib 压缩的字节流。对于每种支持的 text-type，会发送 size + data 数据对。\n\n```\n  +--------------+--------------+--------------+\n  | No. of bytes | Type [Value] | Description  |\n  +--------------+--------------+--------------+\n  | 4            | U32          | size         |\n  | size         | U8 array     | data         |\n  +--------------+--------------+--------------+\n```\n"
  },
  {
    "path": "docs/rfc6143/transfer/input/keyboard.md",
    "content": "# 键盘事件\n\n在正常理解中，键盘事件的处理应该是简单明了的。参考以下协议报文\n\n```\n  +--------------+--------------+--------------+\n  | No. of bytes | Type [Value] | Description  |\n  +--------------+--------------+--------------+\n  | 1            | U8 [4]       | message-type |\n  | 1            | U8           | down-flag    |\n  | 2            |              | padding      |\n  | 4            | U32          | key          |\n  +--------------+--------------+--------------+\n```\n\n- message-type: 固定为 `0x4`\n- down-flag: `1` 表示键位按下，`0` 表示弹起\n- pending: 对齐字节，方便解析\n- key: 表示具体的键位\n\n其中 key 的值在 X 系统中有[明确定义](https://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.html#keysym_encoding)\n\n```\n +-----------------+--------------------+\n | Key name        | Keysym value (hex) |\n +-----------------+--------------------+\n | BackSpace       | 0xff08             |\n | Tab             | 0xff09             |\n | Return or Enter | 0xff0d             |\n | Escape          | 0xff1b             |\n | Insert          | 0xff63             |\n | Delete          | 0xffff             |\n | Home            | 0xff50             |\n | End             | 0xff57             |\n | Page Up         | 0xff55             |\n | Page Down       | 0xff56             |\n | Left            | 0xff51             |\n | Up              | 0xff52             |\n | Right           | 0xff53             |\n | Down            | 0xff54             |\n | F1              | 0xffbe             |\n | F2              | 0xffbf             |\n | F3              | 0xffc0             |\n | F4              | 0xffc1             |\n | ...             | ...                |\n | F12             | 0xffc9             |\n | Shift (left)    | 0xffe1             |\n | Shift (right)   | 0xffe2             |\n | Control (left)  | 0xffe3             |\n | Control (right) | 0xffe4             |\n | Meta (left)     | 0xffe7             |\n | Meta (right)    | 0xffe8             |\n | Alt (left)      | 0xffe9             |\n | Alt (right)     | 0xffea             |\n +-----------------+--------------------+\n```\n\n## 组合键\n\n组合键指 `Ctrl + Alt + Del` 或 `Shift + 3` 等组合按键。受不同的操作系统、键盘布局影响，组合键是按键事件中容易发生歧义的一环。\n\nRFB 基本遵循以下规则：\n\n- 如果客户端 key 在 `keysym` 中存在，服务端应该遵循 `keysym` 的指示，尽可能的忽略客户端传递 `Shift`、`CpasLock` 等键位，在需要时，应该主动补充/忽略 `Shift` 等键位。例如，在 US 键盘布局中，`#` 需要按下 `Shift + 3`，但是在 UK 布局中不需要。这就意味着用户在输入 `#` 的时候不会输入 `Shift`。这种情况下，服务端应该主动模拟一个 `Shift` 状态，防止输入的键位是 `3`。同理，如果 key 输入的键位是 `A`，服务端统一要模拟一个 `Shift`，保证输入的是 `A` 而不是 `a`。\n- 如果客户端 key 在 `keysym` 中不存在（例如 `Ctrl + A`），服务端应该遵循客户端指示，客户端应该主动在 `A` 前发送 `Ctrl` 的按键。\n- 如果客户端通过 `Ctrl + Alt + Q` 来输入 `@`，客户端应该在发送 `Ctrl`/`Alt`/`@`后，主动发送`Ctrl`/`Alt`的弹起事件。\n- 对于 `BackTab`，常见的有三种实现，`ISO_Left_Tab` `BackTab` 和 `Shift + Tab`。RFB 协议优先使用 `Shift + Tab`，但对于其他的键位，服务端和客户端应当尽量提供兼容。\n- 优先使用 `ASCII` 而不是 `unicode`\n- 对于 `Ctrl + Alt + Del` 等无法被客户端操作系统拦截的按键（系统拦截有更高优先级），客户端应该提供操作按钮\n"
  },
  {
    "path": "docs/rfc6143/transfer/input/mouse.md",
    "content": "# 鼠标事件\n\n鼠标指针即鼠标操作事件，分移动事件和点击事件两种。在 RFB 协议中，使用 PointerEvent 表示客户端触发一次鼠标事件。\n\n## 协议报文\n\n鼠标事件的报文如下\n\n```\n  +--------------+--------------+--------------+\n  | No. of bytes | Type [Value] | Description  |\n  +--------------+--------------+--------------+\n  | 1            | U8 [5]       | message-type |\n  | 1            | U8           | button-mask  |\n  | 2            | U16          | x-position   |\n  | 2            | U16          | y-position   |\n  +--------------+--------------+--------------+\n```\n\n- message-type: 无符号整形，固定 `0x5`\n- button-mask: 8 位掩码，表示键位状态，`1`为按下，`0`为弹起\n- x-position: 当前 X 坐标\n- y-position: 当前 Y 坐标\n\nbutton-mask 的 Bit0/1/2 位置分别代表鼠标的左键、中建、右键。使用滚轮的鼠标，每向上滑动一次，会发送一个 Bit3 的按下和弹起事件；每向下滑动一次，会发送一个 Bit4 的按下和弹起事件。\n\n## USB/PS/2 鼠标协议\n\n在 PS/2 或 USB 鼠标协议中，有类似 RFB 协议的表达方式。\n\n```\n ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐\n │      │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │\n ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤\n │Byte 1│Y Over│X Over│X Sign│Y Sign│ 0x1  │ Mid  │Right │ Left │\n ├──────┼──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┤\n │Byte 2│                      X Movement                       │\n ├──────┼───────────────────────────────────────────────────────┤\n │Byte 3│                      Y Movement                       │\n ├──────┼───────────────────────────────────────────────────────┤\n │Byte 4│                      Z Movement                       │\n └──────┴───────────────────────────────────────────────────────┘\n```\n\n鼠标协议和 RFB 协议有两点不同：\n\n- 鼠标协议的 Bit0/1/2 和 RFB 协议的 Bit0/2/1 对应\n- 鼠标协议的中轮转动，需要转换为 RFB 协议的按下/弹起事件\n\n> 根据 [wiki](https://en.wikipedia.org/wiki/RFB_protocol#Limitations) 描述，RFB 协议只支持8个鼠标键位，左中右和滑轮占用5个，只有3个键位给特殊按键（例如：多功能游戏鼠标）\n"
  },
  {
    "path": "docs/rfc6143/transfer/pixel-format.md",
    "content": "# 像素格式\n\n[帧缓冲](/rfc6143/GLOSSORY.md#帧缓冲)由像素构成。\n\n![像素组成帧缓冲](../../images/rfc6143-transfer-pixel-format-1.png)\n\n## PixelFormat\n\nRFB 协议中，使用 16 字节结构体 PIXEL_FORMAT 描述像素的格式。\n\n```\n +--------------+--------------+-----------------+\n | No. of bytes | Type [Value] | Description     |\n +--------------+--------------+-----------------+\n | 1            | U8           | bits-per-pixel  |\n | 1            | U8           | depth           |\n | 1            | U8           | big-endian-flag |\n | 1            | U8           | true-color-flag |\n | 2            | U16          | red-max         |\n | 2            | U16          | green-max       |\n | 2            | U16          | blue-max        |\n | 1            | U8           | red-shift       |\n | 1            | U8           | green-shift     |\n | 1            | U8           | blue-shift      |\n | 3            |              | padding         |\n +--------------+--------------+-----------------+\n```\n\n- bits-per-pixel: 像素的位数，位数越大，色彩越丰富。只支持[8|16|32]\n- depth: 色深，像素中表示色彩的位数\n- big-endian-flag: 多字节像素的字节序，非零即大端序\n- true-color-flag: 1 表示真彩色，pixel 的值表示 RGB 颜色；0 表示调色板，pexel 的值表示颜色在调色板的偏移量\n- -max/-shift: 获取红蓝绿三色的位移量和长度，max=2^N-1,N是颜色的位数\n\n```\n BigEndian:    Blue Shift       Green Shift         Red Shift\n                   │                 │                 │\n                   ▼                 ▼                 ▼\n┌──────────────────┬─────────────────┬─────────────────┐\n│     BLUE MAX     │    GREEN MAX    │     RED MAX     │\n└──────────────────┴─────────────────┴─────────────────┘\n```\n\n> bits-per-pixel 必须大于或等于 depth\n\n## SetPixelFormat\n\n客户端发送 SetPixelFormat，声明需要的的像素格式（画面质量）。此消息覆盖 [ServerInit](/rfc6143/handshake/initial.md#服务端初始化) 消息中服务端声明的初始化像素格式。\n\n当 true-color-flag 为 0 时，服务端必须发送 SetColorMapEntries，声明使用的颜色表。客户端发送 SetPixelFormat 后，需清空本地缓存的颜色表，无论颜色表中是否有内容。\n\n```\n+--------------+--------------+--------------+\n| No. of bytes | Type [Value] | Description  |\n+--------------+--------------+--------------+\n| 1            | U8 [0]       | message-type |\n| 3            |              | padding      |\n| 16           | PIXEL_FORMAT | pixel-format |\n+--------------+--------------+--------------+\n```\n\n- message-type: 消息类型，固定为 0\n- pixel-format: [PixelFormat](#pixelformat) 结构\n"
  },
  {
    "path": "docs/rfc6143/transfer/set-color-map.md",
    "content": "# 设置颜色表\n\n当 PIXEL_FORMAT 的 true-color-flag 字段被设置为 0 时，服务端使用颜色表表示像素的颜色。\n`SetColorMapEntries` 用于设置颜色表的内容。\n\n```\n+--------------+--------------+------------------+\n| No. of bytes | Type [Value] | Description      |\n+--------------+--------------+------------------+\n| 1            | U8 [1]       | message-type     |\n| 1            |              | padding          |\n| 2            | U16          | first-color      |\n| 2            | U16          | number-of-colors |\n+--------------+--------------+------------------+\n```\n\n- message-type: 消息类型，固定是 `1`\n- first-color: [未知](https://github.com/rfbproto/rfbproto/issues/42)\n- number-of-colors: 颜色的数量\n\n## 色值\n\n颜色表的值总是 3 个 16 bits，代表红、绿、蓝三种颜色，每个颜色的范围是 0-65535。\n例如，白色的色值是 65535,65535,65535。\n\n```\n+--------------+--------------+-------------+\n| No. of bytes | Type [Value] | Description |\n+--------------+--------------+-------------+\n| 2            | U16          | red         |\n| 2            | U16          | green       |\n| 2            | U16          | blue        |\n+--------------+--------------+-------------+\n```\n"
  },
  {
    "path": "docs/screenshot/.keep",
    "content": ""
  },
  {
    "path": "docs/screenshot/README.md",
    "content": "## Screenshot\n\n代码路径在`./cmd/screenshot`,如果单独编译该组件，也可以到该目录下自行执行`go build`命令编译\n\n### 获取帮助信息\n```shell\n# 查看帮助信息\n$ ./screenshot --help\n\n# 查看版本信息\n$ ./screenshot version\n```\n\n### 启动Screenshot 获取vnc服务器的屏幕截图\n\n```shell\n\n# imageFile  要生成的截图地址,暂时只支持jpeg格式(必填)\n# vncHost   要连接的vnc服务端地址(必填)\n# vncPort   要连接的vnc服务端端口(必填)\n# vncPassword  要连接的vnc服务端密码，不传则使用auth none\n\n$ ./screenshot --imageFile=./screen.jpeg --vncHost=127.0.0.1 --vncPort=5900 --vncPassword=12345612       \n```"
  },
  {
    "path": "docs/summary.md",
    "content": "* 快速入门\n  * [项目介绍](README.md)\n  * [快速开始](overview.md)\n  * [常见问题](questions.md)\n  * [版本更新记录](changelog.md)\n\n* 代理(Proxy)\n  - [使用方式](proxy/README.md)\n* 屏幕录像(Recorder)\n  - [使用方式](recorder/README.md)\n* 屏幕录像播放(Player)\n  - [使用方式](player/README.md)\n* 截屏(Screenshot)\n  - [使用方式](screenshot/README.md)\n* 屏幕录像转视频(Video)\n\n* RFB协议详解\n  - [远程帧缓冲协议](rfc6143/README.md)\n  - [术语表](rfc6143/GLOSSORY.md)\n  - [握手](rfc6143/handshake/README.md)\n    - [协议版本握手](rfc6143/handshake/protocol-version.md)\n    - [安全握手](rfc6143/handshake/security-type.md)\n    - [初始化](rfc6143/handshake/initial.md)\n  - [数据交互](rfc6143/transfer/README.md)\n    - [显示协议](rfc6143/transfer/display.md)\n    - [像素格式](rfc6143/transfer/pixel-format.md)\n    - [输入协议](rfc6143/transfer/input/README.md)\n      - [鼠标事件](rfc6143/transfer/input/mouse.md)\n      - [键盘事件](rfc6143/transfer/input/keyboard.md)\n      - [剪贴板](rfc6143/transfer/input/clipboard.md)\n  - [编码](rfc6143/transfer/encoding/README.md)\n      - [设置编码](rfc6143/transfer/encoding/set-encoding.md)\n      - [Raw 原始编码](rfc6143/transfer/encoding/raw.md)\n      - [CopyRect 镜像编码](rfc6143/transfer/encoding/copy-rect.md)\n      - [RRE 上升游程编码](rfc6143/transfer/encoding/rise-and-run-length.md)\n      - [TRLE 图块游程编码](rfc6143/transfer/encoding/tiled-run-length.md)\n      - [ZRLE Zlib游程编码](rfc6143/transfer/encoding/zlib-run-length.md)\n  \n  "
  },
  {
    "path": "docs/video/.keep",
    "content": ""
  },
  {
    "path": "docs/video/README.md",
    "content": ""
  },
  {
    "path": "encodings/default_encoding.go",
    "content": "package encodings\n\nimport \"github.com/vprix/vncproxy/rfb\"\n\nvar (\n\tDefaultEncodings = []rfb.IEncoding{\n\t\t&ZRLEEncoding{},\n\t\t&TightEncoding{},\n\t\t&HexTileEncoding{},\n\t\t&TightPngEncoding{},\n\t\t&RREEncoding{},\n\t\t&ZLibEncoding{},\n\t\t&CopyRectEncoding{},\n\t\t&CoRREEncoding{},\n\t\t&RawEncoding{},\n\t\t&CursorPseudoEncoding{},\n\t\t&DesktopNamePseudoEncoding{},\n\t\t&DesktopSizePseudoEncoding{},\n\t\t&CursorPosPseudoEncoding{},\n\t\t&ExtendedDesktopSizePseudo{},\n\t\t&CursorWithAlphaPseudoEncoding{},\n\t\t&LedStatePseudo{},\n\t\t&LastRectPseudo{},\n\t\t&FencePseudo{},\n\t\t&XCursorPseudoEncoding{},\n\t}\n)\n"
  },
  {
    "path": "encodings/encoding.go",
    "content": "package encodings\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"github.com/vprix/vncproxy/internal/dbuffer\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"io\"\n)\n\nfunc ReadUint8(r io.Reader) (uint8, error) {\n\tvar myUint uint8\n\tif err := binary.Read(r, binary.BigEndian, &myUint); err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn myUint, nil\n}\n\nfunc ReadUint16(r io.Reader) (uint16, error) {\n\tvar myUint uint16\n\tif err := binary.Read(r, binary.BigEndian, &myUint); err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn myUint, nil\n}\n\nfunc ReadUint32(r io.Reader) (uint32, error) {\n\tvar myUint uint32\n\tif err := binary.Read(r, binary.BigEndian, &myUint); err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn myUint, nil\n}\n\nfunc ReadBytes(count int, r io.Reader) ([]byte, error) {\n\tbuff := dbuffer.GetByteBuffer()\n\tdefer dbuffer.ReleaseByteBuffer(buff)\n\tbuff.ChangeLen(count)\n\tlengthRead, err := io.ReadFull(r, buff.B)\n\n\tif lengthRead != count {\n\t\treturn nil, errors.New(\"ReadBytes unable to read bytes\")\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buff.Bytes(), nil\n}\n\nfunc ReadPixel(c io.Reader, pf *rfb.PixelFormat) ([]byte, error) {\n\tpx := make([]byte, int(pf.BPP/8))\n\tif err := binary.Read(c, pf.Order(), &px); err != nil {\n\t\treturn nil, err\n\t}\n\treturn px, nil\n}\n"
  },
  {
    "path": "encodings/encoding_copyrect.go",
    "content": "package encodings\n\nimport (\n\t\"encoding/binary\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// CopyRectEncoding 该编码方式对于客户端在某些已经有了相同的象素数据的时候是非常简单和有效的。\n// 这种编码方式在网络中表现为x,y 坐标。让客户端知道去拷贝那一个矩形的象素数据。\n// 它可以应用于很多种情况。最明显的就是当用户在屏幕上移动某一个窗口的时候，还有在窗口内容滚动的时候。\n// 在优化画的时候不是很明显，一个比较智能的服务器可能只会发送一次，因为它知道在客户端的帧缓存里已经存在了。\n// 复制矩形编码并不是完全独立地发送所有的数据矩形，而是对于像素值完全相同的一组矩形，\n// 只发送第一个矩形全部数据，随后的矩形则只需要发送左上角X、Y坐标。\n// 实际上，复制矩形编码主要指的就是随后的这一系列X、Y坐标，而对于第一个矩形具体采用何种编码类型并没有限制，\n// 仅仅需要知道第一个矩形在帧缓冲区中的位置，以便于完成复制操作。\n// 因此，往往是把复制矩形编码和其它针对某一个矩形的编码类型结合使用。\ntype CopyRectEncoding struct {\n\tSX, SY uint16\n}\n\nfunc (that *CopyRectEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncCopyRect\n}\n\nfunc (that *CopyRectEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *CopyRectEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &CopyRectEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tobj.SX = that.SX\n\t\tobj.SY = that.SY\n\t}\n\treturn obj\n}\n\nfunc (that *CopyRectEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif err := binary.Read(session, binary.BigEndian, &that.SX); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &that.SY); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (that *CopyRectEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif err := binary.Write(session, binary.BigEndian, that.SX); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.SY); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/encoding_corre.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// CoRREEncoding CoRRE是RRE的变体，它把发送的最大矩形限制在255×255个像素以内，用一个字节就能表示子矩形的维度。\n// 如果服务器想要发送一个超出限制的矩形，则只要把它划分成几个更小的RFB矩形即可。\n// “对于通常的桌面，这样的方式具有比RRE更好的压缩度”。\n// 实际上，如果进一步限制矩形的大小，就能够获得最好的压缩度。“矩形的最大值越小，决策的尺度就越好”。\n// 但是，如果把矩形的最大值限制得太小，就增加了矩形的数量，而由于每个RFB矩形都会有一定的开销，结果反而会使压缩度变差。\n// 所以应该选择一个比较恰当的数字。在目前的实现中，采用的最大值为48×48。\ntype CoRREEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nfunc (that *CoRREEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncCoRRE\n}\n\nfunc (that *CoRREEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *CoRREEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &CoRREEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\n\treturn obj\n}\n\nfunc (that *CoRREEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tpf := session.Options().PixelFormat\n\t// 子矩形的数量\n\tvar numOfSubRectangles uint32\n\tif err := binary.Read(session, binary.BigEndian, &numOfSubRectangles); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(that.buff, binary.BigEndian, numOfSubRectangles); err != nil {\n\t\treturn err\n\t}\n\t// (backgroundColor + (color=BPP + x=8b + y=8b + w=8b + h=8b))\n\tsize := uint32(pf.BPP/8) + (uint32((pf.BPP/8)+4) * numOfSubRectangles)\n\tb, err := ReadBytes(int(size), session)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = that.buff.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (that *CoRREEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) (err error) {\n\tif that.buff == nil {\n\t\treturn errors.New(\"ByteBuffer is nil\")\n\t}\n\t_, err = that.buff.WriteTo(session)\n\tthat.buff.Reset()\n\treturn err\n}\n"
  },
  {
    "path": "encodings/encoding_h264.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\ntype H264Encoding struct {\n\tbuff *bytes.Buffer\n}\n\nvar _ rfb.IEncoding = new(H264Encoding)\n\nfunc (that *H264Encoding) Type() rfb.EncodingType {\n\treturn rfb.EncH264\n}\n\nfunc (that *H264Encoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *H264Encoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &ZLibEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\n\nfunc (that *H264Encoding) Read(session rfb.ISession, rectangle *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tsize, err := ReadUint32(session)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = binary.Write(that.buff, binary.BigEndian, size)\n\tif err != nil {\n\t\treturn err\n\t}\n\tflags, err := ReadUint32(session)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = binary.Write(that.buff, binary.BigEndian, flags)\n\tif err != nil {\n\t\treturn err\n\t}\n\tb, err := ReadBytes(int(size), session)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = that.buff.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (that *H264Encoding) Write(session rfb.ISession, rectangle *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\treturn errors.New(\"ByteBuffer is nil\")\n\t}\n\t_, err := that.buff.WriteTo(session)\n\tthat.buff.Reset()\n\treturn err\n}\n"
  },
  {
    "path": "encodings/encoding_hextile.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/errors/gerror\"\n\t\"github.com/vprix/vncproxy/canvas\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"image\"\n\t\"image/color\"\n)\n\nconst (\n\tHexTileRaw                 = 1 << 0 // Raw数据：不压缩，直接传送，一般此位置1时其它位都置0\n\tHexTileBackgroundSpecified = 1 << 1 // 包含背景色数据：标志位之后需要接收背景色数据\n\tHexTileForegroundSpecified = 1 << 2 // 包含前景色数据：背景色之后需要接收前景色数据\n\tHexTileAnySubRects         = 1 << 3 // 是否含有子块：只要该块中含有两种及两种以上颜色，则此位置1\n\tHexTileSubRectsColoured    = 1 << 4 // 子块的颜色：如果含有两种颜色，此位置0，子块颜色用前景色；若该块中含有两种以上的颜色，此位置1，子块颜色需要单独指明\n)\n\n// HexTileEncoding 是RRE编码的变种，把屏幕分成16x16象素的小块，每块用Raw或RRE方式转送.\n// 通过解释HexTile算法，说明了简单而常用的屏幕传送和压缩算法，希望对屏幕监测、传送相关的工作有所启发\ntype HexTileEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nvar _ rfb.IEncoding = new(HexTileEncoding)\n\nfunc (that *HexTileEncoding) Supported(_ rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *HexTileEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &HexTileEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\n\nfunc (that *HexTileEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncHexTile\n}\n\n//  1. 分割\n//     a. 传送的图像区域被分为若干个大小为16×16象素的块，如果整个矩形不是16的倍数，则最后1行（或列）的块宽度（或高度）变小\n//     b. 这些块按从左到右，从上到下的顺序排列，屏幕中变化的块被传送，不变的不被传送.\n//     c. 由于块大小是16x16，所以块内坐标XY可用一字节表示，WH可用一个字节表示\n//  2. 块内部的编码\n//     a. 计算块内部的颜色数：一种、两种、多种，并记录出现频率最多的颜色为背景色，如果仅有两种颜色，则另一颜色记为前景色\n//     b. 判断块内颜色数是否为一种，如果是，则先修改传送标志位，然后传送整块大小（0，0，w，h）和背景色，此块传送即完成.\n//     c. 如果不是一种，则把块拆分成颜色不同的小矩形，方法如下：\n//  1. 先把块复制到一块内存区中，以免破坏原始数据，暂称tmpBuf\n//  2. 从第一个象素开始判断，该点颜色是否与背景色相同.\n//  3. 如果不同，则分别向右和向下求得与该点颜色连续的色块.\n//  4. 对比右色块和下色块，取出其中较大的一个，做为一个矩形色块\n//  5. 在tmpBuf中把此矩形填成背景色，以避免重复判断\n//  6. 继续判断下一象素点……\n//     d. 记录各个矩形色块的位置（x,y,w,h），如果块内含两种以上颜色，还要记录矩形色块的颜色值\n//     e. 一边取得矩形色块，一边判断矩形色块描述数据的总长是否大于原始数据，如果大于原始数据，则放弃取色块，标志字节Raw(HexTileRaw)位置\n//  1. 以Raw方式直接传送原始数据，此块传送完成\n//     f. 如果块含两种以上颜色，则将标志位的子块位（HexTileSubRectsColoured）置1，否则置0\n//     g. 传送标志位，传送矩形色块个数，然后传送各矩形块数据，块中颜色数为2时不需要传送每个矩形块的颜色数据，只传位置即可.\n//     h. 注意：如果背景色或前景色与前一块(16x16块)相同，则可以不传送背景色或前景色，客户端会默认延用前一块的\nfunc (that *HexTileEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tbytesPerPixel := int(session.Options().PixelFormat.BPP) / 8\n\t// 从上到下\n\tfor ty := rect.Y; ty < rect.Y+rect.Height; ty += 16 {\n\t\tth := 16\n\t\t//  如果整个矩形不是16的倍数，则最后1列的块高度为实际高度\n\t\tif rect.Y+rect.Height-ty < 16 {\n\t\t\tth = int(rect.Y) + int(rect.Height) - int(ty)\n\t\t}\n\t\t// 从左到右\n\t\tfor tx := rect.X; tx < rect.X+rect.Width; tx += 16 {\n\t\t\ttw := 16\n\t\t\t//  如果整个矩形不是16的倍数，则最后1行的块宽度为实际宽度\n\t\t\tif rect.X+rect.Width-tx < 16 {\n\t\t\t\ttw = int(rect.X) + int(rect.Width) - int(tx)\n\t\t\t}\n\t\t\tvar bt []byte\n\t\t\t// 读取标志位\n\t\t\tsubEncoding, err := ReadUint8(session)\n\t\t\tif err != nil {\n\t\t\t\treturn gerror.Newf(\"HextileEncoding.Read: error in hextile reader: %v\", err)\n\t\t\t}\n\t\t\t_ = binary.Write(that.buff, session.Options().PixelFormat.Order(), subEncoding)\n\t\t\t// 如果是原始编码\n\t\t\tif (subEncoding & HexTileRaw) != 0 {\n\t\t\t\tbt, err = ReadBytes(tw*th*bytesPerPixel, session)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn gerror.Newf(\"HextileEncoding.Read: error in hextile reader: %v\", err)\n\t\t\t\t}\n\t\t\t\t_, _ = that.buff.Write(bt)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// 包含背景色数据\n\t\t\tif (subEncoding & HexTileBackgroundSpecified) != 0 {\n\t\t\t\tbt, err = ReadBytes(bytesPerPixel, session)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn gerror.Newf(\"HextileEncoding.Read: error in hextile reader: %v\", err)\n\t\t\t\t}\n\t\t\t\t_, _ = that.buff.Write(bt)\n\t\t\t}\n\n\t\t\t// 包含前景色数据\n\t\t\tif (subEncoding & HexTileForegroundSpecified) != 0 {\n\t\t\t\tbt, err = ReadBytes(bytesPerPixel, session)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn gerror.Newf(\"HextileEncoding.Read: error in hextile reader: %v\", err)\n\t\t\t\t}\n\t\t\t\t_, _ = that.buff.Write(bt)\n\t\t\t}\n\n\t\t\t// 不包含子块则跳过\n\t\t\tif (subEncoding & HexTileAnySubRects) != 0 {\n\t\t\t\tnSubRects, err := ReadUint8(session)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn gerror.Newf(\"HextileEncoding.Read: error in hextile reader: %v\", err)\n\t\t\t\t}\n\t\t\t\t_ = binary.Write(that.buff, session.Options().PixelFormat.Order(), nSubRects)\n\n\t\t\t\tfor i := 0; i < int(nSubRects); i++ {\n\t\t\t\t\tif (subEncoding & HexTileSubRectsColoured) != 0 {\n\t\t\t\t\t\tbt, err = ReadBytes(bytesPerPixel, session)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn gerror.Newf(\"HextileEncoding.Read: error in hextile reader: %v\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, _ = that.buff.Write(bt)\n\t\t\t\t\t}\n\t\t\t\t\txy, err := ReadUint8(session)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn gerror.Newf(\"HextileEncoding.Read: error in hextile reader: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\t_ = binary.Write(that.buff, session.Options().PixelFormat.Order(), xy)\n\n\t\t\t\t\twh, err := ReadUint8(session)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn gerror.Newf(\"HextileEncoding.Read: error in hextile reader: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\t_ = binary.Write(that.buff, session.Options().PixelFormat.Order(), wh)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (that *HexTileEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error {\n\tif sess.Type() == rfb.CanvasSessionType {\n\t\treturn that.draw(sess.Conn().(*canvas.VncCanvas), sess.Options().PixelFormat, rect)\n\t}\n\tvar err error\n\t_, err = that.buff.WriteTo(sess)\n\tthat.buff.Reset()\n\treturn err\n}\n\nfunc (that *HexTileEncoding) draw(cv *canvas.VncCanvas, pf rfb.PixelFormat, rect *rfb.Rectangle) error {\n\tvar bgCol *color.RGBA\n\tvar fgCol *color.RGBA\n\tvar err error\n\tvar subEncoding byte\n\tvar dimensions byte\n\tvar nSubRects uint8\n\n\t// 从上到下\n\tfor ty := rect.Y; ty < rect.Y+rect.Height; ty += 16 {\n\t\tth := 16\n\t\t//  如果整个矩形不是16的倍数，则最后1列的块高度为实际高度\n\t\tif rect.Y+rect.Height-ty < 16 {\n\t\t\tth = int(rect.Y) + int(rect.Height) - int(ty)\n\t\t}\n\t\t// 从左到右\n\t\tfor tx := rect.X; tx < rect.X+rect.Width; tx += 16 {\n\t\t\ttw := 16\n\t\t\t//  如果整个矩形不是16的倍数，则最后1行的块宽度为实际宽度\n\t\t\tif rect.X+rect.Width-tx < 16 {\n\t\t\t\ttw = int(rect.X) + int(rect.Width) - int(tx)\n\t\t\t}\n\t\t\tsubEncoding, err = ReadUint8(that.buff)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"HextileEncoding.Read: error in hextile reader: %v\", err)\n\t\t\t}\n\t\t\t// 如果是原始编码\n\t\t\tif (subEncoding & HexTileRaw) != 0 {\n\t\t\t\terr = cv.DecodeRaw(that.buff, &pf, &rfb.Rectangle{X: tx, Y: ty, Width: uint16(tw), Height: uint16(th), EncType: rfb.EncRaw})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// 读取单个背景颜色\n\t\t\tif (subEncoding & HexTileBackgroundSpecified) != 0 {\n\t\t\t\tbgCol, err = cv.ReadColor(that.buff, &pf)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"HexTileEncoding.Read: error in hexTile bg color reader: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 绘制一个矩形\n\t\t\trBounds := image.Rectangle{\n\t\t\t\tMin: image.Point{X: int(tx), Y: int(ty)},\n\t\t\t\tMax: image.Point{X: int(tx) + tw, Y: int(ty) + th},\n\t\t\t}\n\t\t\t// 填充背景色\n\t\t\tcv.FillRect(&rBounds, bgCol)\n\n\t\t\t// 读取前景色\n\t\t\tif (subEncoding & HexTileForegroundSpecified) != 0 {\n\t\t\t\tfgCol, err = cv.ReadColor(that.buff, &pf)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"HexTileEncoding.Read: error in hexTile fg color reader: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (subEncoding & HexTileAnySubRects) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// 读取子块的个数\n\t\t\tnSubRects, err = ReadUint8(that.buff)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// 是否指定子块的填充颜色，如果未指定，则使用前景色\n\t\t\tcolorSpecified := (subEncoding & HexTileSubRectsColoured) != 0\n\t\t\tfor i := 0; i < int(nSubRects); i++ {\n\t\t\t\tvar co *color.RGBA\n\t\t\t\tif colorSpecified {\n\t\t\t\t\tco, err = cv.ReadColor(that.buff, &pf)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"HexTileEncoding.Read: problem reading color from connection: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tco = fgCol\n\t\t\t\t}\n\t\t\t\tfgCol = co\n\t\t\t\tdimensions, err = ReadUint8(that.buff) // bits 7-4 for x, bits 3-0 for y\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"HexTileEncoding.Read: problem reading dimensions from connection: %v\", err)\n\t\t\t\t}\n\t\t\t\tsubTileX := dimensions >> 4 & 0x0f\n\t\t\t\tsubTileY := dimensions & 0x0f\n\t\t\t\tdimensions, err = ReadUint8(that.buff) // bits 7-4 for x, bits 3-0 for y\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"HexTileEncoding.Read: problem reading dimensions from connection: %v\", err)\n\t\t\t\t}\n\t\t\t\tsubTileWidth := 1 + (dimensions >> 4 & 0x0f)\n\t\t\t\tsubTileHeight := 1 + (dimensions & 0x0f)\n\t\t\t\tsubRectBounds := image.Rectangle{\n\t\t\t\t\tMin: image.Point{X: int(tx) + int(subTileX), Y: int(ty) + int(subTileY)},\n\t\t\t\t\tMax: image.Point{X: int(tx) + int(subTileX) + int(subTileWidth), Y: int(ty) + int(subTileY) + int(subTileHeight)},\n\t\t\t\t}\n\t\t\t\tcv.FillRect(&subRectBounds, fgCol)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/encoding_jpeg.go",
    "content": "package encodings\n"
  },
  {
    "path": "encodings/encoding_jrle.go",
    "content": "package encodings\n"
  },
  {
    "path": "encodings/encoding_raw.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"github.com/vprix/vncproxy/canvas\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// RawEncoding 采用原始地像素数据，而不进行任何的加工处理。\n// 在这种情况下，对于一个宽度乘以高度（即面积）为N的矩形，数据就由N个像素值组成，这些值表示按照扫描线顺序从左到右排列的每个像素。\n// 很明显，这种编码方式是最简单的，也是效率最低的。\n// RFB要求所有的客户都必须能够处理这种原始编码的数据，并且在客户没有特别指定需要某种编码方式的时候，RFB服务器就默认生成原始编码。\ntype RawEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nvar _ rfb.IEncoding = new(RawEncoding)\n\nfunc (that *RawEncoding) Supported(rfb.ISession) bool {\n\treturn true\n}\nfunc (that *RawEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncRaw\n}\n\nfunc (that *RawEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &RawEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\n\nfunc (that *RawEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error {\n\tif sess.Type() == rfb.CanvasSessionType {\n\t\tcv, ok := sess.Conn().(*canvas.VncCanvas)\n\t\tif !ok {\n\t\t\treturn errors.New(\"canvas error\")\n\t\t}\n\t\tpf := sess.Options().PixelFormat\n\t\treturn cv.DecodeRaw(that.buff, &pf, rect)\n\t}\n\tvar err error\n\t_, err = that.buff.WriteTo(sess)\n\tthat.buff.Reset()\n\treturn err\n}\n\n// Read 读取原始色彩表示\nfunc (that *RawEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tpf := session.Options().PixelFormat\n\t// 表示单个像素是使用多少字节表示，分别为 8，16，32，对应的是1，2，4字节\n\t// 知道表示像素的字节长度，则根据宽高就能算出此次传输的总长度\n\tsize := int(rect.Height) * int(rect.Width) * int(pf.BPP/8)\n\n\tb, err := ReadBytes(size, session)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = that.buff.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/encoding_rre.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// RREEncoding RRE表示提升和运行长度，正如它名字暗示的那样，它实质上表示二维向量的运行长度编码。\n// RRE把矩形编码成可以被客户机的图形引擎翻译的格式。RRE不适合复杂的桌面，但在一些情况下比较有用。\n// RRE的思想就是把像素矩形的数据分成一些子区域，和一些压缩原始区域的单元。最近最佳的分区方式一般是比较容易计算的。\n// 编码是由像素值组成的，Vb(基本上是在矩形中最常用的像素值）和一个计数N，紧接着是N的子矩形列表，这些里面由数组组成，(x,y)是对应子矩形的坐标，\n// 表示子矩形上-左的坐标值，(w,h) 则表示子矩形的宽高。客户端可以通过绘制使用背景像素数据值，然后再根据子矩形来绘制原始矩形。\n// 二维行程编码本质上是对行程编码的一个二维模拟，而其压缩度可以保证与行程编码相同甚至更好。\n// 而且更重要的是，采用RRE编码的矩形被传送到客户端以后，可以立即有效地被最简单的图形引擎所还原。\ntype RREEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nfunc (that *RREEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncRRE\n}\nfunc (that *RREEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\nfunc (that *RREEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &RREEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\n\nfunc (that *RREEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tpf := session.Options().PixelFormat\n\t// 子矩形的数量\n\tvar numOfSubRectangles uint32\n\tif err := binary.Read(session, binary.BigEndian, &numOfSubRectangles); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(that.buff, binary.BigEndian, numOfSubRectangles); err != nil {\n\t\treturn err\n\t}\n\n\t// (backgroundColor + (color=BPP + x=16b + y=16b + w=16b + h=16b))\n\tsize := uint32(pf.BPP/8) + (uint32((pf.BPP/8)+8) * numOfSubRectangles)\n\tb, err := ReadBytes(int(size), session)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = that.buff.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\nfunc (that *RREEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\treturn errors.New(\"ByteBuffer is nil\")\n\t}\n\t_, err := that.buff.WriteTo(session)\n\tthat.buff.Reset()\n\treturn err\n}\n"
  },
  {
    "path": "encodings/encoding_tight.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\nvar TightMinToCompress int = 12\n\nconst (\n\ttightCompressionBasic = 0\n\ttightCompressionFill  = 0x08\n\ttightCompressionJPEG  = 0x09\n\ttightCompressionPNG   = 0x0A\n)\n\nconst (\n\tTightFilterCopy     = 0\n\tTightFilterPalette  = 1\n\tTightFilterGradient = 2\n)\n\ntype TightEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nfunc (that *TightEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *TightEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &TightEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\nfunc (that *TightEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncTight\n}\n\nfunc calcTightBytePerPixel(pf *rfb.PixelFormat) int {\n\tbytesPerPixel := int(pf.BPP / 8)\n\n\tvar bytesPerPixelTight int\n\tif 24 == pf.Depth && 32 == pf.BPP {\n\t\tbytesPerPixelTight = 3\n\t} else {\n\t\tbytesPerPixelTight = bytesPerPixel\n\t}\n\treturn bytesPerPixelTight\n}\n\nfunc (that *TightEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\treturn errors.New(\"ByteBuffer is nil\")\n\t}\n\t_, err := that.buff.WriteTo(session)\n\tthat.buff.Reset()\n\treturn err\n}\nfunc (that *TightEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tpf := session.Options().PixelFormat\n\tbytesPixel := calcTightBytePerPixel(&pf)\n\tcompressionControl, err := ReadUint8(session)\n\tif err != nil {\n\t\treturn nil\n\t}\n\t_ = binary.Write(that.buff, binary.BigEndian, compressionControl)\n\n\tcompType := compressionControl >> 4 & 0x0F\n\n\tswitch compType {\n\tcase tightCompressionFill: // 全部为紧凑压缩\n\t\tbt, err := ReadBytes(bytesPixel, session)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, _ = that.buff.Write(bt)\n\n\t\t//jpeg紧凑压缩\n\tcase tightCompressionJPEG:\n\t\tif pf.BPP == 8 {\n\t\t\treturn errors.New(\"Tight encoding: JPEG is not supported in 8 bpp mode. \")\n\t\t}\n\t\t// 获取jpeg流的长度\n\t\tsize, err := that.ReadCompactLen(session)\n\t\t//读取jpeg流\n\t\tjpegBytes, err := ReadBytes(size, session)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, _ = that.buff.Write(jpegBytes)\n\tdefault:\n\t\t// 默认使用基础的压缩方式\n\t\tif compType > tightCompressionJPEG {\n\t\t\treturn errors.New(\"Compression control byte is incorrect! \")\n\t\t}\n\t\terr = that.handleTightFilters(session, rect, &pf, compressionControl)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ReadCompactLen 获取动态长度\nfunc (that *TightEncoding) ReadCompactLen(session rfb.ISession) (int, error) {\n\tvar err error\n\tpart, err := ReadUint8(session)\n\tif err := binary.Write(that.buff, binary.BigEndian, part); err != nil {\n\t\treturn 0, err\n\t}\n\tsize := uint32(part & 0x7F)\n\tif (part & 0x80) == 0 {\n\t\treturn int(size), nil\n\t}\n\tpart, err = ReadUint8(session)\n\tif err := binary.Write(that.buff, binary.BigEndian, part); err != nil {\n\t\treturn 0, err\n\t}\n\tsize |= uint32(int(part)&0x7F) << 7\n\tif (part & 0x80) == 0 {\n\t\treturn int(size), nil\n\t}\n\tpart, err = ReadUint8(session)\n\tif err := binary.Write(that.buff, binary.BigEndian, part); err != nil {\n\t\treturn 0, err\n\t}\n\tsize |= uint32(int(part)&0xFF) << 14\n\n\treturn int(size), err\n}\n\n// 基础压缩格式\nfunc (that *TightEncoding) handleTightFilters(session rfb.ISession, rect *rfb.Rectangle, pf *rfb.PixelFormat, compCtl uint8) error {\n\n\tvar FilterIdMask uint8 = 0x40\n\n\tvar filterId uint8\n\tvar err error\n\n\tif (compCtl & FilterIdMask) > 0 {\n\t\tfilterId, err = ReadUint8(session)\n\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error in handling tight encoding, reading filterid: %s\", err.Error())\n\t\t}\n\t\t_ = binary.Write(that.buff, binary.BigEndian, filterId)\n\t}\n\tbytesPixel := calcTightBytePerPixel(pf)\n\tlengthCurrentBPP := bytesPixel * int(rect.Width) * int(rect.Height)\n\tswitch filterId {\n\tcase TightFilterPalette:\n\t\tpalette, err := that.readTightPalette(session, bytesPixel)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar dataLength int\n\t\tif palette == 2 {\n\t\t\tdataLength = int(rect.Height) * ((int(rect.Width) + 7) / 8)\n\t\t} else {\n\t\t\tdataLength = int(rect.Width) * int(rect.Height)\n\t\t}\n\t\terr = that.ReadTightData(dataLength, session)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tcase TightFilterGradient:\n\t\terr = that.ReadTightData(lengthCurrentBPP, session)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"handleTightFilters: error in handling tight encoding, Reading GRADIENT_FILTER: %v\", err)\n\t\t}\n\n\tcase TightFilterCopy:\n\t\terr = that.ReadTightData(lengthCurrentBPP, session)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"handleTightFilters: error in handling tight encoding, Reading BASIC_FILTER: %v\", err)\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"handleTightFilters: Bad tight filter id: %d\", filterId)\n\t}\n\treturn nil\n}\n\n// 获取调色板数据\nfunc (that *TightEncoding) readTightPalette(session rfb.ISession, bytesPixel int) (int, error) {\n\tcolorCount, err := ReadUint8(session)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"handleTightFilters: error in handling tight encoding, reading TightFilterPalette: %v\", err)\n\t}\n\t_ = binary.Write(that.buff, binary.BigEndian, colorCount)\n\t// 注意这个地方，必须先转换为int类型，不然如果colorCount为255，+1的情况下会溢出，变成0，造成bug\n\tnumColors := int(colorCount) + 1\n\tpaletteSize := numColors * bytesPixel\n\tpaletteColorBytes, err := ReadBytes(paletteSize, session)\n\tif err != nil {\n\t\treturn numColors, err\n\t}\n\t_, _ = that.buff.Write(paletteColorBytes)\n\treturn numColors, nil\n}\n\nfunc (that *TightEncoding) ReadTightData(dataSize int, session rfb.ISession) error {\n\tif dataSize < TightMinToCompress {\n\t\tb, err := ReadBytes(dataSize, session)\n\t\tif err == nil {\n\t\t\t_, _ = that.buff.Write(b)\n\t\t}\n\t\treturn err\n\t}\n\tzlibDataLen, err := that.ReadCompactLen(session)\n\tif err != nil {\n\t\treturn err\n\t}\n\tzippedBytes, err := ReadBytes(zlibDataLen, session)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, _ = that.buff.Write(zippedBytes)\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/encoding_tightpng.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\ntype TightPngEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nfunc (that *TightPngEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *TightPngEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &TightPngEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\n\nfunc (that *TightPngEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncTightPng\n}\n\nfunc (that *TightPngEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\treturn errors.New(\"ByteBuffer is nil\")\n\t}\n\t_, err := that.buff.WriteTo(session)\n\tthat.buff.Reset()\n\treturn err\n}\nfunc (that *TightPngEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tpf := session.Options().PixelFormat\n\tbytesPixel := calcTightBytePerPixel(&pf)\n\tcompressionControl, err := ReadUint8(session)\n\tif err != nil {\n\t\treturn nil\n\t}\n\t_ = binary.Write(that.buff, binary.BigEndian, compressionControl)\n\n\tcompType := compressionControl >> 4 & 0x0F\n\n\tswitch compType {\n\tcase tightCompressionPNG:\n\t\tsize, err := that.ReadCompactLen(session)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbt, err := ReadBytes(size, session)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, _ = that.buff.Write(bt)\n\n\tcase tightCompressionFill:\n\t\tbt, err := ReadBytes(bytesPixel, session)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, _ = that.buff.Write(bt)\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown tight compression %d\", compType)\n\t}\n\treturn nil\n}\n\n// ReadCompactLen 获取动态长度\nfunc (that *TightPngEncoding) ReadCompactLen(session rfb.ISession) (int, error) {\n\tvar err error\n\tpart, err := ReadUint8(session)\n\tif err := binary.Write(that.buff, binary.BigEndian, part); err != nil {\n\t\treturn 0, err\n\t}\n\tsize := uint32(part & 0x7F)\n\tif (part & 0x80) == 0 {\n\t\treturn int(size), nil\n\t}\n\tpart, err = ReadUint8(session)\n\tif err := binary.Write(that.buff, binary.BigEndian, part); err != nil {\n\t\treturn 0, err\n\t}\n\tsize |= uint32(int(part)&0x7F) << 7\n\tif (part & 0x80) == 0 {\n\t\treturn int(size), nil\n\t}\n\tpart, err = ReadUint8(session)\n\tif err := binary.Write(that.buff, binary.BigEndian, part); err != nil {\n\t\treturn 0, err\n\t}\n\tsize |= uint32(int(part)&0xFF) << 14\n\n\treturn int(size), err\n}\n"
  },
  {
    "path": "encodings/encoding_trle.go",
    "content": "package encodings\n"
  },
  {
    "path": "encodings/encoding_zlib.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\ntype ZLibEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nfunc (that *ZLibEncoding) Supported(c rfb.ISession) bool {\n\treturn true\n}\nfunc (that *ZLibEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncZlib\n}\n\nfunc (that *ZLibEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &ZLibEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\n\nfunc (that *ZLibEncoding) Read(sess rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tsize, err := ReadUint32(sess)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = binary.Write(that.buff, binary.BigEndian, size)\n\tif err != nil {\n\t\treturn err\n\t}\n\tb, err := ReadBytes(int(size), sess)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = that.buff.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (that *ZLibEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\treturn errors.New(\"ByteBuffer is nil\")\n\t}\n\t_, err := that.buff.WriteTo(sess)\n\tthat.buff.Reset()\n\treturn err\n}\n"
  },
  {
    "path": "encodings/encoding_zrle.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"compress/zlib\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/canvas\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"image/color\"\n\t\"io\"\n)\n\nconst (\n\tZRLERawPixelData = 0\n\tZRLESingleColour = 1\n)\n\n// ZRLEEncoding ZRLE(Zlib Run - Length Encoding),它结合了zlib 压缩，片技术、调色板和运行长度编码。\n// 在传输中，矩形以4 字节长度区域开始，紧接着是zlib 压缩的数据，一个单一的 zlib“流”对象被用在RFB协议的连接上，\n// 因此ZRLE矩形必须严格的按照顺序进行编码和译码。\ntype ZRLEEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nfunc (that *ZRLEEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncZRLE\n}\n\nfunc (that *ZRLEEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *ZRLEEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &ZRLEEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\n\nfunc (that *ZRLEEncoding) Read(sess rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tsize, err := ReadUint32(sess)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = binary.Write(that.buff, binary.BigEndian, size)\n\tif err != nil {\n\t\treturn err\n\t}\n\tb, err := ReadBytes(int(size), sess)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = that.buff.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\nfunc (that *ZRLEEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\treturn errors.New(\"ByteBuffer is nil\")\n\t}\n\tif sess.Type() == rfb.CanvasSessionType {\n\t\treturn that.draw(sess.Conn().(*canvas.VncCanvas), sess.Options().PixelFormat, rect)\n\t}\n\t_, err := that.buff.WriteTo(sess)\n\tthat.buff.Reset()\n\treturn err\n}\n\n// 绘制画布\nfunc (that *ZRLEEncoding) draw(cv *canvas.VncCanvas, pf rfb.PixelFormat, rect *rfb.Rectangle) error {\n\tvar size uint32\n\terr := binary.Read(that.buff, binary.BigEndian, &size)\n\tif err != nil {\n\t\treturn err\n\t}\n\tb, err := ReadBytes(int(size), that.buff)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbytesBuff := bytes.NewBuffer(b)\n\tunZipper, err := zlib.NewReader(bytesBuff)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor tileOffsetY := 0; tileOffsetY < int(rect.Height); tileOffsetY += 64 {\n\n\t\ttileHeight := min(64, int(rect.Height)-tileOffsetY)\n\n\t\tfor tileOffsetX := 0; tileOffsetX < int(rect.Width); tileOffsetX += 64 {\n\n\t\t\ttileWidth := min(64, int(rect.Width)-tileOffsetX)\n\t\t\t// 获取二级编码格式\n\t\t\tsubEnc, err := ReadUint8(unZipper)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"renderZRLE: error while reading subencoding: %v\", err)\n\t\t\t}\n\n\t\t\tswitch {\n\t\t\tcase subEnc == ZRLERawPixelData: // 原始编码格式\n\t\t\t\terr = that.readZRLERaw(cv, unZipper, &pf, int(rect.X)+tileOffsetX, int(rect.Y)+tileOffsetY, tileWidth, tileHeight)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"renderZRLE: error while reading Raw tile: %v\", err)\n\t\t\t\t}\n\t\t\tcase subEnc == ZRLESingleColour: // 获取一个颜色，填充指定区域\n\t\t\t\tco, err := readCPixel(cv, unZipper, &pf)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"renderZRLE: error while reading CPixel for bgColor tile: %v\", err)\n\t\t\t\t}\n\t\t\t\tmyRect := canvas.MakeRect(int(rect.X)+tileOffsetX, int(rect.Y)+tileOffsetY, tileWidth, tileHeight)\n\t\t\t\tcv.FillRect(&myRect, co)\n\t\t\tcase subEnc >= 2 && subEnc <= 16: // 调色版编码\n\t\t\t\terr = that.handlePaletteTile(cv, unZipper, tileOffsetX, tileOffsetY, tileWidth, tileHeight, subEnc, &pf, rect)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tcase subEnc == 128: //\n\t\t\t\terr = that.handlePlainRLETile(cv, unZipper, tileOffsetX, tileOffsetY, tileWidth, tileHeight, &pf, rect)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tcase subEnc >= 130:\n\t\t\t\terr = that.handlePaletteRLETile(cv, unZipper, tileOffsetX, tileOffsetY, tileWidth, tileHeight, subEnc, &pf, rect)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\"Unknown ZRLE subencoding: %v \", subEnc)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc min(a, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc (that *ZRLEEncoding) readZRLERaw(cv *canvas.VncCanvas, reader io.Reader, pf *rfb.PixelFormat, tx, ty, tw, th int) error {\n\tfor y := 0; y < th; y++ {\n\t\tfor x := 0; x < tw; x++ {\n\t\t\tcol, err := readCPixel(cv, reader, pf)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcv.Set(tx+x, ty+y, col)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// 获取像素格式\nfunc readCPixel(cv *canvas.VncCanvas, c io.Reader, pf *rfb.PixelFormat) (*color.RGBA, error) {\n\tif pf.TrueColor == 0 {\n\t\treturn nil, errors.New(\"support for non true color formats was not implemented\")\n\t}\n\n\tisZRLEFormat := IsCPixelSpecific(pf)\n\tvar col *color.RGBA\n\tif isZRLEFormat {\n\t\ttBytes, err := ReadBytes(3, c)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif pf.BigEndian != 1 {\n\t\t\tcol = &color.RGBA{\n\t\t\t\tB: tBytes[0],\n\t\t\t\tG: tBytes[1],\n\t\t\t\tR: tBytes[2],\n\t\t\t\tA: uint8(1),\n\t\t\t}\n\t\t} else {\n\t\t\tcol = &color.RGBA{\n\t\t\t\tR: tBytes[0],\n\t\t\t\tG: tBytes[1],\n\t\t\t\tB: tBytes[2],\n\t\t\t\tA: uint8(1),\n\t\t\t}\n\t\t}\n\t\treturn col, nil\n\t}\n\n\tcol, err := cv.ReadColor(c, pf)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"readCPixel: Error while reading zrle: %v\", err)\n\t}\n\n\treturn col, nil\n}\n\nfunc IsCPixelSpecific(pf *rfb.PixelFormat) bool {\n\tsignificant := int(pf.RedMax<<pf.RedShift | pf.GreenMax<<pf.GreenShift | pf.BlueMax<<pf.BlueShift)\n\n\tif pf.Depth <= 24 && 32 == pf.BPP && ((significant&0x00ff000000) == 0 || (significant&0x000000ff) == 0) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// 调色板编码\nfunc (that *ZRLEEncoding) handlePaletteTile(cv *canvas.VncCanvas, unZipper io.Reader, tileOffsetX, tileOffsetY, tileWidth, tileHeight int, subEnc uint8, pf *rfb.PixelFormat, rect *rfb.Rectangle) error {\n\tpaletteSize := subEnc\n\tpalette := make([]*color.RGBA, paletteSize)\n\tvar err error\n\t// Read palette\n\tfor j := 0; j < int(paletteSize); j++ {\n\t\tpalette[j], err = readCPixel(cv, unZipper, pf)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"renderZRLE: error while reading CPixel for palette tile: %v\", err)\n\t\t}\n\t}\n\t// Calculate index size\n\tvar indexBits, mask uint32\n\tif paletteSize == 2 {\n\t\tindexBits = 1\n\t\tmask = 0x80\n\t} else if paletteSize <= 4 {\n\t\tindexBits = 2\n\t\tmask = 0xC0\n\t} else {\n\t\tindexBits = 4\n\t\tmask = 0xF0\n\t}\n\tfor y := 0; y < tileHeight; y++ {\n\n\t\t// Packing only occurs per-row\n\t\tbitsAvailable := uint32(0)\n\t\tbuffer := uint32(0)\n\n\t\tfor x := 0; x < tileWidth; x++ {\n\n\t\t\t// Buffer more bits if necessary\n\t\t\tif bitsAvailable == 0 {\n\t\t\t\tbits, err := ReadUint8(unZipper)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"renderZRLE: error while reading first uint8 into buffer: %v\", err)\n\t\t\t\t}\n\t\t\t\tbuffer = uint32(bits)\n\t\t\t\tbitsAvailable = 8\n\t\t\t}\n\n\t\t\t// Read next pixel\n\t\t\tindex := (buffer & mask) >> (8 - indexBits)\n\t\t\tbuffer <<= indexBits\n\t\t\tbitsAvailable -= indexBits\n\n\t\t\t// Write pixel to image\n\t\t\tcv.Set(tileOffsetX+int(rect.X)+x, tileOffsetY+int(rect.Y)+y, palette[index])\n\t\t}\n\t}\n\treturn err\n}\n\n// 普通rle编码\nfunc (that *ZRLEEncoding) handlePlainRLETile(cv *canvas.VncCanvas, unZipper io.Reader, tileOffsetX int, tileOffsetY int, tileWidth int, tileHeight int, pf *rfb.PixelFormat, rect *rfb.Rectangle) error {\n\tvar col *color.RGBA\n\tvar err error\n\trunLen := 0\n\tfor y := 0; y < tileHeight; y++ {\n\t\tfor x := 0; x < tileWidth; x++ {\n\n\t\t\tif runLen == 0 {\n\t\t\t\t// Read length and color\n\t\t\t\tcol, err = readCPixel(cv, unZipper, pf)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"handlePlainRLETile: error while reading CPixel in plain RLE subencoding: %v\", err)\n\t\t\t\t}\n\t\t\t\trunLen, err = readRunLength(unZipper)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"handlePlainRLETile: error while reading runlength in plain RLE subencoding: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Write pixel to image\n\t\t\tcv.Set(tileOffsetX+int(rect.X)+x, tileOffsetY+int(rect.Y)+y, col)\n\t\t\trunLen--\n\t\t}\n\t}\n\treturn err\n}\n\nfunc readRunLength(r io.Reader) (int, error) {\n\trunLen := 1\n\n\taddition, err := ReadUint8(r)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"renderZRLE: error while reading addition to runLen in plain RLE subencoding: %v\", err)\n\t}\n\trunLen += int(addition)\n\n\tfor addition == 255 {\n\t\taddition, err = ReadUint8(r)\n\t\tif err != nil {\n\t\t\treturn 0, fmt.Errorf(\"renderZRLE: error while reading addition to runLen in-loop plain RLE subencoding: %v\", err)\n\t\t}\n\t\trunLen += int(addition)\n\t}\n\treturn runLen, nil\n}\n\n// 调色板rle编码\nfunc (that *ZRLEEncoding) handlePaletteRLETile(cv *canvas.VncCanvas, unZipper io.Reader, tileOffsetX, tileOffsetY, tileWidth, tileHeight int, subEnc uint8, pf *rfb.PixelFormat, rect *rfb.Rectangle) error {\n\t// Palette RLE\n\tpaletteSize := subEnc - 128\n\tpalette := make([]*color.RGBA, paletteSize)\n\tvar err error\n\n\t// Read RLE palette\n\tfor j := 0; j < int(paletteSize); j++ {\n\t\tpalette[j], err = readCPixel(cv, unZipper, pf)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"renderZRLE: error while reading color in palette RLE subencoding: %v\", err)\n\t\t}\n\t}\n\tvar index uint8\n\trunLen := 0\n\tfor y := 0; y < tileHeight; y++ {\n\t\tfor x := 0; x < tileWidth; x++ {\n\t\t\tif runLen == 0 {\n\t\t\t\t// Read length and index\n\t\t\t\tindex, err = ReadUint8(unZipper)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"renderZRLE: error while reading length and index in palette RLE subencoding: %v\", err)\n\t\t\t\t}\n\t\t\t\trunLen = 1\n\t\t\t\t// Run is represented by index | 0x80\n\t\t\t\t// Otherwise, single pixel\n\t\t\t\tif (index & 0x80) != 0 {\n\t\t\t\t\tindex -= 128\n\t\t\t\t\trunLen, err = readRunLength(unZipper)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"handlePlainRLETile: error while reading runlength in plain RLE subencoding: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Write pixel to image\n\t\t\tcv.Set(tileOffsetX+int(rect.X)+x, tileOffsetY+int(rect.Y)+y, palette[index])\n\t\t\trunLen--\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/pseudo_cursor.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"github.com/vprix/vncproxy/canvas\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"image\"\n\t\"image/color\"\n\t\"math\"\n)\n\n// CursorPseudoEncoding 如果客户端请求指针/鼠标伪编码，那么就是说它有能力进行本地绘制鼠标。\n// 这样就可以明显改善传输性能。服务器通过发送带有伪鼠标编码的伪矩形来设置鼠标的形状作为更新的一部分。\n// 伪矩形的x 和y 表示鼠标的热点，宽和高表示用像素来表示鼠标的宽和高。包含宽X高像素值的数据带有位掩码。\n// 位掩码是由从左到右，从上到下的扫描线组成，而每一扫描线被填充为floor((width +7) / 8)。\n// 对应每一字节最重要的位表示最左边像素，对应1 位表示相应指针的像素是正确的。\ntype CursorPseudoEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nfunc (that *CursorPseudoEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\n// Draw 绘制鼠标指针\nfunc (that *CursorPseudoEncoding) draw(cv *canvas.VncCanvas, pf rfb.PixelFormat, rect *rfb.Rectangle) error {\n\tnumColors := int(rect.Height) * int(rect.Width)\n\tcolors := make([]color.Color, numColors)\n\tvar err error\n\tfor i := 0; i < numColors; i++ {\n\t\tcolors[i], err = cv.ReadColor(that.buff, &pf)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t// 获取掩码信息\n\tbitmask := make([]byte, int((rect.Width+7)/8*rect.Height))\n\tif err = binary.Read(that.buff, binary.BigEndian, &bitmask); err != nil {\n\t\treturn err\n\t}\n\tscanLine := (rect.Width + 7) / 8\n\t// 生成鼠标指针的形状\n\tcursorImg := image.NewRGBA(canvas.MakeRect(0, 0, int(rect.Width), int(rect.Height)))\n\tvar cursorMask [][]bool\n\tfor i := 0; i < int(rect.Width); i++ {\n\t\tcursorMask = append(cursorMask, make([]bool, rect.Height))\n\t}\n\t// 填充鼠标指针的颜色\n\tfor y := 0; y < int(rect.Height); y++ {\n\t\tfor x := 0; x < int(rect.Width); x++ {\n\t\t\toffset := y*int(rect.Width) + x\n\t\t\tif bitmask[y*int(scanLine)+x/8]&(1<<uint(7-x%8)) > 0 {\n\t\t\t\tcursorImg.Set(x, y, colors[offset])\n\t\t\t\tcursorMask[x][y] = true\n\t\t\t}\n\t\t}\n\t}\n\t// 设置鼠标指针\n\tcv.CursorOffset = &image.Point{X: int(rect.X), Y: int(rect.Y)}\n\tcv.Cursor = cursorImg\n\tcv.CursorBackup = image.NewRGBA(cursorImg.Bounds())\n\tcv.CursorMask = cursorMask\n\n\treturn nil\n}\n\nfunc (that *CursorPseudoEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &CursorPseudoEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\n\nfunc (that *CursorPseudoEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncCursorPseudo\n}\n\nfunc (that *CursorPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif rect.Width*rect.Height == 0 {\n\t\treturn nil\n\t}\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tvar bt []byte\n\tvar err error\n\n\tbytesPixel := int(session.Options().PixelFormat.BPP / 8) //calcTightBytePerPixel(pf)\n\tbt, err = ReadBytes(int(rect.Width*rect.Height)*bytesPixel, session)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, _ = that.buff.Write(bt)\n\tmask := ((rect.Width + 7) / 8) * rect.Height\n\tbt, err = ReadBytes(int(math.Floor(float64(mask))), session)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, _ = that.buff.Write(bt)\n\treturn nil\n}\n\nfunc (that *CursorPseudoEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\treturn nil\n\t}\n\tif sess.Type() == rfb.CanvasSessionType {\n\t\treturn that.draw(sess.Conn().(*canvas.VncCanvas), sess.Options().PixelFormat, rect)\n\t}\n\tvar err error\n\t_, err = that.buff.WriteTo(sess)\n\tthat.buff.Reset()\n\treturn err\n}\n"
  },
  {
    "path": "encodings/pseudo_cursor_with_alpha.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\ntype CursorWithAlphaPseudoEncoding struct {\n\tbuff *bytes.Buffer\n}\n\nfunc (that *CursorWithAlphaPseudoEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *CursorWithAlphaPseudoEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &CursorWithAlphaPseudoEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tif that.buff != nil {\n\t\t\tobj.buff = &bytes.Buffer{}\n\t\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t\t}\n\t}\n\treturn obj\n}\n\nfunc (that *CursorWithAlphaPseudoEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncCursorWithAlphaPseudo\n}\n\nfunc (that *CursorWithAlphaPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\tvar bt []byte\n\tvar err error\n\tbt, err = ReadBytes(4, session)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, _ = that.buff.Write(bt)\n\n\tif rect.Width*rect.Height > 0 {\n\t\tbt2, err := ReadBytes(int(rect.Width*rect.Height)*4, session)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, _ = that.buff.Write(bt2)\n\t}\n\treturn nil\n}\n\nfunc (that *CursorWithAlphaPseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\treturn nil\n\t}\n\tvar err error\n\t_, err = that.buff.WriteTo(session)\n\tthat.buff.Reset()\n\treturn err\n}\n"
  },
  {
    "path": "encodings/pseudo_desktop_name.go",
    "content": "package encodings\n\nimport (\n\t\"encoding/binary\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// DesktopNamePseudoEncoding 服务端设置桌面名字的消息\ntype DesktopNamePseudoEncoding struct {\n\tName []byte\n}\n\nfunc (that *DesktopNamePseudoEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *DesktopNamePseudoEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &DesktopNamePseudoEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tobj.Name = that.Name\n\t}\n\treturn obj\n}\n\nfunc (that *DesktopNamePseudoEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncDesktopNamePseudo\n}\n\n// Read 实现了编码接口\nfunc (that *DesktopNamePseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tvar length uint32\n\tif err := binary.Read(session, binary.BigEndian, &length); err != nil {\n\t\treturn err\n\t}\n\tname := make([]byte, length)\n\tif err := binary.Read(session, binary.BigEndian, &name); err != nil {\n\t\treturn err\n\t}\n\tthat.Name = name\n\treturn nil\n}\n\nfunc (that *DesktopNamePseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif err := binary.Write(session, binary.BigEndian, uint32(len(that.Name))); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.Name); err != nil {\n\t\treturn err\n\t}\n\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "encodings/pseudo_desktop_size.go",
    "content": "package encodings\n\nimport (\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// DesktopSizePseudoEncoding 如果客户端请求桌面大小伪编码，那么就是说它能处理帧缓存宽/高的改变。\n// 服务器通过发送带有桌面大小伪编码的伪矩形作为上一个矩形来完成一次更新。\n// 伪矩形的x 和y 被忽略，而宽和高表示帧缓存新的宽和高。没有其他的数据与伪矩形有关。\ntype DesktopSizePseudoEncoding struct {\n}\n\nfunc (that *DesktopSizePseudoEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *DesktopSizePseudoEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &DesktopSizePseudoEncoding{}\n\treturn obj\n}\n\nfunc (that *DesktopSizePseudoEncoding) Type() rfb.EncodingType { return rfb.EncDesktopSizePseudo }\n\n// Read implements the Encoding interface.\nfunc (that *DesktopSizePseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\treturn nil\n}\n\nfunc (that *DesktopSizePseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/pseudo_extended_desktop_size.go",
    "content": "package encodings\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ExtendedDesktopSizePseudo 扩展适应客户端桌面分辨率\ntype ExtendedDesktopSizePseudo struct {\n\tbuff *bytes.Buffer\n}\n\nfunc (that *ExtendedDesktopSizePseudo) Supported(rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *ExtendedDesktopSizePseudo) Clone(data ...bool) rfb.IEncoding {\n\tobj := &ExtendedDesktopSizePseudo{}\n\tif len(data) > 0 && data[0] {\n\t\tobj.buff = &bytes.Buffer{}\n\t\t_, _ = obj.buff.Write(that.buff.Bytes())\n\t}\n\treturn obj\n}\n\nfunc (that *ExtendedDesktopSizePseudo) Type() rfb.EncodingType {\n\treturn rfb.EncExtendedDesktopSizePseudo\n}\n\nfunc (that *ExtendedDesktopSizePseudo) Write(session rfb.ISession, rect *rfb.Rectangle) (err error) {\n\t_, err = that.buff.WriteTo(session)\n\tthat.buff.Reset()\n\treturn err\n}\n\nfunc (that *ExtendedDesktopSizePseudo) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif that.buff == nil {\n\t\tthat.buff = &bytes.Buffer{}\n\t}\n\t//读取屏幕数量\n\tscreensNumber, err := ReadUint8(session)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = binary.Write(that.buff, binary.BigEndian, screensNumber)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// 填充\n\tpad, err := ReadBytes(3, session)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = binary.Write(that.buff, binary.BigEndian, pad)\n\n\tb2, err := ReadBytes(int(screensNumber)*16, session)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, _ = that.buff.Write(b2)\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/pseudo_fence.go",
    "content": "package encodings\n\nimport (\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\ntype FencePseudo struct {\n}\n\nfunc (that *FencePseudo) Supported(_ rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *FencePseudo) Clone(_ ...bool) rfb.IEncoding {\n\tobj := &FencePseudo{}\n\treturn obj\n}\n\nfunc (that *FencePseudo) Type() rfb.EncodingType {\n\treturn rfb.EncFencePseudo\n}\n\nfunc (that *FencePseudo) Read(_ rfb.ISession, _ *rfb.Rectangle) error {\n\treturn nil\n}\n\nfunc (that *FencePseudo) Write(_ rfb.ISession, _ *rfb.Rectangle) error {\n\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/pseudo_last_rect.go",
    "content": "package encodings\n\nimport (\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\ntype LastRectPseudo struct {\n}\n\nfunc (that *LastRectPseudo) Supported(_ rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *LastRectPseudo) Clone(_ ...bool) rfb.IEncoding {\n\tobj := &LastRectPseudo{}\n\treturn obj\n}\n\nfunc (that *LastRectPseudo) Type() rfb.EncodingType {\n\treturn rfb.EncLastRectPseudo\n}\n\nfunc (that *LastRectPseudo) Read(_ rfb.ISession, _ *rfb.Rectangle) error {\n\treturn nil\n}\n\nfunc (that *LastRectPseudo) Write(_ rfb.ISession, _ *rfb.Rectangle) error {\n\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/pseudo_led_state.go",
    "content": "package encodings\n\nimport (\n\t\"encoding/binary\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// LedStatePseudo 切换客户端本地小键盘锁定的led灯\n// 0 滚动锁\n// 1 数字锁定\n// 2 大写锁定\ntype LedStatePseudo struct {\n\tLedState uint8\n}\n\nfunc (that *LedStatePseudo) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *LedStatePseudo) Clone(data ...bool) rfb.IEncoding {\n\tobj := &LedStatePseudo{}\n\treturn obj\n}\n\nfunc (that *LedStatePseudo) Type() rfb.EncodingType {\n\treturn rfb.EncLedStatePseudo\n}\n\nfunc (that *LedStatePseudo) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tu8, err := ReadUint8(session)\n\tif err != nil {\n\t\treturn err\n\t}\n\tthat.LedState = u8\n\treturn nil\n}\n\nfunc (that *LedStatePseudo) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif err := binary.Write(session, binary.BigEndian, that.LedState); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/pseudo_pointer_pos.go",
    "content": "package encodings\n\nimport (\n\t\"errors\"\n\t\"github.com/vprix/vncproxy/canvas\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"image\"\n\t\"image/draw\"\n)\n\ntype CursorPosPseudoEncoding struct {\n}\n\nfunc (that *CursorPosPseudoEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *CursorPosPseudoEncoding) Draw(img draw.Image, rect *rfb.Rectangle) error {\n\tcv, ok := img.(*canvas.VncCanvas)\n\tif !ok {\n\t\treturn errors.New(\"canvas error\")\n\t}\n\t// 本地鼠标指针的位置\n\tcv.CursorLocation = &image.Point{X: int(rect.X), Y: int(rect.Y)}\n\treturn nil\n}\n\nfunc (that *CursorPosPseudoEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &CursorPosPseudoEncoding{}\n\treturn obj\n}\n\nfunc (that *CursorPosPseudoEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncPointerPosPseudo\n}\n\nfunc (that *CursorPosPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\treturn nil\n}\n\nfunc (that *CursorPosPseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\treturn nil\n}\n"
  },
  {
    "path": "encodings/pseudo_x_cursor.go",
    "content": "package encodings\n\nimport (\n\t\"encoding/binary\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"math\"\n)\n\ntype XCursorPseudoEncoding struct {\n\tPrimaryR, PrimaryG, PrimaryB       uint8  // 主颜色\n\tSecondaryR, SecondaryG, SecondaryB uint8  // 次颜色\n\tBitmap                             []byte //颜色位图\n\tBitmask                            []byte //透明度位掩码\n}\n\nfunc (that *XCursorPseudoEncoding) Supported(session rfb.ISession) bool {\n\treturn true\n}\nfunc (that *XCursorPseudoEncoding) Clone(data ...bool) rfb.IEncoding {\n\tobj := &XCursorPseudoEncoding{}\n\tif len(data) > 0 && data[0] {\n\t\tobj.PrimaryR = that.PrimaryR\n\t\tobj.PrimaryG = that.PrimaryG\n\t\tobj.PrimaryB = that.PrimaryB\n\t\tobj.SecondaryR = that.SecondaryR\n\t\tobj.SecondaryG = that.SecondaryG\n\t\tobj.SecondaryB = that.SecondaryB\n\t\tBitmap := make([]byte, len(that.Bitmap))\n\t\tBitmask := make([]byte, len(that.Bitmask))\n\t\tcopy(Bitmap, that.Bitmap)\n\t\tcopy(Bitmask, that.Bitmask)\n\t\tobj.Bitmap = Bitmap\n\t\tobj.Bitmask = Bitmask\n\t}\n\treturn obj\n}\nfunc (that *XCursorPseudoEncoding) Type() rfb.EncodingType {\n\treturn rfb.EncXCursorPseudo\n}\n\nfunc (that *XCursorPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif err := binary.Read(session, binary.BigEndian, &that.PrimaryR); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &that.PrimaryG); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &that.PrimaryB); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &that.SecondaryR); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &that.SecondaryG); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &that.SecondaryB); err != nil {\n\t\treturn err\n\t}\n\n\tbitMapSize := int(math.Floor((float64(rect.Width)+7)/8) * float64(rect.Height))\n\tbitMaskSize := int(math.Floor((float64(rect.Width)+7)/8) * float64(rect.Height))\n\n\tthat.Bitmap = make([]byte, bitMapSize)\n\tthat.Bitmask = make([]byte, bitMaskSize)\n\tif err := binary.Read(session, binary.BigEndian, &that.Bitmap); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &that.Bitmask); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (that *XCursorPseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {\n\tif err := binary.Write(session, binary.BigEndian, that.PrimaryR); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.PrimaryG); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.PrimaryB); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.SecondaryR); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.SecondaryG); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.SecondaryB); err != nil {\n\t\treturn err\n\t}\n\n\tif err := binary.Write(session, binary.BigEndian, that.Bitmap); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.Bitmask); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/vprix/vncproxy\n\nrequire (\n\tgithub.com/gogf/gf/v2 v2.9.5\n\tgithub.com/osgochina/dmicro v1.3.1\n\tgolang.org/x/net v0.47.0\n)\n\nrequire (\n\tgithub.com/BurntSushi/toml v1.5.0 // indirect\n\tgithub.com/clbanning/mxj/v2 v2.7.0 // indirect\n\tgithub.com/emirpasic/gods v1.18.1 // indirect\n\tgithub.com/fatih/color v1.18.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/gorilla/websocket v1.5.3 // indirect\n\tgithub.com/grokify/html-strip-tags-go v0.1.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.6 // indirect\n\tgithub.com/klauspost/reedsolomon v1.12.0 // indirect\n\tgithub.com/magiconair/properties v1.8.10 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgithub.com/olekukonko/errors v1.1.0 // indirect\n\tgithub.com/olekukonko/ll v0.0.9 // indirect\n\tgithub.com/olekukonko/tablewriter v1.1.0 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/quic-go/quic-go v0.56.0 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/rogpeppe/go-internal v1.14.1 // indirect\n\tgithub.com/tjfoc/gmsm v1.4.1 // indirect\n\tgithub.com/xtaci/kcp-go/v5 v5.6.37 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/otel v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.38.0 // indirect\n\tgolang.org/x/crypto v0.44.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.10 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\ngo 1.24.7\n\n"
  },
  {
    "path": "handler/ClientClientInitHandler.go",
    "content": "package handler\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ClientClientInitHandler vnc握手步骤第三步\n// 1. 根据配置信息判断该vnc会话是否独占，\n// 2. 发送是否独占标识给vnc服务端\ntype ClientClientInitHandler struct{}\n\nfunc (that *ClientClientInitHandler) Handle(session rfb.ISession) error {\n\tif logger.IsDebug() {\n\t\tlogger.Debug(context.TODO(), \"[Proxy客户端->VNC服务端]: 执行vnc握手步骤第三步[ClientInit]\")\n\t}\n\tcfg := session.Options()\n\tvar shared uint8\n\tif cfg.Exclusive {\n\t\tshared = 0\n\t} else {\n\t\tshared = 1\n\t}\n\tif err := binary.Write(session, binary.BigEndian, shared); err != nil {\n\t\treturn err\n\t}\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[Proxy客户端->VNC服务端]: 执行ClientInit步骤，发送shared=%d\", shared)\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "handler/ClientMessageHandler.go",
    "content": "package handler\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"golang.org/x/net/context\"\n)\n\n// ClientMessageHandler vnc握手已结束，进入消息交互阶段\n// 启动两个协程处理后续消息逻辑\n// 1. 协程1：通过ClientMessageCh通道获取消息，并把该消息写入到vnc服务端会话中。\n// 2. 协程2：从vnc服务端会话中读取消息类型及消息内容，组装该消息，发消息发送到ServerMessageCh通道中，供其他功能消费\n// 3. 发送编码格式消息SetEncodings到vnc服务端\n// 4. 发送帧数据请求消息FramebufferUpdateRequest到vnc服务端\ntype ClientMessageHandler struct{}\n\nfunc (*ClientMessageHandler) Handle(session rfb.ISession) error {\n\tif logger.IsDebug() {\n\t\tlogger.Debug(context.TODO(), \"[Proxy客户端->VNC服务端]: vnc握手已结束，进入消息交互阶段[ClientMessageHandler]\")\n\t}\n\tcfg := session.Options()\n\tvar err error\n\n\t// proxy客户端支持的消息类型\n\tserverMessages := make(map[rfb.MessageType]rfb.Message)\n\tfor _, m := range cfg.Messages {\n\t\tserverMessages[m.Type()] = m\n\t}\n\n\t// 通过ClientMessageCh通道获取消息，并把该消息写入到vnc服务端会话中。\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-session.Wait():\n\t\t\t\treturn\n\t\t\tcase msg := <-cfg.Input:\n\t\t\t\tif logger.IsDebug() {\n\t\t\t\t\tlogger.Debugf(context.TODO(), \"[Proxy客户端->VNC服务端] 消息类型:%s,消息内容:%s\", rfb.ClientMessageType(msg.Type()), msg.String())\n\t\t\t\t}\n\t\t\t\tif err = msg.Write(session); err != nil {\n\t\t\t\t\tcfg.ErrorCh <- err\n\t\t\t\t\t_ = session.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\t// 从vnc服务端会话中读取消息类型及消息内容，组装该消息，发消息发送到ServerMessageCh通道中，供其他功能消费\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-session.Wait():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\t// 从会话中读取消息类型\n\t\t\t\tvar messageType rfb.MessageType\n\t\t\t\tif err = binary.Read(session, binary.BigEndian, &messageType); err != nil {\n\t\t\t\t\tcfg.ErrorCh <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif logger.IsDebug() {\n\t\t\t\t\tlogger.Debugf(context.TODO(), \"[VNC服务端->Proxy客户端] 消息类型:%s\", rfb.ServerMessageType(messageType))\n\t\t\t\t}\n\t\t\t\t// 判断proxy客户端是否支持该消息\n\t\t\t\tmsg, ok := serverMessages[messageType]\n\t\t\t\tif !ok {\n\t\t\t\t\terr = fmt.Errorf(\"未知的消息类型: %v\", messageType)\n\t\t\t\t\tcfg.ErrorCh <- err\n\t\t\t\t\t_ = session.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// 读取消息内容\n\t\t\t\tparsedMsg, err := msg.Read(session)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcfg.ErrorCh <- err\n\t\t\t\t\t_ = session.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif logger.IsDebug() {\n\t\t\t\t\tlogger.Debugf(context.TODO(), \"[VNC服务端->Proxy客户端] 消息类型:%s,消息内容:%s\", rfb.ServerMessageType(parsedMsg.Type()), parsedMsg)\n\t\t\t\t}\n\t\t\t\tcfg.Output <- parsedMsg\n\t\t\t}\n\t\t}\n\t}()\n\treturn nil\n}\n"
  },
  {
    "path": "handler/ClientSecurityHandler.go",
    "content": "package handler\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ClientSecurityHandler vnc握手步骤第二步\n// 1. 读取vnc服务端支持的安全认证套件数量及类型\n// 2. 匹配vnc服务端与proxy客户端的安全认证套件\n// 3. 进入安全认证套件认证流程\n// 4. 获取认证结果，如果认证失败，获取失败的原因。\ntype ClientSecurityHandler struct{}\n\nfunc (*ClientSecurityHandler) Handle(session rfb.ISession) error {\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[Proxy客户端->VNC服务端]: 执行vnc握手第二步:[Security]\")\n\t}\n\tcfg := session.Options()\n\t// 读取vnc服务端支持的安全认证套件数量\n\tvar numSecurityTypes uint8\n\tif err := binary.Read(session, binary.BigEndian, &numSecurityTypes); err != nil {\n\t\treturn err\n\t}\n\t// 读取vnc服务端支持的安全认证套件类型\n\tsecTypes := make([]rfb.SecurityType, numSecurityTypes)\n\tif err := binary.Read(session, binary.BigEndian, &secTypes); err != nil {\n\t\treturn err\n\t}\n\n\t// 匹配vnc服务端与proxy客户端的安全认证套件\n\tvar secType rfb.ISecurityHandler\n\tfor _, st := range cfg.SecurityHandlers {\n\t\tfor _, sc := range secTypes {\n\t\t\tif st.Type() == sc {\n\t\t\t\tsecType = st\n\t\t\t}\n\t\t}\n\t}\n\n\t// 发送proxy客户端选中的安全认证套件\n\tif err := binary.Write(session, binary.BigEndian, cfg.SecurityHandlers[0].Type()); err != nil {\n\t\treturn err\n\t}\n\n\tif err := session.Flush(); err != nil {\n\t\treturn err\n\t}\n\n\t// 进入安全认证套件认证流程\n\terr := secType.Auth(session)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"安全认证失败, error:%v\", err)\n\t}\n\n\t// 读取安全认证结果\n\tvar authCode uint32\n\tif err := binary.Read(session, binary.BigEndian, &authCode); err != nil {\n\t\treturn err\n\t}\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"安全认证中, 安全认证套件类型: %d,认证结果(0为成功): %d\", rfb.ClientMessageType(secType.Type()), authCode)\n\t}\n\t//如果认证失败，则读取失败原因\n\tif authCode == 1 {\n\t\tvar reasonLength uint32\n\t\tif err = binary.Read(session, binary.BigEndian, &reasonLength); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treasonText := make([]byte, reasonLength)\n\t\tif err = binary.Read(session, binary.BigEndian, &reasonText); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn fmt.Errorf(\"%s\", reasonText)\n\t}\n\tsession.SetSecurityHandler(secType)\n\treturn nil\n}\n"
  },
  {
    "path": "handler/ClientServerInitHandler.go",
    "content": "package handler\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ClientServerInitHandler vnc握手第四步\n// 1. 读取vnc服务端发送的屏幕宽高，像素格式，桌面名称\ntype ClientServerInitHandler struct{}\n\nfunc (*ClientServerInitHandler) Handle(session rfb.ISession) error {\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[Proxy客户端->VNC服务端]: 执行vnc握手第四步:[ServerInit]\")\n\t}\n\tvar err error\n\tsrvInit := messages.ServerInit{}\n\n\tif err = binary.Read(session, binary.BigEndian, &srvInit.FBWidth); err != nil {\n\t\treturn err\n\t}\n\tif err = binary.Read(session, binary.BigEndian, &srvInit.FBHeight); err != nil {\n\t\treturn err\n\t}\n\tif err = binary.Read(session, binary.BigEndian, &srvInit.PixelFormat); err != nil {\n\t\treturn err\n\t}\n\tif err = binary.Read(session, binary.BigEndian, &srvInit.NameLength); err != nil {\n\t\treturn err\n\t}\n\n\tsrvInit.NameText = make([]byte, srvInit.NameLength)\n\tif err = binary.Read(session, binary.BigEndian, &srvInit.NameText); err != nil {\n\t\treturn err\n\t}\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[Proxy客户端->VNC服务端]:  serverInit: %s\", srvInit)\n\t}\n\tsession.SetDesktopName(srvInit.NameText)\n\t// 如果协议是aten1，则执行特殊的逻辑\n\tif session.ProtocolVersion() == \"aten1\" {\n\t\tsession.SetWidth(800)\n\t\tsession.SetHeight(600)\n\t\t// 发送像素格式消息\n\t\tsession.SetPixelFormat(rfb.NewPixelFormatAten())\n\t} else {\n\t\tsession.SetWidth(srvInit.FBWidth)\n\t\tsession.SetHeight(srvInit.FBHeight)\n\n\t\t//告诉vnc服务端，proxy客户端支持的像素格式，发送`SetPixelFormat`消息\n\t\tpixelMsg := messages.SetPixelFormat{PF: rfb.PixelFormat32bit}\n\t\terr = pixelMsg.Write(session)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsession.SetPixelFormat(rfb.PixelFormat32bit)\n\t}\n\t// aten1协议需要再次读取扩展信息\n\tif session.ProtocolVersion() == \"aten1\" {\n\t\tikvm := struct {\n\t\t\t_               [8]byte\n\t\t\tIKVMVideoEnable uint8\n\t\t\tIKVMKMEnable    uint8\n\t\t\tIKVMKickEnable  uint8\n\t\t\tVUSBEnable      uint8\n\t\t}{}\n\t\tif err = binary.Read(session, binary.BigEndian, &ikvm); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "handler/ClientVersionHandler.go",
    "content": "package handler\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ClientVersionHandler vnc握手第一步\n// 1. 连接到vnc服务端后,读取其支持的rfb协议版本。\n// 2. 解析版本，判断该版本proxy客户端是否支持。\n// 3. 如果支持该版本，则发送支持的版本给vnc服务端\ntype ClientVersionHandler struct{}\n\nfunc (*ClientVersionHandler) Handle(session rfb.ISession) error {\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[Proxy客户端->VNC服务端]: 执行vnc握手第一步:[Version]\")\n\t}\n\tvar version [rfb.ProtoVersionLength]byte\n\n\tif err := binary.Read(session, binary.BigEndian, &version); err != nil {\n\t\treturn err\n\t}\n\n\tmajor, minor, err := ParseProtoVersion(version[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpv := rfb.ProtoVersionUnknown\n\tif major == 3 {\n\t\tif minor >= 8 {\n\t\t\tpv = rfb.ProtoVersion38\n\t\t} else if minor >= 3 {\n\t\t\tpv = rfb.ProtoVersion38\n\t\t}\n\t}\n\tif pv == rfb.ProtoVersionUnknown {\n\t\treturn fmt.Errorf(\"rfb协议握手失败; 不支持的版本 '%v'\", string(version[:]))\n\t}\n\tsession.SetProtocolVersion(string(version[:]))\n\n\tif err = binary.Write(session, binary.BigEndian, []byte(pv)); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n\nfunc ParseProtoVersion(pv []byte) (uint, uint, error) {\n\tvar major, minor uint\n\n\tif len(pv) < rfb.ProtoVersionLength {\n\t\treturn 0, 0, fmt.Errorf(\"协议版本的长度太短 (%v < %v)\", len(pv), rfb.ProtoVersionLength)\n\t}\n\n\tl, err := fmt.Sscanf(string(pv), \"RFB %d.%d\\n\", &major, &minor)\n\tif l != 2 {\n\t\treturn 0, 0, fmt.Errorf(\"解析rfb协议失败\")\n\t}\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\treturn major, minor, nil\n}\n"
  },
  {
    "path": "handler/ServerClientInitHandler.go",
    "content": "package handler\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ServerClientInitHandler vnc握手步骤第三步\n// 读取vnc客户端发送的是否支持共享屏幕标识\ntype ServerClientInitHandler struct{}\n\nfunc (*ServerClientInitHandler) Handle(session rfb.ISession) error {\n\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[VNC客户端->Proxy服务端]: 执行vnc握手第三步:[ClientInit]\")\n\t}\n\t// 读取分享屏幕标识符，proxy会无视该标识，因为通过proxy链接的vnc服务端都是默认支持分享的。\n\tvar shared uint8\n\tif err := binary.Read(session, binary.BigEndian, &shared); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "handler/ServerMessageHandler.go",
    "content": "package handler\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ServerMessageHandler vnc握手已结束，进入消息交互阶段\n// 启动两个协程，\n// 1. 处理proxy服务端的ServerMessage,在ServerMessageCh通道的消息都转发写入到该会话中.\n// 2. 从会话中读取clientMessages，并判断是否支持该消息，如果支持则转发到ClientMessageCh通道中。如果不支持则关闭该会话并报错。\ntype ServerMessageHandler struct{}\n\nfunc (*ServerMessageHandler) Handle(session rfb.ISession) error {\n\tif logger.IsDebug() {\n\t\tlogger.Debug(context.TODO(), \"[VNC客户端->Proxy服务端]: vnc握手已结束，进入消息交互阶段[ServerMessageHandler]\")\n\t}\n\n\tcfg := session.Options()\n\tvar err error\n\tclientMessages := make(map[rfb.ClientMessageType]rfb.Message)\n\tfor _, m := range cfg.Messages {\n\t\tclientMessages[rfb.ClientMessageType(m.Type())] = m\n\t}\n\n\t// 处理proxy服务端发送给vnc客户端的消息\n\tgo func() {\n\t\t//defer wg.Done()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-session.Wait(): // 如果收到退出信号，则退出协程\n\t\t\t\treturn\n\t\t\tcase msg := <-cfg.Input:\n\t\t\t\t// 收到proxy服务端消息，则转发写入到vnc客户端会话中。\n\t\t\t\tif logger.IsDebug() {\n\t\t\t\t\tlogger.Debugf(context.TODO(), \"[Proxy服务端->VNC客户端] 消息类型:%s,消息内容:%s\", rfb.ServerMessageType(msg.Type()), msg.String())\n\t\t\t\t}\n\t\t\t\tif err = msg.Write(session); err != nil {\n\t\t\t\t\tcfg.ErrorCh <- err\n\t\t\t\t\t_ = session.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\t// 处理vnc客户端发送给proxy服务端的消息\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-session.Wait():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\t// 从vnc客户端的会话中读取消息类型\n\t\t\t\tvar messageType rfb.ClientMessageType\n\t\t\t\tif err = binary.Read(session, binary.BigEndian, &messageType); err != nil {\n\t\t\t\t\tcfg.ErrorCh <- fmt.Errorf(\"读取vnc客户端数据失败，err:%v\", err)\n\t\t\t\t\t_ = session.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// 判断vnc客户端发送的消息类型proxy服务端是否支持。\n\t\t\t\tmsg, ok := clientMessages[messageType]\n\t\t\t\tif !ok {\n\t\t\t\t\tcfg.ErrorCh <- fmt.Errorf(\"不支持的消息类型: %v\", messageType)\n\t\t\t\t\t_ = session.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// 从会话中读取消息内容\n\t\t\t\tparsedMsg, e := msg.Read(session)\n\t\t\t\tif e != nil {\n\t\t\t\t\tcfg.ErrorCh <- fmt.Errorf(\"解析消息失败，err:%v\", e)\n\t\t\t\t\t_ = session.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif logger.IsDebug() {\n\t\t\t\t\tlogger.Debugf(context.TODO(), \"[VNC客户端->Proxy服务端] 消息类型:%s,消息内容:%s\", rfb.ClientMessageType(parsedMsg.Type()), parsedMsg.String())\n\t\t\t\t}\n\n\t\t\t\tcfg.Output <- parsedMsg\n\t\t\t}\n\t\t}\n\t}()\n\treturn nil\n}\n"
  },
  {
    "path": "handler/ServerSecurityHandler.go",
    "content": "package handler\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ServerSecurityHandler vnc握手步骤第二步\n// 1.发送proxy服务端支持的安全认证套件数量及类型。\n// 2.读取vnc客户端支持的安全认证套件类型，判断是否支持，\n// 3.选择互相支持的安全认证套件进行认证，进入认证逻辑，如果认证成功则进入下一步，认证失败则报错。\ntype ServerSecurityHandler struct{}\n\nfunc (*ServerSecurityHandler) Handle(session rfb.ISession) error {\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[VNC客户端->Proxy服务端]: 执行vnc握手第二步:[Security]\")\n\t}\n\tcfg := session.Options()\n\tvar secType rfb.SecurityType\n\tif session.ProtocolVersion() == rfb.ProtoVersion37 || session.ProtocolVersion() == rfb.ProtoVersion38 {\n\t\tif err := binary.Write(session, binary.BigEndian, uint8(len(cfg.SecurityHandlers))); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, sType := range cfg.SecurityHandlers {\n\t\t\tif err := binary.Write(session, binary.BigEndian, sType.Type()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t} else {\n\t\tst := uint32(0)\n\t\tfor _, sType := range cfg.SecurityHandlers {\n\t\t\tif uint32(sType.Type()) > st {\n\t\t\t\tst = uint32(sType.Type())\n\t\t\t\tsecType = sType.Type()\n\t\t\t}\n\t\t}\n\t\tif err := binary.Write(session, binary.BigEndian, st); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err := session.Flush(); err != nil {\n\t\treturn err\n\t}\n\n\tif session.ProtocolVersion() == rfb.ProtoVersion38 {\n\t\tif err := binary.Read(session, binary.BigEndian, &secType); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tsecTypes := make(map[rfb.SecurityType]rfb.ISecurityHandler)\n\tfor _, sType := range cfg.SecurityHandlers {\n\t\tsecTypes[sType.Type()] = sType\n\t}\n\n\tsType, ok := secTypes[secType]\n\tif !ok {\n\t\treturn fmt.Errorf(\"security type %d not implemented\", secType)\n\t}\n\n\tvar authCode uint32\n\tauthErr := sType.Auth(session)\n\tif authErr != nil {\n\t\tauthCode = uint32(1)\n\t}\n\n\tif err := binary.Write(session, binary.BigEndian, authCode); err != nil {\n\t\treturn err\n\t}\n\n\tif authErr == nil {\n\t\tif err := session.Flush(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsession.SetSecurityHandler(sType)\n\t\treturn nil\n\t}\n\n\tif session.ProtocolVersion() == rfb.ProtoVersion38 {\n\t\tif err := binary.Write(session, binary.BigEndian, uint32(len(authErr.Error()))); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := binary.Write(session, binary.BigEndian, []byte(authErr.Error())); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := session.Flush(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn authErr\n}\n"
  },
  {
    "path": "handler/ServerServerInitHandler.go",
    "content": "package handler\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ServerServerInitHandler vnc握手步骤第四步\n// 1. 发送proxy服务端的参数信息，屏幕宽高，像素格式，桌面名称\ntype ServerServerInitHandler struct{}\n\nfunc (*ServerServerInitHandler) Handle(session rfb.ISession) error {\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[Proxy服务端->VNC客户端]: 执行vnc握手第四步:[ServerInit]\")\n\t}\n\tif err := binary.Write(session, binary.BigEndian, session.Options().Width); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, session.Options().Height); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, session.Options().PixelFormat); err != nil {\n\t\treturn err\n\t}\n\tdesktopName := session.Options().DesktopName\n\tsize := uint32(len(session.Options().DesktopName))\n\tif size == 0 {\n\t\tdesktopName = []byte(\"vprix\")\n\t\tsize = uint32(len(desktopName))\n\t}\n\tif err := binary.Write(session, binary.BigEndian, size); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, desktopName); err != nil {\n\t\treturn err\n\t}\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[Proxy服务端->VNC客户端]: ServerInit[Width:%d,Height:%d,PixelFormat:%s,DesktopName:%s]\",\n\t\t\tsession.Options().Width, session.Options().Height, session.Options().PixelFormat, desktopName)\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "handler/ServerVersionHandler.go",
    "content": "package handler\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ServerVersionHandler vnc握手步骤第一步。\n// 1. vnc客户端链接到proxy服务端后，proxy服务端发送rfb版本信息。\n// 2. 发送版本信息后，接受vnc客户端返回的版本信息，进行版本匹配。\n// 3. 确定版本信息是相互支持的，如果不支持，则返回错误信息，如果支持则进行下一步。\ntype ServerVersionHandler struct{}\n\nfunc (*ServerVersionHandler) Handle(session rfb.ISession) error {\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[VNC客户端->Proxy服务端]: 执行vnc握手第一步:[Version]\")\n\t}\n\tvar version [rfb.ProtoVersionLength]byte\n\tif err := binary.Write(session, binary.BigEndian, []byte(rfb.ProtoVersion38)); err != nil {\n\t\treturn err\n\t}\n\tif err := session.Flush(); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &version); err != nil {\n\t\treturn err\n\t}\n\tmajor, minor, err := ParseProtoVersion(version[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpv := rfb.ProtoVersionUnknown\n\tif major == 3 {\n\t\tif minor >= 8 {\n\t\t\tpv = rfb.ProtoVersion38\n\t\t} else if minor >= 3 {\n\t\t\tpv = rfb.ProtoVersion33\n\t\t}\n\t}\n\tif pv == rfb.ProtoVersionUnknown {\n\t\treturn fmt.Errorf(\"rfb协议握手; 不支持的协议版本 '%v'\", string(version[:]))\n\t}\n\n\tsession.SetProtocolVersion(pv)\n\treturn nil\n}\n"
  },
  {
    "path": "internal/dbuffer/buffer.go",
    "content": "package dbuffer\n\nimport (\n\t\"io\"\n)\n\n// ByteBuffer provides byte buffer, which can be used for minimizing\n// memory allocations.\n//\n// ByteBuffer may be used with functions appending data to the given []byte\n// slice. See example code for details.\n//\n// Use Get for obtaining an empty byte buffer.\ntype ByteBuffer struct {\n\n\t// B is a byte buffer to use in append-like workloads.\n\t// See example code for details.\n\tB []byte\n}\n\n// Len returns the size of the byte buffer.\nfunc (b *ByteBuffer) Len() int {\n\treturn len(b.B)\n}\n\n// ReadFrom implements io.ReaderFrom.\n//\n// The function appends all the data read from r to b.\nfunc (b *ByteBuffer) ReadFrom(r io.Reader) (int64, error) {\n\tp := b.B\n\tnStart := int64(len(p))\n\tnMax := int64(cap(p))\n\tn := nStart\n\tif nMax == 0 {\n\t\tnMax = 64\n\t\tp = make([]byte, nMax)\n\t} else {\n\t\tp = p[:nMax]\n\t}\n\tfor {\n\t\tif n == nMax {\n\t\t\tnMax *= 2\n\t\t\tbNew := make([]byte, nMax)\n\t\t\tcopy(bNew, p)\n\t\t\tp = bNew\n\t\t}\n\t\tnn, err := r.Read(p[n:])\n\t\tn += int64(nn)\n\t\tif err != nil {\n\t\t\tb.B = p[:n]\n\t\t\tn -= nStart\n\t\t\tif err == io.EOF {\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t\treturn n, err\n\t\t}\n\t}\n}\n\n// WriteTo implements io.WriterTo.\nfunc (b *ByteBuffer) WriteTo(w io.Writer) (int64, error) {\n\tn, err := w.Write(b.B)\n\treturn int64(n), err\n}\n\n// Bytes returns b.B, i.e. all the bytes accumulated in the buffer.\n//\n// The purpose of this function is bytes.Buffer compatibility.\nfunc (b *ByteBuffer) Bytes() []byte {\n\treturn b.B\n}\n\n// Write implements io.Writer - it appends p to ByteBuffer.B\nfunc (b *ByteBuffer) Write(p []byte) (int, error) {\n\tb.B = append(b.B, p...)\n\treturn len(p), nil\n}\n\n// WriteByte appends the byte c to the buffer.\n//\n// The purpose of this function is bytes.Buffer compatibility.\n//\n// The function always returns nil.\nfunc (b *ByteBuffer) WriteByte(c byte) error {\n\tb.B = append(b.B, c)\n\treturn nil\n}\n\n// WriteString appends s to ByteBuffer.B.\nfunc (b *ByteBuffer) WriteString(s string) (int, error) {\n\tb.B = append(b.B, s...)\n\treturn len(s), nil\n}\n\n// Set sets ByteBuffer.B to p.\nfunc (b *ByteBuffer) Set(p []byte) {\n\tb.B = append(b.B[:0], p...)\n}\n\n// SetString sets ByteBuffer.B to s.\nfunc (b *ByteBuffer) SetString(s string) {\n\tb.B = append(b.B[:0], s...)\n}\n\n// String returns string representation of ByteBuffer.B.\nfunc (b *ByteBuffer) String() string {\n\treturn string(b.B)\n}\n\n// Reset makes ByteBuffer.B empty.\nfunc (b *ByteBuffer) Reset() {\n\tb.B = b.B[:0]\n}\n\n// ChangeLen changes the buffer length.\nfunc (b *ByteBuffer) ChangeLen(newLen int) {\n\tif cap(b.B) < newLen {\n\t\tb.B = make([]byte, newLen)\n\t} else {\n\t\tb.B = b.B[:newLen]\n\t}\n}\n"
  },
  {
    "path": "internal/dbuffer/pool.go",
    "content": "package dbuffer\n\nimport (\n\t\"sort\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\nconst (\n\tminBitSize = 6 // 2**6=64 is a CPU cache line size\n\tsteps      = 20\n\n\tminSize = 1 << minBitSize\n\tmaxSize = 1 << (minBitSize + steps - 1)\n\n\tcalibrateCallsThreshold = 42000\n\tmaxPercentile           = 0.95\n)\n\n// BufferPool represents byte buffer pool.\n//\n// Distinct pools may be used for distinct types of byte buffers.\n// Properly determined byte buffer types with their own pools may help reducing\n// memory waste.\ntype BufferPool struct {\n\tcalls       [steps]uint64\n\tcalibrating uint64\n\n\tdefaultSize uint64\n\tmaxSize     uint64\n\n\tpool sync.Pool\n}\n\nvar defaultBufferPool BufferPool\n\n// GetByteBuffer returns an empty byte buffer from the pool.\n//\n// Got byte buffer may be returned to the pool via Put call.\n// This reduces the number of memory allocations required for byte buffer\n// management.\nfunc GetByteBuffer() *ByteBuffer { return defaultBufferPool.Get() }\n\n// Get returns new byte buffer with zero length.\n//\n// The byte buffer may be returned to the pool via Put after the use\n// in order to minimize GC overhead.\nfunc (p *BufferPool) Get() *ByteBuffer {\n\tv := p.pool.Get()\n\tif v != nil {\n\t\treturn v.(*ByteBuffer)\n\t}\n\treturn &ByteBuffer{\n\t\tB: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)),\n\t}\n}\n\n// ReleaseByteBuffer returns byte buffer to the pool.\n//\n// ByteBuffer.B mustn't be touched after returning it to the pool.\n// Otherwise data races will occur.\nfunc ReleaseByteBuffer(b *ByteBuffer) { defaultBufferPool.Put(b) }\n\n// Put releases byte buffer obtained via Get to the pool.\n//\n// The buffer mustn't be accessed after returning to the pool.\nfunc (p *BufferPool) Put(b *ByteBuffer) {\n\tidx := index(len(b.B))\n\n\tif atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {\n\t\tp.calibrate()\n\t}\n\n\tmaxSize := int(atomic.LoadUint64(&p.maxSize))\n\tif maxSize == 0 || cap(b.B) <= maxSize {\n\t\tb.Reset()\n\t\tp.pool.Put(b)\n\t}\n}\n\nfunc (p *BufferPool) calibrate() {\n\tif !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {\n\t\treturn\n\t}\n\n\ta := make(callSizes, 0, steps)\n\tvar callsSum uint64\n\tfor i := uint64(0); i < steps; i++ {\n\t\tcalls := atomic.SwapUint64(&p.calls[i], 0)\n\t\tcallsSum += calls\n\t\ta = append(a, callSize{\n\t\t\tcalls: calls,\n\t\t\tsize:  minSize << i,\n\t\t})\n\t}\n\tsort.Sort(a)\n\n\tdefaultSize := a[0].size\n\tmaxSize := defaultSize\n\n\tmaxSum := uint64(float64(callsSum) * maxPercentile)\n\tcallsSum = 0\n\tfor i := 0; i < steps; i++ {\n\t\tif callsSum > maxSum {\n\t\t\tbreak\n\t\t}\n\t\tcallsSum += a[i].calls\n\t\tsize := a[i].size\n\t\tif size > maxSize {\n\t\t\tmaxSize = size\n\t\t}\n\t}\n\n\tatomic.StoreUint64(&p.defaultSize, defaultSize)\n\tatomic.StoreUint64(&p.maxSize, maxSize)\n\n\tatomic.StoreUint64(&p.calibrating, 0)\n}\n\ntype callSize struct {\n\tcalls uint64\n\tsize  uint64\n}\n\ntype callSizes []callSize\n\nfunc (ci callSizes) Len() int {\n\treturn len(ci)\n}\n\nfunc (ci callSizes) Less(i, j int) bool {\n\treturn ci[i].calls > ci[j].calls\n}\n\nfunc (ci callSizes) Swap(i, j int) {\n\tci[i], ci[j] = ci[j], ci[i]\n}\n\nfunc index(n int) int {\n\tn--\n\tn >>= minBitSize\n\tidx := 0\n\tfor n > 0 {\n\t\tn >>= 1\n\t\tidx++\n\t}\n\tif idx >= steps {\n\t\tidx = steps - 1\n\t}\n\treturn idx\n}\n"
  },
  {
    "path": "internal/syncPool/sync_pool.go",
    "content": "package syncPool\n\nimport \"sync\"\n\ntype SyncPool struct {\n\tpool     sync.Pool\n\tnewFunc  func() interface{}\n\tinitFunc func(interface{})\n}\n\n// NewSyncPool 创建对象池\n// newFunc：创建对象的方法\n// init： 对象被创建后，返回之前，调用该方法初始化对象\nfunc NewSyncPool(newFunc func() interface{}, init func(interface{})) *SyncPool {\n\treturn &SyncPool{\n\t\tnewFunc:  newFunc,\n\t\tinitFunc: init,\n\t\tpool: sync.Pool{\n\t\t\tNew: newFunc,\n\t\t},\n\t}\n}\n\n// Get 获取对象\nfunc (that *SyncPool) Get() interface{} {\n\tvar object = that.pool.Get()\n\tif that.initFunc != nil {\n\t\tthat.initFunc(object)\n\t}\n\treturn object\n}\n\n// Put 把对象放回对象池\nfunc (that *SyncPool) Put(value interface{}) {\n\tthat.pool.Put(value)\n}\n"
  },
  {
    "path": "messages/clientClientCutText.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ClientCutText 客户端发送剪切板内容到服务端\ntype ClientCutText struct {\n\t_      [3]byte // 填充\n\tLength uint32  // 剪切板内容长度\n\tText   []byte  // 剪切板\n}\n\nfunc (that *ClientCutText) Clone() rfb.Message {\n\tc := &ClientCutText{\n\t\tLength: that.Length,\n\t\tText:   that.Text,\n\t}\n\treturn c\n}\nfunc (that *ClientCutText) Supported(rfb.ISession) bool {\n\treturn true\n}\n\n// String\nfunc (that *ClientCutText) String() string {\n\treturn fmt.Sprintf(\"length: %d\", that.Length)\n}\n\n// Type returns MessageType\nfunc (that *ClientCutText) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.ClientCutText)\n}\n\n// Read 从会话中解析消息内容\nfunc (that *ClientCutText) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &ClientCutText{}\n\t// 读取填充字节\n\tvar pad [3]byte\n\tif err := binary.Read(session, binary.BigEndian, &pad); err != nil {\n\t\treturn nil, err\n\t}\n\t// 读取消息长度\n\tif err := binary.Read(session, binary.BigEndian, &msg.Length); err != nil {\n\t\treturn nil, err\n\t}\n\t// 读取指定长度的消息内容\n\tmsg.Text = make([]byte, msg.Length)\n\tif err := binary.Read(session, binary.BigEndian, &msg.Text); err != nil {\n\t\treturn nil, err\n\t}\n\treturn msg, nil\n}\n\n// Write 把消息按协议格式写入会话\nfunc (that *ClientCutText) Write(session rfb.ISession) error {\n\t// 写入消息类型\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\n\t// 写入3给字节的填充\n\tvar pad [3]byte\n\tif err := binary.Write(session, binary.BigEndian, &pad); err != nil {\n\t\treturn err\n\t}\n\n\tif uint32(len(that.Text)) > that.Length {\n\t\tthat.Length = uint32(len(that.Text))\n\t}\n\n\t// 写入剪切板内容长度\n\tif err := binary.Write(session, binary.BigEndian, that.Length); err != nil {\n\t\treturn err\n\t}\n\n\t// 写入消息内容\n\tif err := binary.Write(session, binary.BigEndian, that.Text); err != nil {\n\t\treturn err\n\t}\n\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/clientClientFence.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ClientFence 支持 Fence扩展的客户端发送此扩展以请求数据流的同步。\ntype ClientFence struct {\n\tflags   uint32\n\tlength  uint8\n\tpayload []byte\n}\n\nfunc (that *ClientFence) Clone() rfb.Message {\n\n\tc := &ClientFence{\n\t\tflags:   that.flags,\n\t\tlength:  that.length,\n\t\tpayload: that.payload,\n\t}\n\treturn c\n}\nfunc (that *ClientFence) Supported(session rfb.ISession) bool {\n\treturn true\n}\nfunc (that *ClientFence) String() string {\n\treturn fmt.Sprintf(\"(type=%d)\", that.Type())\n}\n\nfunc (that *ClientFence) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.ClientFence)\n}\n\n// 读取数据\nfunc (that *ClientFence) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &ClientFence{}\n\tbytes := make([]byte, 3)\n\t//c.Read(bytes)\n\tif _, err := session.Read(bytes); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &msg.flags); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &msg.length); err != nil {\n\t\treturn nil, err\n\t}\n\tbytes = make([]byte, msg.length)\n\tif _, err := session.Read(bytes); err != nil {\n\t\treturn nil, err\n\t}\n\tmsg.payload = bytes\n\treturn msg, nil\n}\n\nfunc (that *ClientFence) Write(session rfb.ISession) error {\n\t// 写入消息类型\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\t//写入填充\n\tvar pad [3]byte\n\tif err := binary.Write(session, binary.BigEndian, pad); err != nil {\n\t\treturn err\n\t}\n\n\tif err := binary.Write(session, binary.BigEndian, that.flags); err != nil {\n\t\treturn err\n\t}\n\n\tif err := binary.Write(session, binary.BigEndian, that.length); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.payload); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/clientEnableContinuousUpdates.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// EnableContinuousUpdates 客户端发送连续更新消息\ntype EnableContinuousUpdates struct {\n\tflag   uint8\n\tx      uint16\n\ty      uint16\n\twidth  uint16\n\theight uint16\n}\n\nfunc (that *EnableContinuousUpdates) Clone() rfb.Message {\n\n\tc := &EnableContinuousUpdates{\n\t\tflag:   that.flag,\n\t\tx:      that.x,\n\t\ty:      that.y,\n\t\twidth:  that.width,\n\t\theight: that.height,\n\t}\n\treturn c\n}\nfunc (that *EnableContinuousUpdates) Supported(rfb.ISession) bool {\n\treturn true\n}\nfunc (that *EnableContinuousUpdates) String() string {\n\treturn 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)\n}\n\nfunc (that *EnableContinuousUpdates) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.EnableContinuousUpdates)\n}\n\n// 读取数据\nfunc (that *EnableContinuousUpdates) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &EnableContinuousUpdates{}\n\tif err := binary.Read(session, binary.BigEndian, &msg.flag); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &msg.x); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &msg.y); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &msg.width); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &msg.height); err != nil {\n\t\treturn nil, err\n\t}\n\treturn msg, nil\n}\n\nfunc (that *EnableContinuousUpdates) Write(session rfb.ISession) error {\n\t// 写入消息类型\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.flag); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.x); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.y); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.width); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.height); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/clientFramebufferUpdateRequest.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// FramebufferUpdateRequest 请求帧缓存更新消息\n// incremental 通常为非 0 值，服务器只需要发有变化的图像信息。\n// 当客户端丢失了缓存的帧缓冲信息，或者刚建立连接，需要完整的图像信息时，\n// 将 incremental 置为 0，获取全量信息。\ntype FramebufferUpdateRequest struct {\n\tInc           uint8  // 是否是增量请求\n\tX, Y          uint16 // 区域的起始坐标\n\tWidth, Height uint16 // 区域的宽度和高度\n}\n\nfunc (that *FramebufferUpdateRequest) Clone() rfb.Message {\n\n\tc := &FramebufferUpdateRequest{\n\t\tInc:    that.Inc,\n\t\tX:      that.X,\n\t\tY:      that.Y,\n\t\tWidth:  that.Width,\n\t\tHeight: that.Height,\n\t}\n\treturn c\n}\nfunc (that *FramebufferUpdateRequest) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\n// String returns string\nfunc (that *FramebufferUpdateRequest) String() string {\n\treturn fmt.Sprintf(\"incremental: %d, x: %d, y: %d, width: %d, height: %d\", that.Inc, that.X, that.Y, that.Width, that.Height)\n}\n\n// Type returns MessageType\nfunc (that *FramebufferUpdateRequest) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.FramebufferUpdateRequest)\n}\n\n// Read 从会话中解析消息内容\nfunc (that *FramebufferUpdateRequest) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &FramebufferUpdateRequest{}\n\tif err := binary.Read(session, binary.BigEndian, msg); err != nil {\n\t\treturn nil, err\n\t}\n\treturn msg, nil\n}\n\n// Write 把消息按协议格式写入会话\nfunc (that *FramebufferUpdateRequest) Write(session rfb.ISession) error {\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/clientKeyEvent.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// KeyEvent 键盘按键事件\ntype KeyEvent struct {\n\tDown uint8   // 1 表示键位按下，0 表示弹起\n\t_    [2]byte // 对齐字节，方便解析\n\tKey  rfb.Key // 表示具体的键位，https://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.html#keysym_encoding\n}\n\nfunc (that *KeyEvent) Clone() rfb.Message {\n\n\tc := &KeyEvent{\n\t\tDown: that.Down,\n\t\tKey:  that.Key,\n\t}\n\treturn c\n}\nfunc (that *KeyEvent) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\n// String returns string\nfunc (that *KeyEvent) String() string {\n\treturn fmt.Sprintf(\"down: %d, key: %v\", that.Down, that.Key)\n}\n\n// Type returns MessageType\nfunc (that *KeyEvent) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.KeyEvent)\n}\n\n// Read 从会话中解析消息内容\nfunc (that *KeyEvent) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &KeyEvent{}\n\tif err := binary.Read(session, binary.BigEndian, msg); err != nil {\n\t\treturn nil, err\n\t}\n\treturn msg, nil\n}\n\n// Write 把消息按协议格式写入会话\nfunc (that *KeyEvent) Write(session rfb.ISession) error {\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/clientPointerEvent.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// PointerEvent 鼠标事件\ntype PointerEvent struct {\n\tMask uint8  //8 位掩码，表示键位状态，1为按下，0为弹起\n\tX, Y uint16 // 当前 X,Y 坐标\n}\n\nfunc (that *PointerEvent) Clone() rfb.Message {\n\n\tc := &PointerEvent{\n\t\tMask: that.Mask,\n\t\tX:    that.X,\n\t\tY:    that.Y,\n\t}\n\treturn c\n}\nfunc (that *PointerEvent) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\n// String returns string\nfunc (that *PointerEvent) String() string {\n\treturn fmt.Sprintf(\"mask %d, x: %d, y: %d\", that.Mask, that.X, that.Y)\n}\n\n// Type returns MessageType\nfunc (that *PointerEvent) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.PointerEvent)\n}\n\n// Read 从会话中解析消息内容\nfunc (that *PointerEvent) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &PointerEvent{}\n\tif err := binary.Read(session, binary.BigEndian, msg); err != nil {\n\t\treturn nil, err\n\t}\n\treturn msg, nil\n}\n\n// Write 把消息按协议格式写入会话\nfunc (that *PointerEvent) Write(session rfb.ISession) error {\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/clientQEMUExtKeyEvent.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\ntype QEMUExtKeyEvent struct {\n\tSubMessageType uint8   // submessage type\n\tDownFlag       uint16  // down-flag\n\tKeySym         rfb.Key // key symbol\n\tKeyCode        uint32  // scan code\n}\n\nfunc (that *QEMUExtKeyEvent) Clone() rfb.Message {\n\n\tc := &QEMUExtKeyEvent{\n\t\tSubMessageType: that.SubMessageType,\n\t\tDownFlag:       that.DownFlag,\n\t\tKeySym:         that.KeySym,\n\t\tKeyCode:        that.KeyCode,\n\t}\n\treturn c\n}\nfunc (that *QEMUExtKeyEvent) Supported(session rfb.ISession) bool {\n\treturn true\n}\nfunc (that *QEMUExtKeyEvent) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.QEMUExtendedKeyEvent)\n}\n\nfunc (that *QEMUExtKeyEvent) String() string {\n\treturn fmt.Sprintf(\"SubMessageType=%d,DownFlag=%d,KeySym=%d,KeyCode=%d\", that.SubMessageType, that.DownFlag, that.KeySym, that.KeyCode)\n}\n\nfunc (that *QEMUExtKeyEvent) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &QEMUExtKeyEvent{}\n\tif err := binary.Read(session, binary.BigEndian, msg); err != nil {\n\t\treturn nil, err\n\t}\n\treturn msg, nil\n}\n\nfunc (that *QEMUExtKeyEvent) Write(session rfb.ISession) error {\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "messages/clientSetDesktopSize.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/internal/dbuffer\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// SetDesktopSize 客户端发起设置桌面大小\ntype SetDesktopSize struct {\n\tbuff *dbuffer.ByteBuffer\n}\n\nfunc (that *SetDesktopSize) Clone() rfb.Message {\n\n\tc := &SetDesktopSize{\n\t\tbuff: dbuffer.GetByteBuffer(),\n\t}\n\t_, _ = c.buff.Write(that.buff.Bytes())\n\treturn c\n}\nfunc (that *SetDesktopSize) Supported(rfb.ISession) bool {\n\treturn true\n}\nfunc (that *SetDesktopSize) String() string {\n\treturn fmt.Sprintf(\"(type=%d)\", that.Type())\n}\n\nfunc (that *SetDesktopSize) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.SetDesktopSize)\n}\n\n// 读取数据\nfunc (that *SetDesktopSize) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &SetDesktopSize{buff: dbuffer.GetByteBuffer()}\n\tpad := make([]byte, 1)\n\tif _, err := session.Read(pad); err != nil {\n\t\treturn nil, err\n\t}\n\tvar width uint16\n\t_, _ = msg.buff.Write(pad)\n\tif err := binary.Read(session, binary.BigEndian, &width); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Write(msg.buff, binary.BigEndian, width); err != nil {\n\t\treturn nil, err\n\t}\n\tvar height uint16\n\tif err := binary.Read(session, binary.BigEndian, &height); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Write(msg.buff, binary.BigEndian, height); err != nil {\n\t\treturn nil, err\n\t}\n\tvar numberOfScreens uint8\n\tif err := binary.Read(session, binary.BigEndian, &numberOfScreens); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Write(msg.buff, binary.BigEndian, numberOfScreens); err != nil {\n\t\treturn nil, err\n\t}\n\tpad = make([]byte, 1)\n\tif err := binary.Read(session, binary.BigEndian, &pad); err != nil {\n\t\treturn nil, err\n\t}\n\t_, _ = msg.buff.Write(pad)\n\tfor i := 0; i < int(numberOfScreens); i++ {\n\t\tb, err := that.readExtendedDesktopSize(session)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t_, _ = msg.buff.Write(b)\n\t}\n\treturn msg, nil\n}\n\nfunc (that *SetDesktopSize) Write(session rfb.ISession) error {\n\t// 写入消息类型\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\t_, err := session.Write(that.buff.Bytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\tdbuffer.ReleaseByteBuffer(that.buff)\n\tthat.buff = nil\n\treturn session.Flush()\n}\n\n// No. of bytes\t\tType\tDescription\n//\t4\t\t\t\tU32\t\t\tid\n//\t2\t\t\t\tU16\t\t\tx-position\n//\t2\t\t\t\tU16\t\t\ty-position\n//\t2\t\t\t\tU16\t\t\twidth\n//\t2\t\t\t\tU16\t\t\theight\n//\t4\t\t\t\tU32\t\t\tflags\nfunc (that *SetDesktopSize) readExtendedDesktopSize(session rfb.ISession) ([]byte, error) {\n\tdesktopSizeBuf := make([]byte, 16)\n\tif err := binary.Read(session, binary.BigEndian, &desktopSizeBuf); err != nil {\n\t\treturn nil, err\n\t}\n\treturn desktopSizeBuf, nil\n}\n"
  },
  {
    "path": "messages/clientSetEncodings.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/text/gstr\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// SetEncodings 设置编码类型消息\ntype SetEncodings struct {\n\t_         [1]byte // padding\n\tEncNum    uint16  // number-of-encodings\n\tEncodings []rfb.EncodingType\n}\n\nfunc (that *SetEncodings) Clone() rfb.Message {\n\tc := &SetEncodings{\n\t\tEncNum:    that.EncNum,\n\t\tEncodings: that.Encodings,\n\t}\n\treturn c\n}\nfunc (that *SetEncodings) Supported(_ rfb.ISession) bool {\n\treturn true\n}\n\n// String return string\nfunc (that *SetEncodings) String() string {\n\ts := fmt.Sprintf(\"encNum: %d, encodings[]: \", that.EncNum)\n\tvar s1 []string\n\tfor _, e := range that.Encodings {\n\t\ts1 = append(s1, fmt.Sprintf(\"%s\", e))\n\t}\n\treturn s + gstr.Implode(\",\", s1)\n}\n\n// Type returns MessageType\nfunc (that *SetEncodings) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.SetEncodings)\n}\n\n// Read 从会话中解析消息内容\nfunc (that *SetEncodings) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &SetEncodings{}\n\t//读取一个字节的填充数据\n\tvar pad [1]byte\n\tif err := binary.Read(session, binary.BigEndian, &pad); err != nil {\n\t\treturn nil, err\n\t}\n\t//读取编码格式数量\n\tif err := binary.Read(session, binary.BigEndian, &msg.EncNum); err != nil {\n\t\treturn nil, err\n\t}\n\tvar enc rfb.EncodingType\n\t//读取指定数据量的编码信息\n\tfor i := uint16(0); i < msg.EncNum; i++ {\n\t\tif err := binary.Read(session, binary.BigEndian, &enc); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmsg.Encodings = append(msg.Encodings, enc)\n\t}\n\tif err := session.SetEncodings(msg.Encodings); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn msg, nil\n}\n\n// Write 把消息按协议格式写入会话\nfunc (that *SetEncodings) Write(session rfb.ISession) error {\n\t// 写入消息类型\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\t// 写入一个字节的填充数据\n\tvar pad [1]byte\n\tif err := binary.Write(session, binary.BigEndian, pad); err != nil {\n\t\treturn err\n\t}\n\t// 写入当前支持的编码类型的数量\n\tif uint16(len(that.Encodings)) > that.EncNum {\n\t\tthat.EncNum = uint16(len(that.Encodings))\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.EncNum); err != nil {\n\t\treturn err\n\t}\n\t// 写入当前支持的编码类型的列表\n\tfor _, enc := range that.Encodings {\n\t\tif err := binary.Write(session, binary.BigEndian, enc); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/clientSetPixelFormat.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// SetPixelFormat 设置像素格式\ntype SetPixelFormat struct {\n\t_  [3]byte         // 填充\n\tPF rfb.PixelFormat // 像素格式\n}\n\nfunc (that *SetPixelFormat) Clone() rfb.Message {\n\tc := &SetPixelFormat{\n\t\tPF: that.PF,\n\t}\n\treturn c\n}\n\nfunc (that *SetPixelFormat) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\n// String returns string\nfunc (that *SetPixelFormat) String() string {\n\treturn fmt.Sprintf(\"%s\", that.PF)\n}\n\n// Type returns MessageType\nfunc (that *SetPixelFormat) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.SetPixelFormat)\n}\n\n// Write 写入像素格式\nfunc (that *SetPixelFormat) Write(session rfb.ISession) error {\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\n\tif err := binary.Write(session, binary.BigEndian, that); err != nil {\n\t\treturn err\n\t}\n\n\tpf := session.Options().PixelFormat\n\t// Invalidate the color map.\n\tif pf.TrueColor != 0 {\n\t\tsession.SetColorMap(rfb.ColorMap{})\n\t}\n\n\treturn session.Flush()\n}\n\n// Read 从链接中读取像素格式到当前对象\nfunc (that *SetPixelFormat) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &SetPixelFormat{}\n\tif err := binary.Read(session, binary.BigEndian, msg); err != nil {\n\t\treturn nil, err\n\t}\n\treturn msg, nil\n}\n"
  },
  {
    "path": "messages/default_message.go",
    "content": "package messages\n\nimport \"github.com/vprix/vncproxy/rfb\"\n\nvar (\n\t// DefaultClientMessage 默认client支持的消息\n\tDefaultClientMessage = []rfb.Message{\n\t\t&SetPixelFormat{},\n\t\t&SetEncodings{},\n\t\t&FramebufferUpdateRequest{},\n\t\t&KeyEvent{},\n\t\t&PointerEvent{},\n\t\t&ClientCutText{},\n\t\t&ClientFence{},\n\t\t&SetDesktopSize{},\n\t\t&EnableContinuousUpdates{},\n\t}\n\t// DefaultServerMessages 默认server支持的消息\n\tDefaultServerMessages = []rfb.Message{\n\t\t&FramebufferUpdate{},\n\t\t&SetColorMapEntries{},\n\t\t&Bell{},\n\t\t&ServerCutText{},\n\t\t&EndOfContinuousUpdates{},\n\t\t&ServerFence{},\n\t}\n)\n"
  },
  {
    "path": "messages/serverBell.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// Bell 响铃\ntype Bell struct{}\n\nfunc (that *Bell) Clone() rfb.Message {\n\treturn &Bell{}\n}\nfunc (that *Bell) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\n// String return string\nfunc (that *Bell) String() string {\n\treturn fmt.Sprintf(\"bell\")\n}\n\n// Type 消息类型\nfunc (that *Bell) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.Bell)\n}\n\n// Read 响铃消息只有消息类型，没有数据\nfunc (that *Bell) Read(session rfb.ISession) (rfb.Message, error) {\n\treturn &Bell{}, nil\n}\n\n// Write 写入响应消息类型\nfunc (that *Bell) Write(session rfb.ISession) error {\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/serverEndOfContinuousUpdates.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// EndOfContinuousUpdates Bell 结束连续更新\ntype EndOfContinuousUpdates struct{}\n\nfunc (that *EndOfContinuousUpdates) Clone() rfb.Message {\n\treturn &EndOfContinuousUpdates{}\n}\nfunc (that *EndOfContinuousUpdates) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\n// String return string\nfunc (that *EndOfContinuousUpdates) String() string {\n\treturn fmt.Sprintf(\"EndOfContinuousUpdates\")\n}\n\n// Type 消息类型\nfunc (that *EndOfContinuousUpdates) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.EndOfContinuousUpdates)\n}\n\n// Read 响铃消息只有消息类型，没有数据\nfunc (that *EndOfContinuousUpdates) Read(session rfb.ISession) (rfb.Message, error) {\n\treturn &EndOfContinuousUpdates{}, nil\n}\n\n// Write 写入响应消息类型\nfunc (that *EndOfContinuousUpdates) Write(session rfb.ISession) error {\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/serverFramebufferUpdate.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"golang.org/x/net/context\"\n)\n\n// FramebufferUpdate 帧缓冲更新\ntype FramebufferUpdate struct {\n\t_       [1]byte          // 填充\n\tNumRect uint16           // 多少个像素数据的矩形\n\tRects   []*rfb.Rectangle // 像素数据的矩形列表\n}\n\nfunc (that *FramebufferUpdate) String() string {\n\treturn fmt.Sprintf(\"rects %d rectangle[]: { %v }\", that.NumRect, that.Rects)\n}\nfunc (that *FramebufferUpdate) Supported(rfb.ISession) bool {\n\treturn true\n}\n\nfunc (that *FramebufferUpdate) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.FramebufferUpdate)\n}\n\n// 读取帧数据\nfunc (that *FramebufferUpdate) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &FramebufferUpdate{}\n\tvar pad [1]byte\n\tif err := binary.Read(session, binary.BigEndian, &pad); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := binary.Read(session, binary.BigEndian, &msg.NumRect); err != nil {\n\t\treturn nil, err\n\t}\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"FramebufferUpdate->读取帧数据有 %d 个矩形-------\", msg.NumRect)\n\t}\n\n\tfor i := uint16(0); i < msg.NumRect; i++ {\n\t\trect := rfb.NewRectangle()\n\t\tif logger.IsDebug() {\n\t\t\tlogger.Debugf(context.TODO(), \"开始读取第 %d 个矩形\", i)\n\t\t}\n\n\t\tif err := rect.Read(session); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// 如果服务器告诉客户端这是最后一个rect，则停止解析\n\t\tif rect.EncType == rfb.EncLastRectPseudo {\n\t\t\tif logger.IsDebug() {\n\t\t\t\tlogger.Debugf(context.TODO(), \"读取第 %d 个矩形成功，但是是最后一帧:EncLastRectPseudo\", i)\n\t\t\t}\n\t\t\tmsg.Rects = append(msg.Rects, rect)\n\t\t\tbreak\n\t\t}\n\t\t//if rect.EncType == rfb.EncDesktopSizePseudo {\n\t\t//\tsession.ResetAllEncodings()\n\t\t//}\n\t\tif logger.IsDebug() {\n\t\t\tlogger.Debugf(context.TODO(), \"结束读取第 %d 个矩形,宽高:(%dx%d) 编码格式:%s\", i, rect.Width, rect.Height, rect.EncType)\n\t\t}\n\t\tmsg.Rects = append(msg.Rects, rect)\n\t}\n\treturn msg, nil\n}\n\n// 写入帧数据\nfunc (that *FramebufferUpdate) Write(session rfb.ISession) error {\n\t// 写入消息类型\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\t// 填充字节\n\tvar pad [1]byte\n\tif err := binary.Write(session, binary.BigEndian, pad); err != nil {\n\t\treturn err\n\t}\n\t// 写入矩形数量\n\tif err := binary.Write(session, binary.BigEndian, that.NumRect); err != nil {\n\t\treturn err\n\t}\n\t// 编码后写入\n\tfor _, rect := range that.Rects {\n\t\tif err := rect.Write(session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn session.Flush()\n}\n\nfunc (that *FramebufferUpdate) Clone() rfb.Message {\n\n\tc := &FramebufferUpdate{\n\t\tNumRect: that.NumRect,\n\t}\n\tfor _, rect := range that.Rects {\n\t\tc.Rects = append(c.Rects, rect.Clone())\n\t}\n\treturn c\n}\n"
  },
  {
    "path": "messages/serverInit.go",
    "content": "package messages\n\nimport (\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ServerInit  握手的时候服务端初始化消息\ntype ServerInit struct {\n\tFBWidth     uint16\n\tFBHeight    uint16\n\tPixelFormat rfb.PixelFormat\n\tNameLength  uint32\n\tNameText    []byte\n}\n\nfunc (srvInit ServerInit) String() string {\n\treturn fmt.Sprintf(\"ServerInit->Width: %d, Height: %d, PixelFormat: %s, NameLength: %d, MameText: %s\", srvInit.FBWidth, srvInit.FBHeight, srvInit.PixelFormat, srvInit.NameLength, srvInit.NameText)\n}\n"
  },
  {
    "path": "messages/serverServerCutText.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ServerCutText 服务端剪切板发送到客户端\ntype ServerCutText struct {\n\t_      [3]byte // 填充\n\tLength uint32  // 剪切板内容长度\n\tText   []byte  // 剪切板内容\n}\n\nfunc (that *ServerCutText) Clone() rfb.Message {\n\treturn &ServerCutText{\n\t\tLength: that.Length,\n\t\tText:   that.Text,\n\t}\n}\nfunc (that *ServerCutText) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\n// String returns string\nfunc (that *ServerCutText) String() string {\n\treturn fmt.Sprintf(\"lenght: %d\", that.Length)\n}\n\nfunc (that *ServerCutText) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.ServerCutText)\n}\n\n// 读取消息数据\nfunc (that *ServerCutText) Read(session rfb.ISession) (rfb.Message, error) {\n\t// 每次读取以后生成的都是一个新的对象\n\tmsg := &ServerCutText{}\n\tvar pad [3]byte\n\tif err := binary.Read(session, binary.BigEndian, &pad); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := binary.Read(session, binary.BigEndian, &msg.Length); err != nil {\n\t\treturn nil, err\n\t}\n\n\tmsg.Text = make([]byte, msg.Length)\n\tif err := binary.Read(session, binary.BigEndian, &msg.Text); err != nil {\n\t\treturn nil, err\n\t}\n\treturn msg, nil\n}\n\nfunc (that *ServerCutText) Write(session rfb.ISession) error {\n\t// 写入消息类型\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\t//写入填充\n\tvar pad [3]byte\n\tif err := binary.Write(session, binary.BigEndian, pad); err != nil {\n\t\treturn err\n\t}\n\n\tif that.Length < uint32(len(that.Text)) {\n\t\tthat.Length = uint32(len(that.Text))\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.Length); err != nil {\n\t\treturn err\n\t}\n\n\tif err := binary.Write(session, binary.BigEndian, that.Text); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/serverServerFence.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ServerFence 支持 Fence扩展的服务器发送此扩展以请求数据流的同步。\ntype ServerFence struct {\n\tflags   uint32\n\tlength  uint8\n\tpayload []byte\n}\n\nfunc (that *ServerFence) Clone() rfb.Message {\n\n\tc := &ServerFence{\n\t\tflags:   that.flags,\n\t\tlength:  that.length,\n\t\tpayload: that.payload,\n\t}\n\treturn c\n}\nfunc (that *ServerFence) Supported(session rfb.ISession) bool {\n\treturn true\n}\nfunc (that *ServerFence) String() string {\n\treturn fmt.Sprintf(\"type=%d\", that.Type())\n}\n\nfunc (that *ServerFence) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.ServerFence)\n}\n\n// 读取数据\nfunc (that *ServerFence) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &ServerFence{}\n\tbytes := make([]byte, 3)\n\t//c.Read(bytes)\n\tif _, err := session.Read(bytes); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &msg.flags); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &msg.length); err != nil {\n\t\treturn nil, err\n\t}\n\tbytes = make([]byte, msg.length)\n\tif _, err := session.Read(bytes); err != nil {\n\t\treturn nil, err\n\t}\n\tmsg.payload = bytes\n\treturn msg, nil\n}\n\nfunc (that *ServerFence) Write(session rfb.ISession) error {\n\t// 写入消息类型\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\t//写入填充\n\tvar pad [3]byte\n\tif err := binary.Write(session, binary.BigEndian, pad); err != nil {\n\t\treturn err\n\t}\n\n\tif err := binary.Write(session, binary.BigEndian, that.flags); err != nil {\n\t\treturn err\n\t}\n\n\tif err := binary.Write(session, binary.BigEndian, that.length); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.payload); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "messages/serverSetColorMapEntries.go",
    "content": "package messages\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// SetColorMapEntries 设置颜色表的内容\n//  See RFC 6143 Section 7.6.2\ntype SetColorMapEntries struct {\n\t_          [1]byte //填充\n\tFirstColor uint16  // 颜色的起始位置，\n\tColorsNum  uint16  // 颜色的数目\n\tColors     []rfb.Color\n}\n\nfunc (that *SetColorMapEntries) Clone() rfb.Message {\n\n\tc := &SetColorMapEntries{\n\t\tFirstColor: that.FirstColor,\n\t\tColorsNum:  that.ColorsNum,\n\t\tColors:     that.Colors,\n\t}\n\treturn c\n}\nfunc (that *SetColorMapEntries) Supported(session rfb.ISession) bool {\n\treturn true\n}\n\n// String returns string\nfunc (that *SetColorMapEntries) String() string {\n\treturn fmt.Sprintf(\"first color: %d, numcolors: %d, colors[]: { %v }\", that.FirstColor, that.ColorsNum, that.Colors)\n}\n\n// Type returns MessageType\nfunc (*SetColorMapEntries) Type() rfb.MessageType {\n\treturn rfb.MessageType(rfb.SetColorMapEntries)\n}\n\nfunc (that *SetColorMapEntries) Read(session rfb.ISession) (rfb.Message, error) {\n\tmsg := &SetColorMapEntries{}\n\t// 先读取一个字节的填充\n\tvar pad [1]byte\n\tif err := binary.Read(session, binary.BigEndian, &pad); err != nil {\n\t\treturn nil, err\n\t}\n\t// 单个消息不必指定整个色彩映射表，而可能能只更新几个条目。\n\t//例如，如果我想更新条目 5 和 6，我会在FirstColor中指定，后跟两组 RGB 值。first-colour:5 number-of-colours:2\n\tif err := binary.Read(session, binary.BigEndian, &msg.FirstColor); err != nil {\n\t\treturn nil, err\n\t}\n\t// 获取此次要更新几个颜色\n\tif err := binary.Read(session, binary.BigEndian, &msg.ColorsNum); err != nil {\n\t\treturn nil, err\n\t}\n\n\tmsg.Colors = make([]rfb.Color, msg.ColorsNum)\n\tcolorMap := session.Options().ColorMap\n\t//读取指定的颜色数据\n\tfor i := uint16(0); i < msg.ColorsNum; i++ {\n\t\tcolor := &msg.Colors[i]\n\t\terr := color.Read(session)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcolorMap[msg.FirstColor+i] = *color\n\t}\n\tsession.SetColorMap(colorMap)\n\treturn msg, nil\n}\n\nfunc (that *SetColorMapEntries) Write(session rfb.ISession) error {\n\n\t// 写入消息类型\n\tif err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {\n\t\treturn err\n\t}\n\t// 填充\n\tvar pad [1]byte\n\tif err := binary.Write(session, binary.BigEndian, &pad); err != nil {\n\t\treturn err\n\t}\n\n\t// 首个颜色\n\tif err := binary.Write(session, binary.BigEndian, that.FirstColor); err != nil {\n\t\treturn err\n\t}\n\t// 要更新的颜色数目\n\tif that.ColorsNum < uint16(len(that.Colors)) {\n\t\tthat.ColorsNum = uint16(len(that.Colors))\n\t}\n\tif err := binary.Write(session, binary.BigEndian, that.ColorsNum); err != nil {\n\t\treturn err\n\t}\n\n\t// 颜色数据\n\tfor i := 0; i < len(that.Colors); i++ {\n\t\tcolor := that.Colors[i]\n\t\tif err := binary.Write(session, binary.BigEndian, color); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn session.Flush()\n}\n"
  },
  {
    "path": "rfb/color_map.go",
    "content": "package rfb\n\nimport \"encoding/binary\"\n\n// ColorMap 颜色地图\ntype ColorMap [256]Color\n\n// Color 表示颜色地图中的一个颜色。\ntype Color struct {\n\tpf      *PixelFormat\n\tcm      *ColorMap\n\tcmIndex uint32 // Only valid if pf.TrueColor is false.\n\tR, G, B uint16\n}\n\n// 写入颜色数据\nfunc (that *Color) Write(session ISession) error {\n\tvar err error\n\tpf := session.Options().PixelFormat\n\torder := pf.Order()\n\tpixel := that.cmIndex\n\tif that.pf.TrueColor != 0 {\n\t\tpixel = uint32(that.R) << pf.RedShift\n\t\tpixel |= uint32(that.G) << pf.GreenShift\n\t\tpixel |= uint32(that.B) << pf.BlueShift\n\t}\n\n\tswitch pf.BPP {\n\tcase 8:\n\t\terr = binary.Write(session, order, byte(pixel))\n\tcase 16:\n\t\terr = binary.Write(session, order, uint16(pixel))\n\tcase 32:\n\t\terr = binary.Write(session, order, uint32(pixel))\n\t}\n\n\treturn err\n}\n\n// 从链接中读取颜色偏移量\nfunc (that *Color) Read(session ISession) error {\n\torder := that.pf.Order()\n\tvar pixel uint32\n\n\tswitch that.pf.BPP {\n\tcase 8:\n\t\tvar px uint8\n\t\tif err := binary.Read(session, order, &px); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpixel = uint32(px)\n\tcase 16:\n\t\tvar px uint16\n\t\tif err := binary.Read(session, order, &px); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpixel = uint32(px)\n\tcase 32:\n\t\tvar px uint32\n\t\tif err := binary.Read(session, order, &px); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpixel = px\n\t}\n\n\tif that.pf.TrueColor != 0 {\n\t\tthat.R = uint16((pixel >> that.pf.RedShift) & uint32(that.pf.RedMax))\n\t\tthat.G = uint16((pixel >> that.pf.GreenShift) & uint32(that.pf.GreenMax))\n\t\tthat.B = uint16((pixel >> that.pf.BlueShift) & uint32(that.pf.BlueMax))\n\t} else {\n\t\t*that = that.cm[pixel]\n\t\tthat.cmIndex = pixel\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "rfb/desktop.go",
    "content": "package rfb\n\n//\n//type Desktop struct {\n//\tdesktopName []byte      // 桌面名称\n//\tfbHeight    uint16      // 缓冲帧高度\n//\tfbWidth     uint16      // 缓冲帧宽度\n//\tcolorMap    ColorMap    // 颜色地图\n//\tpixelFormat PixelFormat // 像素格式\n//}\n//\n//// PixelFormat 获取像素格式\n//func (that *Desktop) PixelFormat() PixelFormat {\n//\treturn that.pixelFormat\n//}\n//\n//// SetPixelFormat 设置像素格式\n//func (that *Desktop) SetPixelFormat(pf PixelFormat) {\n//\tthat.pixelFormat = pf\n//}\n//\n//// ColorMap 获取颜色地图\n//func (that *Desktop) ColorMap() ColorMap {\n//\treturn that.colorMap\n//}\n//\n//// SetColorMap 设置颜色地图\n//func (that *Desktop) SetColorMap(cm ColorMap) {\n//\tthat.colorMap = cm\n//}\n//\n//// Width 获取桌面宽度\n//func (that *Desktop) Width() uint16 {\n//\treturn that.fbWidth\n//}\n//\n//// SetWidth 设置桌面宽度\n//func (that *Desktop) SetWidth(width uint16) {\n//\tthat.fbWidth = width\n//}\n//\n//// Height 获取桌面高度\n//func (that *Desktop) Height() uint16 {\n//\treturn that.fbHeight\n//}\n//\n//// SetHeight 设置桌面高度\n//func (that *Desktop) SetHeight(height uint16) {\n//\tthat.fbHeight = height\n//}\n//\n//// DesktopName 获取该会话的桌面名称\n//func (that *Desktop) DesktopName() []byte {\n//\treturn that.desktopName\n//}\n//\n//// SetDesktopName 设置桌面名称\n//func (that *Desktop) SetDesktopName(name []byte) {\n//\tthat.desktopName = name\n//}\n"
  },
  {
    "path": "rfb/encoding.go",
    "content": "package rfb\n\n// IEncoding vnc像素数据编码格式的接口定义\ntype IEncoding interface {\n\tType() EncodingType\n\tSupported(ISession) bool\n\tClone(...bool) IEncoding\n\tRead(ISession, *Rectangle) error\n\tWrite(ISession, *Rectangle) error\n}\n"
  },
  {
    "path": "rfb/encodingtype.go",
    "content": "package rfb\n\ntype EncodingType int32\n\n//go:generate stringer -type=EncodingType\n\n// https://www.iana.org/assignments/rfb/rfb.xml#rfb-4\nconst (\n\tEncRaw                           EncodingType = 0 // 不编码，原始的格式\n\tEncCopyRect                      EncodingType = 1 //从帧缓冲复制\n\tEncRRE                           EncodingType = 2 // 二维游程编码\n\tEncCoRRE                         EncodingType = 4 // 二维游程编码的变体\n\tEncHexTile                       EncodingType = 5 // RRE 的变种，图块游程编码\n\tEncZlib                          EncodingType = 6 // zlib压缩\n\tEncTight                         EncodingType = 7 // tightvnc项目设置的编码\n\tEncZlibHex                       EncodingType = 8 // zlib压缩Hextile\n\tEncUltra1                        EncodingType = 9\n\tEncUltra2                        EncodingType = 10\n\tEncTRLE                          EncodingType = 15 //图块游程编码\n\tEncZRLE                          EncodingType = 16 //zlib 压缩的游程编码\n\tEncH264                          EncodingType = 20\n\tEncJPEG                          EncodingType = 21\n\tEncJRLE                          EncodingType = 22\n\tEncAtenAST2100                   EncodingType = 87\n\tEncAtenASTJPEG                   EncodingType = 88\n\tEncAtenHermon                    EncodingType = 89\n\tEncAtenYarkon                    EncodingType = 90\n\tEncAtenPilot3                    EncodingType = 91\n\tEncJPEGQualityLevelPseudo10      EncodingType = -23\n\tEncJPEGQualityLevelPseudo9       EncodingType = -24\n\tEncJPEGQualityLevelPseudo8       EncodingType = -25\n\tEncJPEGQualityLevelPseudo7       EncodingType = -26\n\tEncJPEGQualityLevelPseudo6       EncodingType = -27\n\tEncJPEGQualityLevelPseudo5       EncodingType = -28\n\tEncJPEGQualityLevelPseudo4       EncodingType = -29\n\tEncJPEGQualityLevelPseudo3       EncodingType = -30\n\tEncJPEGQualityLevelPseudo2       EncodingType = -31\n\tEncJPEGQualityLevelPseudo1       EncodingType = -32\n\tEncDesktopSizePseudo             EncodingType = -223 //桌面分辨率伪编码\n\tEncLastRectPseudo                EncodingType = -224 //  表示是最后一个矩形的伪编码\n\tEncPointerPosPseudo              EncodingType = -232\n\tEncCursorPseudo                  EncodingType = -239 //光标掩码\n\tEncXCursorPseudo                 EncodingType = -240\n\tEncCompressionLevel10            EncodingType = -247\n\tEncCompressionLevel9             EncodingType = -248\n\tEncCompressionLevel8             EncodingType = -249\n\tEncCompressionLevel7             EncodingType = -250\n\tEncCompressionLevel6             EncodingType = -251\n\tEncCompressionLevel5             EncodingType = -252\n\tEncCompressionLevel4             EncodingType = -253\n\tEncCompressionLevel3             EncodingType = -254\n\tEncCompressionLevel2             EncodingType = -255\n\tEncCompressionLevel1             EncodingType = -256\n\tEncQEMUPointerMotionChangePseudo EncodingType = -257\n\tEncQEMUExtendedKeyEventPseudo    EncodingType = -258\n\tEncTightPng                      EncodingType = -260\n\tEncLedStatePseudo                EncodingType = -261\n\tEncDesktopNamePseudo             EncodingType = -307\n\tEncExtendedDesktopSizePseudo     EncodingType = -308\n\tEncXvpPseudo                     EncodingType = -309\n\tEncClientRedirect                EncodingType = -311\n\tEncFencePseudo                   EncodingType = -312\n\tEncContinuousUpdatesPseudo       EncodingType = -313\n\tEncCursorWithAlphaPseudo         EncodingType = -314\n\tEncExtendedClipboardPseudo       EncodingType = -1063131698 //C0A1E5CE\n\tEncTightPNGBase64                EncodingType = 21 + 0x574d5600\n\tEncTightDiffComp                 EncodingType = 22 + 0x574d5600\n\tEncVMWDefineCursor               EncodingType = 100 + 0x574d5600\n\tEncVMWCursorState                EncodingType = 101 + 0x574d5600\n\tEncVMWCursorPosition             EncodingType = 102 + 0x574d5600\n\tEncVMWTypematicInfo              EncodingType = 103 + 0x574d5600\n\tEncVMWLEDState                   EncodingType = 104 + 0x574d5600\n\tEncVMWServerPush2                EncodingType = 123 + 0x574d5600\n\tEncVMWServerCaps                 EncodingType = 122 + 0x574d5600\n\tEncVMWFrameStamp                 EncodingType = 124 + 0x574d5600\n\tEncOffscreenCopyRect             EncodingType = 126 + 0x574d5600\n)\n"
  },
  {
    "path": "rfb/encodingtype_string.go",
    "content": "// Code generated by \"stringer -type=EncodingType\"; DO NOT EDIT.\n\npackage rfb\n\nimport \"strconv\"\n\nfunc _() {\n\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n\t// Re-run the stringer command to generate them again.\n\tvar x [1]struct{}\n\t_ = x[EncRaw-0]\n\t_ = x[EncCopyRect-1]\n\t_ = x[EncRRE-2]\n\t_ = x[EncCoRRE-4]\n\t_ = x[EncHexTile-5]\n\t_ = x[EncZlib-6]\n\t_ = x[EncTight-7]\n\t_ = x[EncZlibHex-8]\n\t_ = x[EncUltra1-9]\n\t_ = x[EncUltra2-10]\n\t_ = x[EncTRLE-15]\n\t_ = x[EncZRLE-16]\n\t_ = x[EncH264-20]\n\t_ = x[EncJPEG-21]\n\t_ = x[EncJRLE-22]\n\t_ = x[EncAtenAST2100-87]\n\t_ = x[EncAtenASTJPEG-88]\n\t_ = x[EncAtenHermon-89]\n\t_ = x[EncAtenYarkon-90]\n\t_ = x[EncAtenPilot3-91]\n\t_ = x[EncJPEGQualityLevelPseudo10 - -23]\n\t_ = x[EncJPEGQualityLevelPseudo9 - -24]\n\t_ = x[EncJPEGQualityLevelPseudo8 - -25]\n\t_ = x[EncJPEGQualityLevelPseudo7 - -26]\n\t_ = x[EncJPEGQualityLevelPseudo6 - -27]\n\t_ = x[EncJPEGQualityLevelPseudo5 - -28]\n\t_ = x[EncJPEGQualityLevelPseudo4 - -29]\n\t_ = x[EncJPEGQualityLevelPseudo3 - -30]\n\t_ = x[EncJPEGQualityLevelPseudo2 - -31]\n\t_ = x[EncJPEGQualityLevelPseudo1 - -32]\n\t_ = x[EncDesktopSizePseudo - -223]\n\t_ = x[EncLastRectPseudo - -224]\n\t_ = x[EncPointerPosPseudo - -232]\n\t_ = x[EncCursorPseudo - -239]\n\t_ = x[EncXCursorPseudo - -240]\n\t_ = x[EncCompressionLevel10 - -247]\n\t_ = x[EncCompressionLevel9 - -248]\n\t_ = x[EncCompressionLevel8 - -249]\n\t_ = x[EncCompressionLevel7 - -250]\n\t_ = x[EncCompressionLevel6 - -251]\n\t_ = x[EncCompressionLevel5 - -252]\n\t_ = x[EncCompressionLevel4 - -253]\n\t_ = x[EncCompressionLevel3 - -254]\n\t_ = x[EncCompressionLevel2 - -255]\n\t_ = x[EncCompressionLevel1 - -256]\n\t_ = x[EncQEMUPointerMotionChangePseudo - -257]\n\t_ = x[EncQEMUExtendedKeyEventPseudo - -258]\n\t_ = x[EncTightPng - -260]\n\t_ = x[EncLedStatePseudo - -261]\n\t_ = x[EncDesktopNamePseudo - -307]\n\t_ = x[EncExtendedDesktopSizePseudo - -308]\n\t_ = x[EncXvpPseudo - -309]\n\t_ = x[EncClientRedirect - -311]\n\t_ = x[EncFencePseudo - -312]\n\t_ = x[EncContinuousUpdatesPseudo - -313]\n\t_ = x[EncCursorWithAlphaPseudo - -314]\n\t_ = x[EncExtendedClipboardPseudo - -1063131698]\n\t_ = x[EncTightPNGBase64-1464686101]\n\t_ = x[EncTightDiffComp-1464686102]\n\t_ = x[EncVMWDefineCursor-1464686180]\n\t_ = x[EncVMWCursorState-1464686181]\n\t_ = x[EncVMWCursorPosition-1464686182]\n\t_ = x[EncVMWTypematicInfo-1464686183]\n\t_ = x[EncVMWLEDState-1464686184]\n\t_ = x[EncVMWServerPush2-1464686203]\n\t_ = x[EncVMWServerCaps-1464686202]\n\t_ = x[EncVMWFrameStamp-1464686204]\n\t_ = x[EncOffscreenCopyRect-1464686206]\n}\n\nconst _EncodingType_name = \"EncExtendedClipboardPseudoEncCursorWithAlphaPseudoEncContinuousUpdatesPseudoEncFencePseudoEncClientRedirectEncXvpPseudoEncExtendedDesktopSizePseudoEncDesktopNamePseudoEncLedStatePseudoEncTightPngEncQEMUExtendedKeyEventPseudoEncQEMUPointerMotionChangePseudoEncCompressionLevel1EncCompressionLevel2EncCompressionLevel3EncCompressionLevel4EncCompressionLevel5EncCompressionLevel6EncCompressionLevel7EncCompressionLevel8EncCompressionLevel9EncCompressionLevel10EncXCursorPseudoEncCursorPseudoEncPointerPosPseudoEncLastRectPseudoEncDesktopSizePseudoEncJPEGQualityLevelPseudo1EncJPEGQualityLevelPseudo2EncJPEGQualityLevelPseudo3EncJPEGQualityLevelPseudo4EncJPEGQualityLevelPseudo5EncJPEGQualityLevelPseudo6EncJPEGQualityLevelPseudo7EncJPEGQualityLevelPseudo8EncJPEGQualityLevelPseudo9EncJPEGQualityLevelPseudo10EncRawEncCopyRectEncRREEncCoRREEncHexTileEncZlibEncTightEncZlibHexEncUltra1EncUltra2EncTRLEEncZRLEEncH264EncJPEGEncJRLEEncAtenAST2100EncAtenASTJPEGEncAtenHermonEncAtenYarkonEncAtenPilot3EncTightPNGBase64EncTightDiffCompEncVMWDefineCursorEncVMWCursorStateEncVMWCursorPositionEncVMWTypematicInfoEncVMWLEDStateEncVMWServerCapsEncVMWServerPush2EncVMWFrameStampEncOffscreenCopyRect\"\n\nvar _EncodingType_map = map[EncodingType]string{\n\t-1063131698: _EncodingType_name[0:26],\n\t-314:        _EncodingType_name[26:50],\n\t-313:        _EncodingType_name[50:76],\n\t-312:        _EncodingType_name[76:90],\n\t-311:        _EncodingType_name[90:107],\n\t-309:        _EncodingType_name[107:119],\n\t-308:        _EncodingType_name[119:147],\n\t-307:        _EncodingType_name[147:167],\n\t-261:        _EncodingType_name[167:184],\n\t-260:        _EncodingType_name[184:195],\n\t-258:        _EncodingType_name[195:224],\n\t-257:        _EncodingType_name[224:256],\n\t-256:        _EncodingType_name[256:276],\n\t-255:        _EncodingType_name[276:296],\n\t-254:        _EncodingType_name[296:316],\n\t-253:        _EncodingType_name[316:336],\n\t-252:        _EncodingType_name[336:356],\n\t-251:        _EncodingType_name[356:376],\n\t-250:        _EncodingType_name[376:396],\n\t-249:        _EncodingType_name[396:416],\n\t-248:        _EncodingType_name[416:436],\n\t-247:        _EncodingType_name[436:457],\n\t-240:        _EncodingType_name[457:473],\n\t-239:        _EncodingType_name[473:488],\n\t-232:        _EncodingType_name[488:507],\n\t-224:        _EncodingType_name[507:524],\n\t-223:        _EncodingType_name[524:544],\n\t-32:         _EncodingType_name[544:570],\n\t-31:         _EncodingType_name[570:596],\n\t-30:         _EncodingType_name[596:622],\n\t-29:         _EncodingType_name[622:648],\n\t-28:         _EncodingType_name[648:674],\n\t-27:         _EncodingType_name[674:700],\n\t-26:         _EncodingType_name[700:726],\n\t-25:         _EncodingType_name[726:752],\n\t-24:         _EncodingType_name[752:778],\n\t-23:         _EncodingType_name[778:805],\n\t0:           _EncodingType_name[805:811],\n\t1:           _EncodingType_name[811:822],\n\t2:           _EncodingType_name[822:828],\n\t4:           _EncodingType_name[828:836],\n\t5:           _EncodingType_name[836:846],\n\t6:           _EncodingType_name[846:853],\n\t7:           _EncodingType_name[853:861],\n\t8:           _EncodingType_name[861:871],\n\t9:           _EncodingType_name[871:880],\n\t10:          _EncodingType_name[880:889],\n\t15:          _EncodingType_name[889:896],\n\t16:          _EncodingType_name[896:903],\n\t20:          _EncodingType_name[903:910],\n\t21:          _EncodingType_name[910:917],\n\t22:          _EncodingType_name[917:924],\n\t87:          _EncodingType_name[924:938],\n\t88:          _EncodingType_name[938:952],\n\t89:          _EncodingType_name[952:965],\n\t90:          _EncodingType_name[965:978],\n\t91:          _EncodingType_name[978:991],\n\t1464686101:  _EncodingType_name[991:1008],\n\t1464686102:  _EncodingType_name[1008:1024],\n\t1464686180:  _EncodingType_name[1024:1042],\n\t1464686181:  _EncodingType_name[1042:1059],\n\t1464686182:  _EncodingType_name[1059:1079],\n\t1464686183:  _EncodingType_name[1079:1098],\n\t1464686184:  _EncodingType_name[1098:1112],\n\t1464686202:  _EncodingType_name[1112:1128],\n\t1464686203:  _EncodingType_name[1128:1145],\n\t1464686204:  _EncodingType_name[1145:1161],\n\t1464686206:  _EncodingType_name[1161:1181],\n}\n\nfunc (i EncodingType) String() string {\n\tif str, ok := _EncodingType_map[i]; ok {\n\t\treturn str\n\t}\n\treturn \"EncodingType(\" + strconv.FormatInt(int64(i), 10) + \")\"\n}\n"
  },
  {
    "path": "rfb/handler.go",
    "content": "package rfb\n\ntype IHandler interface {\n\tHandle(session ISession) error\n}\n\n// ProtoVersionLength rfb协议长度\nconst ProtoVersionLength = 12\n\nconst (\n\t// ProtoVersionUnknown 未知协议\n\tProtoVersionUnknown = \"\"\n\t// ProtoVersion33 版本 003.003\n\tProtoVersion33 = \"RFB 003.003\\n\"\n\t// ProtoVersion38 版本 003.008\n\tProtoVersion38 = \"RFB 003.008\\n\"\n\t// ProtoVersion37 版本 003.007\n\tProtoVersion37 = \"RFB 003.007\\n\"\n)\n"
  },
  {
    "path": "rfb/keys.go",
    "content": "package rfb\n\nimport \"fmt\"\n\ntype Key uint32\n\n// Keys 按键列表\ntype Keys []Key\n\nvar keymap = map[rune]Key{\n\t'-': Minus,\n\t'0': Digit0,\n\t'1': Digit1,\n\t'2': Digit2,\n\t'3': Digit3,\n\t'4': Digit4,\n\t'5': Digit5,\n\t'6': Digit6,\n\t'7': Digit7,\n\t'8': Digit8,\n\t'9': Digit9,\n}\n\n// IntToKeys 返回表示键入int类型所需按下的键的键。\nfunc IntToKeys(v int) Keys {\n\tk := Keys{}\n\tfor _, c := range fmt.Sprintf(\"%d\", v) {\n\t\tk = append(k, keymap[c])\n\t}\n\treturn k\n}\n\n// Latin 1 (byte 3 = 0)\n// ISO/IEC 8859-1 = Unicode U+0020..U+00FF\nconst (\n\tSpace   Key = iota + 0x0020\n\tExclaim     // exclamation mark\n\tQuoteDbl\n\tNumberSign\n\tDollar\n\tPercent\n\tAmpersand\n\tApostrophe\n\tParenLeft\n\tParenRight\n\tAsterisk\n\tPlus\n\tComma\n\tMinus\n\tPeriod\n\tSlash\n\tDigit0\n\tDigit1\n\tDigit2\n\tDigit3\n\tDigit4\n\tDigit5\n\tDigit6\n\tDigit7\n\tDigit8\n\tDigit9\n\tColon\n\tSemicolon\n\tLess\n\tEqual\n\tGreater\n\tQuestion\n\tAt\n\tA\n\tB\n\tC\n\tD\n\tE\n\tF\n\tG\n\tH\n\tI\n\tJ\n\tK\n\tL\n\tM\n\tN\n\tO\n\tP\n\tQ\n\tR\n\tS\n\tT\n\tU\n\tV\n\tW\n\tX\n\tY\n\tZ\n\tBracketLeft\n\tBackslash\n\tBracketRight\n\tAsciiCircum\n\tUnderscore\n\tGrave\n\tSmallA\n\tSmallB\n\tSmallC\n\tSmallD\n\tSmallE\n\tSmallF\n\tSmallG\n\tSmallH\n\tSmallI\n\tSmallJ\n\tSmallK\n\tSmallL\n\tSmallM\n\tSmallN\n\tSmallO\n\tSmallP\n\tSmallQ\n\tSmallR\n\tSmallS\n\tSmallT\n\tSmallU\n\tSmallV\n\tSmallW\n\tSmallX\n\tSmallY\n\tSmallZ\n\tBraceLeft\n\tBar\n\tBraceRight\n\tAsciiTilde\n)\n\nconst (\n\tBackSpace Key = iota + 0xff08\n\tTab\n\tLinefeed\n\tClear\n\t_\n\tReturn\n)\n\nconst (\n\tPause Key = iota + 0xff13\n\tScrollLock\n\tSysReq\n\tEscape Key = 0xff1b\n\tDelete Key = 0xffff\n)\n\nconst ( // Cursor control & motion.\n\tHome Key = iota + 0xff50\n\tLeft\n\tUp\n\tRight\n\tDown\n\tPageUp\n\tPageDown\n\tEnd\n\tBegin\n)\n\nconst ( // Misc functions.\n\tSelect Key = 0xff60\n\tPrint\n\tExecute\n\tInsert\n\tUndo\n\tRedo\n\tMenu\n\tFind\n\tCancel\n\tHelp\n\tBreak\n\tModeSwitch Key = 0xff7e\n\tNumLock    Key = 0xff7f\n)\n\nconst ( // Keypad functions.\n\tKeypadSpace Key = 0xff80\n\tKeypadTab   Key = 0xff89\n\tKeypadEnter Key = 0xff8d\n)\n\nconst ( // Keypad functions cont.\n\tKeypadF1 Key = iota + 0xff91\n\tKeypadF2\n\tKeypadF3\n\tKeypadF4\n\tKeypadHome\n\tKeypadLeft\n\tKeypadUp\n\tKeypadRight\n\tKeypadDown\n\tKeypadPrior\n\tKeypadPageUp\n\tKeypadNext\n\tKeypadPageDown\n\tKeypadEnd\n\tKeypadBegin\n\tKeypadInsert\n\tKeypadDelete\n\tKeypadMultiply\n\tKeypadAdd\n\tKeypadSeparator\n\tKeypadSubtract\n\tKeypadDecimal\n\tKeypadDivide\n\tKeypad0\n\tKeypad1\n\tKeypad2\n\tKeypad3\n\tKeypad4\n\tKeypad5\n\tKeypad6\n\tKeypad7\n\tKeypad8\n\tKeypad9\n\tKeypadEqual Key = 0xffbd\n)\n\nconst (\n\tF1 Key = iota + 0xffbe\n\tF2\n\tF3\n\tF4\n\tF5\n\tF6\n\tF7\n\tF8\n\tF9\n\tF10\n\tF11\n\tF12\n)\n\nconst (\n\tShiftLeft Key = iota + 0xffe1\n\tShiftRight\n\tControlLeft\n\tControlRight\n\tCapsLock\n\tShiftLock\n\tMetaLeft\n\tMetaRight\n\tAltLeft\n\tAltRight\n\tSuperLeft\n\tSuperRight\n\tHyperLeft\n\tHyperRight\n)\n"
  },
  {
    "path": "rfb/keys_string.go",
    "content": "package rfb\n\nimport \"fmt\"\n\nconst keyName = \"SpaceExclaimQuoteDblNumberSignDollarPercentAmpersandApostropheParenLeftParenRightAsteriskPlusCommaMinusPeriodSlashDigit0Digit1Digit2Digit3Digit4Digit5Digit6Digit7Digit8Digit9ColonSemicolonLessEqualGreaterQuestionAtABCDEFGHIJKLMNOPQRSTUVWXYZBracketLeftBackslashBracketRightAsciiCircumUnderscoreGraveSmallASmallBSmallCSmallDSmallESmallFSmallGSmallHSmallISmallJSmallKSmallLSmallMSmallNSmallOSmallPSmallQSmallRSmallSSmallTSmallUSmallVSmallWSmallXSmallYSmallZBraceLeftBarBraceRightAsciiTildeBackSpaceTabLinefeedClearReturnPauseScrollLockSysReqEscapeHomeLeftUpRightDownPageUpPageDownEndBeginSelectModeSwitchNumLockKeypadSpaceKeypadTabKeypadEnterKeypadF1KeypadF2KeypadF3KeypadF4KeypadHomeKeypadLeftKeypadUpKeypadRightKeypadDownKeypadPriorKeypadPageUpKeypadNextKeypadPageDownKeypadEndKeypadBeginKeypadInsertKeypadDeleteKeypadMultiplyKeypadAddKeypadSeparatorKeypadSubtractKeypadDecimalKeypadDivideKeypad0Keypad1Keypad2Keypad3Keypad4Keypad5Keypad6Keypad7Keypad8Keypad9KeypadEqualF1F2F3F4F5F6F7F8F9F10F11F12ShiftLeftShiftRightControlLeftControlRightCapsLockShiftLockMetaLeftMetaRightAltLeftAltRightSuperLeftSuperRightHyperLeftHyperRightDelete\"\n\nvar keyMap = map[Key]string{\n\t32:    keyName[0:5],\n\t33:    keyName[5:12],\n\t34:    keyName[12:20],\n\t35:    keyName[20:30],\n\t36:    keyName[30:36],\n\t37:    keyName[36:43],\n\t38:    keyName[43:52],\n\t39:    keyName[52:62],\n\t40:    keyName[62:71],\n\t41:    keyName[71:81],\n\t42:    keyName[81:89],\n\t43:    keyName[89:93],\n\t44:    keyName[93:98],\n\t45:    keyName[98:103],\n\t46:    keyName[103:109],\n\t47:    keyName[109:114],\n\t48:    keyName[114:120],\n\t49:    keyName[120:126],\n\t50:    keyName[126:132],\n\t51:    keyName[132:138],\n\t52:    keyName[138:144],\n\t53:    keyName[144:150],\n\t54:    keyName[150:156],\n\t55:    keyName[156:162],\n\t56:    keyName[162:168],\n\t57:    keyName[168:174],\n\t58:    keyName[174:179],\n\t59:    keyName[179:188],\n\t60:    keyName[188:192],\n\t61:    keyName[192:197],\n\t62:    keyName[197:204],\n\t63:    keyName[204:212],\n\t64:    keyName[212:214],\n\t65:    keyName[214:215],\n\t66:    keyName[215:216],\n\t67:    keyName[216:217],\n\t68:    keyName[217:218],\n\t69:    keyName[218:219],\n\t70:    keyName[219:220],\n\t71:    keyName[220:221],\n\t72:    keyName[221:222],\n\t73:    keyName[222:223],\n\t74:    keyName[223:224],\n\t75:    keyName[224:225],\n\t76:    keyName[225:226],\n\t77:    keyName[226:227],\n\t78:    keyName[227:228],\n\t79:    keyName[228:229],\n\t80:    keyName[229:230],\n\t81:    keyName[230:231],\n\t82:    keyName[231:232],\n\t83:    keyName[232:233],\n\t84:    keyName[233:234],\n\t85:    keyName[234:235],\n\t86:    keyName[235:236],\n\t87:    keyName[236:237],\n\t88:    keyName[237:238],\n\t89:    keyName[238:239],\n\t90:    keyName[239:240],\n\t91:    keyName[240:251],\n\t92:    keyName[251:260],\n\t93:    keyName[260:272],\n\t94:    keyName[272:283],\n\t95:    keyName[283:293],\n\t96:    keyName[293:298],\n\t97:    keyName[298:304],\n\t98:    keyName[304:310],\n\t99:    keyName[310:316],\n\t100:   keyName[316:322],\n\t101:   keyName[322:328],\n\t102:   keyName[328:334],\n\t103:   keyName[334:340],\n\t104:   keyName[340:346],\n\t105:   keyName[346:352],\n\t106:   keyName[352:358],\n\t107:   keyName[358:364],\n\t108:   keyName[364:370],\n\t109:   keyName[370:376],\n\t110:   keyName[376:382],\n\t111:   keyName[382:388],\n\t112:   keyName[388:394],\n\t113:   keyName[394:400],\n\t114:   keyName[400:406],\n\t115:   keyName[406:412],\n\t116:   keyName[412:418],\n\t117:   keyName[418:424],\n\t118:   keyName[424:430],\n\t119:   keyName[430:436],\n\t120:   keyName[436:442],\n\t121:   keyName[442:448],\n\t122:   keyName[448:454],\n\t123:   keyName[454:463],\n\t124:   keyName[463:466],\n\t125:   keyName[466:476],\n\t126:   keyName[476:486],\n\t65288: keyName[486:495],\n\t65289: keyName[495:498],\n\t65290: keyName[498:506],\n\t65291: keyName[506:511],\n\t65293: keyName[511:517],\n\t65299: keyName[517:522],\n\t65300: keyName[522:532],\n\t65301: keyName[532:538],\n\t65307: keyName[538:544],\n\t65360: keyName[544:548],\n\t65361: keyName[548:552],\n\t65362: keyName[552:554],\n\t65363: keyName[554:559],\n\t65364: keyName[559:563],\n\t65365: keyName[563:569],\n\t65366: keyName[569:577],\n\t65367: keyName[577:580],\n\t65368: keyName[580:585],\n\t65376: keyName[585:591],\n\t65406: keyName[591:601],\n\t65407: keyName[601:608],\n\t65408: keyName[608:619],\n\t65417: keyName[619:628],\n\t65421: keyName[628:639],\n\t65425: keyName[639:647],\n\t65426: keyName[647:655],\n\t65427: keyName[655:663],\n\t65428: keyName[663:671],\n\t65429: keyName[671:681],\n\t65430: keyName[681:691],\n\t65431: keyName[691:699],\n\t65432: keyName[699:710],\n\t65433: keyName[710:720],\n\t65434: keyName[720:731],\n\t65435: keyName[731:743],\n\t65436: keyName[743:753],\n\t65437: keyName[753:767],\n\t65438: keyName[767:776],\n\t65439: keyName[776:787],\n\t65440: keyName[787:799],\n\t65441: keyName[799:811],\n\t65442: keyName[811:825],\n\t65443: keyName[825:834],\n\t65444: keyName[834:849],\n\t65445: keyName[849:863],\n\t65446: keyName[863:876],\n\t65447: keyName[876:888],\n\t65448: keyName[888:895],\n\t65449: keyName[895:902],\n\t65450: keyName[902:909],\n\t65451: keyName[909:916],\n\t65452: keyName[916:923],\n\t65453: keyName[923:930],\n\t65454: keyName[930:937],\n\t65455: keyName[937:944],\n\t65456: keyName[944:951],\n\t65457: keyName[951:958],\n\t65469: keyName[958:969],\n\t65470: keyName[969:971],\n\t65471: keyName[971:973],\n\t65472: keyName[973:975],\n\t65473: keyName[975:977],\n\t65474: keyName[977:979],\n\t65475: keyName[979:981],\n\t65476: keyName[981:983],\n\t65477: keyName[983:985],\n\t65478: keyName[985:987],\n\t65479: keyName[987:990],\n\t65480: keyName[990:993],\n\t65481: keyName[993:996],\n\t65505: keyName[996:1005],\n\t65506: keyName[1005:1015],\n\t65507: keyName[1015:1026],\n\t65508: keyName[1026:1038],\n\t65509: keyName[1038:1046],\n\t65510: keyName[1046:1055],\n\t65511: keyName[1055:1063],\n\t65512: keyName[1063:1072],\n\t65513: keyName[1072:1079],\n\t65514: keyName[1079:1087],\n\t65515: keyName[1087:1096],\n\t65516: keyName[1096:1106],\n\t65517: keyName[1106:1115],\n\t65518: keyName[1115:1125],\n\t65535: keyName[1125:1131],\n}\n\nfunc (i Key) String() string {\n\tif str, ok := keyMap[i]; ok {\n\t\treturn str\n\t}\n\treturn fmt.Sprintf(\"Key(%d)\", i)\n}\n"
  },
  {
    "path": "rfb/message.go",
    "content": "package rfb\n\ntype Message interface {\n\tType() MessageType\n\tString() string\n\tSupported(ISession) bool\n\tRead(ISession) (Message, error)\n\tWrite(ISession) error\n\tClone() Message\n}\n"
  },
  {
    "path": "rfb/message_type_client_string.go",
    "content": "// Code generated by \"stringer -type=ClientMessageType\"; DO NOT EDIT.\n\npackage rfb\n\nimport \"strconv\"\n\nfunc _() {\n\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n\t// Re-run the stringer command to generate them again.\n\tvar x [1]struct{}\n\t_ = x[SetPixelFormat-0]\n\t_ = x[SetEncodings-2]\n\t_ = x[FramebufferUpdateRequest-3]\n\t_ = x[KeyEvent-4]\n\t_ = x[PointerEvent-5]\n\t_ = x[ClientCutText-6]\n\t_ = x[ClientFence-248]\n\t_ = x[QEMUExtendedKeyEvent-255]\n}\n\nconst (\n\t_ClientMessageType_name_0 = \"SetPixelFormat\"\n\t_ClientMessageType_name_1 = \"SetEncodingsFramebufferUpdateRequestKeyEventPointerEventClientCutText\"\n\t_ClientMessageType_name_2 = \"ClientFence\"\n\t_ClientMessageType_name_3 = \"QEMUExtendedKeyEvent\"\n)\n\nvar (\n\t_ClientMessageType_index_1 = [...]uint8{0, 12, 36, 44, 56, 69}\n)\n\nfunc (i ClientMessageType) String() string {\n\tswitch {\n\tcase i == 0:\n\t\treturn _ClientMessageType_name_0\n\tcase 2 <= i && i <= 6:\n\t\ti -= 2\n\t\treturn _ClientMessageType_name_1[_ClientMessageType_index_1[i]:_ClientMessageType_index_1[i+1]]\n\tcase i == 248:\n\t\treturn _ClientMessageType_name_2\n\tcase i == 255:\n\t\treturn _ClientMessageType_name_3\n\tdefault:\n\t\treturn \"ClientMessageType(\" + strconv.FormatInt(int64(i), 10) + \")\"\n\t}\n}\n"
  },
  {
    "path": "rfb/message_type_server_string.go",
    "content": "// Code generated by \"stringer -type=ServerMessageType\"; DO NOT EDIT.\n\npackage rfb\n\nimport \"strconv\"\n\nfunc _() {\n\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n\t// Re-run the stringer command to generate them again.\n\tvar x [1]struct{}\n\t_ = x[FramebufferUpdate-0]\n\t_ = x[SetColorMapEntries-1]\n\t_ = x[Bell-2]\n\t_ = x[ServerCutText-3]\n}\n\nconst _ServerMessageType_name = \"FramebufferUpdateSetColorMapEntriesBellServerCutText\"\n\nvar _ServerMessageType_index = [...]uint8{0, 17, 35, 39, 52}\n\nfunc (i ServerMessageType) String() string {\n\tif i >= ServerMessageType(len(_ServerMessageType_index)-1) {\n\t\treturn \"ServerMessageType(\" + strconv.FormatInt(int64(i), 10) + \")\"\n\t}\n\treturn _ServerMessageType_name[_ServerMessageType_index[i]:_ServerMessageType_index[i+1]]\n}\n"
  },
  {
    "path": "rfb/messagetype.go",
    "content": "package rfb\n\ntype MessageType uint8\n\n// ClientMessageType vnc客户端发送给vnc服务端的消息类型\ntype ClientMessageType MessageType\n\n//go:generate stringer -type=ClientMessageType\n\nconst (\n\tSetPixelFormat           ClientMessageType = 0   // 设置像素格式\n\tSetEncodings             ClientMessageType = 2   // 设置消息的编码格式\n\tFramebufferUpdateRequest ClientMessageType = 3   // 请求帧缓冲内容\n\tKeyEvent                 ClientMessageType = 4   // 键盘事件消息\n\tPointerEvent             ClientMessageType = 5   // 鼠标事件消息\n\tClientCutText            ClientMessageType = 6   // 剪切板消息\n\tEnableContinuousUpdates  ClientMessageType = 150 // 打开连续更新\n\tClientFence              ClientMessageType = 248 //客户端到服务端的数据同步请求\n\tSetDesktopSize           ClientMessageType = 251 //客户端设置桌面大小\n\tQEMUExtendedKeyEvent     ClientMessageType = 255 // qumu虚拟机的扩展按键消息\n)\n\n// ServerMessageType 服务端发送给客户端的消息类型\ntype ServerMessageType MessageType\n\n//go:generate stringer -type=ServerMessageType\n\nconst (\n\tFramebufferUpdate      ServerMessageType = 0   // 帧缓冲区更新消息\n\tSetColorMapEntries     ServerMessageType = 1   // 设置颜色地图\n\tBell                   ServerMessageType = 2   // 响铃\n\tServerCutText          ServerMessageType = 3   // 设置剪切板数据\n\tEndOfContinuousUpdates ServerMessageType = 150 //结束连续更新\n\tServerFence            ServerMessageType = 248 //支持 Fence 扩展的服务器发送此扩展以请求数据流的同步\n)\n"
  },
  {
    "path": "rfb/options.go",
    "content": "package rfb\n\nimport \"io\"\n\ntype Option func(*Options)\ntype GetConn func(sess ISession) (io.ReadWriteCloser, error)\n\n// Options 配置信息\ntype Options struct {\n\t// 公共配置\n\tHandlers                 []IHandler          //  处理程序列表\n\tSecurityHandlers         []ISecurityHandler  // 安全验证\n\tEncodings                []IEncoding         // 支持的编码类型\n\tPixelFormat              PixelFormat         // 像素格式\n\tColorMap                 ColorMap            // 颜色地图\n\tInput                    chan Message        // 输入消息\n\tOutput                   chan Message        // 输出消息\n\tMessages                 []Message           // 支持的消息类型\n\tDisableServerMessageType []ServerMessageType // 禁用的消息，碰到这些消息，则跳过\n\tDisableClientMessageType []ClientMessageType // 禁用的消息，碰到这些消息，则跳过\n\tQuitCh                   chan struct{}       // 退出\n\tErrorCh                  chan error          // 错误通道\n\n\t// 服务端配置\n\tDesktopName []byte // 桌面名称，作为服务端配置的时候，需要设置\n\tHeight      uint16 // 缓冲帧高度，作为服务端配置的时候，需要设置\n\tWidth       uint16 // 缓冲帧宽度，作为服务端配置的时候，需要设置\n\n\t// 客户端配置\n\tDrawCursor bool // 是否绘制鼠标指针\n\tExclusive  bool // 是否独占\n\n\t// 生成连接的方法\n\tGetConn GetConn\n}\n\n// OptHandlers 设置流程处理程序\nfunc OptHandlers(opt ...IHandler) Option {\n\treturn func(options *Options) {\n\t\toptions.Handlers = append(options.Handlers, opt...)\n\t}\n}\n\n// OptSecurityHandlers 设置权限认证处理程序\nfunc OptSecurityHandlers(opt ...ISecurityHandler) Option {\n\treturn func(options *Options) {\n\t\toptions.SecurityHandlers = append(options.SecurityHandlers, opt...)\n\t}\n}\n\n// OptEncodings 设置支持的编码格式\nfunc OptEncodings(opt ...IEncoding) Option {\n\treturn func(options *Options) {\n\t\toptions.Encodings = append(options.Encodings, opt...)\n\t}\n}\n\n// OptMessages 设置支持的消息类型\nfunc OptMessages(opt ...Message) Option {\n\treturn func(options *Options) {\n\t\toptions.Messages = append(options.Messages, opt...)\n\t}\n}\n\n// OptPixelFormat 设置像素格式\nfunc OptPixelFormat(opt PixelFormat) Option {\n\treturn func(options *Options) {\n\t\toptions.PixelFormat = opt\n\t}\n}\n\n// OptGetConn 设置生成连接方法\nfunc OptGetConn(opt GetConn) Option {\n\treturn func(options *Options) {\n\t\toptions.GetConn = opt\n\t}\n}\n\nfunc OptDesktopName(opt []byte) Option {\n\treturn func(options *Options) {\n\t\toptions.DesktopName = opt\n\t}\n}\n\nfunc OptHeight(opt int) Option {\n\treturn func(options *Options) {\n\t\toptions.Height = uint16(opt)\n\t}\n}\nfunc OptWidth(opt int) Option {\n\treturn func(options *Options) {\n\t\toptions.Width = uint16(opt)\n\t}\n}\n\n// OptDisableServerMessageType 要屏蔽的服务端消息\nfunc OptDisableServerMessageType(opt ...ServerMessageType) Option {\n\treturn func(options *Options) {\n\t\toptions.DisableServerMessageType = opt\n\t}\n}\n\n// OptDisableClientMessageType 要屏蔽的客户端消息\nfunc OptDisableClientMessageType(opt ...ClientMessageType) Option {\n\treturn func(options *Options) {\n\t\toptions.DisableClientMessageType = opt\n\t}\n}\n"
  },
  {
    "path": "rfb/pixel_format.go",
    "content": "package rfb\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\nconst PixelFormatLen = 16\n\nvar (\n\t// PixelFormat8bit 获取8bit像素格式\n\tPixelFormat8bit = NewPixelFormat(8)\n\t// PixelFormat16bit 获取15bit像素格式\n\tPixelFormat16bit = NewPixelFormat(16)\n\t// PixelFormat32bit 获取32bit像素格式\n\tPixelFormat32bit = NewPixelFormat(32)\n\t// PixelFormatAten returns pixel format used in Aten IKVM\n\tPixelFormatAten = NewPixelFormatAten()\n)\n\n// PixelFormat 像素格式结构体\ntype PixelFormat struct {\n\tBPP        uint8   // 1 byte,像素的位数，位数越大，色彩越丰富。只支持[8|16|32] 该值必须大于Depth\n\tDepth      uint8   // 1 byte,色深，像素中表示色彩的位数\n\tBigEndian  uint8   // 1 byte,多字节像素的字节序，非零即大端序\n\tTrueColor  uint8   // 1 byte,1 表示真彩色，pixel 的值表示 RGB 颜色；0 表示调色板，pexel 的值表示颜色在调色板的偏移量\n\tRedMax     uint16  // 2 byte,红色的长度\n\tGreenMax   uint16  // 2 byte,绿色的长度\n\tBlueMax    uint16  // 2 byte,蓝色的长度\n\tRedShift   uint8   // 1 byte,红色的位移量\n\tGreenShift uint8   // 1 byte,绿色的位移量\n\tBlueShift  uint8   // 1 byte,蓝色的偏移量\n\t_          [3]byte // 填充字节\n}\n\nfunc (that PixelFormat) String() string {\n\treturn 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 }\",\n\t\tthat.BPP, that.Depth, that.BigEndian, that.TrueColor, that.RedMax, that.GreenMax, that.BlueMax, that.RedShift, that.GreenShift, that.BlueShift)\n}\n\n// Order 确定像素格式是使用了大端字节序还是小端字节序\nfunc (that PixelFormat) Order() binary.ByteOrder {\n\tif that.BigEndian == 1 {\n\t\treturn binary.BigEndian\n\t}\n\treturn binary.LittleEndian\n}\n\nfunc NewPixelFormat(bpp uint8) PixelFormat {\n\tbigEndian := uint8(0)\n\t//\trgbMax := uint16(math.Exp2(float64(bpp))) - 1\n\trMax := uint16(255)\n\tgMax := uint16(255)\n\tbMax := uint16(255)\n\tvar (\n\t\ttc         = uint8(1)\n\t\trs, gs, bs uint8\n\t\tdepth      uint8\n\t)\n\tswitch bpp {\n\tcase 8:\n\t\ttc = 0\n\t\tdepth = 8\n\t\trs, gs, bs = 0, 0, 0\n\tcase 16:\n\t\tdepth = 16\n\t\trs, gs, bs = 0, 4, 8\n\tcase 32:\n\t\tdepth = 24\n\t\t//\trs, gs, bs = 0, 8, 16\n\t\trs, gs, bs = 16, 8, 0\n\t}\n\treturn PixelFormat{\n\t\tBPP:        bpp,\n\t\tDepth:      depth,\n\t\tBigEndian:  bigEndian,\n\t\tTrueColor:  tc,\n\t\tRedMax:     rMax,\n\t\tGreenMax:   gMax,\n\t\tBlueMax:    bMax,\n\t\tRedShift:   rs,\n\t\tGreenShift: gs,\n\t\tBlueShift:  bs,\n\t\t//_:          [3]byte{},\n\t}\n}\n\nfunc NewPixelFormatAten() PixelFormat {\n\treturn PixelFormat{\n\t\tBPP:        16,\n\t\tDepth:      15,\n\t\tBigEndian:  0,\n\t\tTrueColor:  1,\n\t\tRedMax:     (1 << 5) - 1,\n\t\tGreenMax:   (1 << 5) - 1,\n\t\tBlueMax:    (1 << 5) - 1,\n\t\tRedShift:   10,\n\t\tGreenShift: 5,\n\t\tBlueShift:  0,\n\t\t//_:          [3]byte{},\n\t}\n}\n"
  },
  {
    "path": "rfb/rectangle.go",
    "content": "package rfb\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\n// Rectangle 表示像素数据的矩形\ntype Rectangle struct {\n\tX       uint16\n\tY       uint16\n\tWidth   uint16\n\tHeight  uint16\n\tEncType EncodingType\n\tEnc     IEncoding\n}\n\nfunc NewRectangle() *Rectangle {\n\treturn &Rectangle{}\n}\nfunc (that *Rectangle) String() string {\n\treturn fmt.Sprintf(\"X:%d,Y:%d,Width:%d,Height:%d,EncType:%s\", that.X, that.Y, that.Width, that.Height, that.EncType)\n}\n\n// 读取矩形数据\nfunc (that *Rectangle) Read(sess ISession) error {\n\tvar err error\n\n\t//读取x坐标\n\tif err = binary.Read(sess, binary.BigEndian, &that.X); err != nil {\n\t\treturn err\n\t}\n\t// 读取y坐标\n\tif err = binary.Read(sess, binary.BigEndian, &that.Y); err != nil {\n\t\treturn err\n\t}\n\t// 读取x坐标上的宽度\n\tif err = binary.Read(sess, binary.BigEndian, &that.Width); err != nil {\n\t\treturn err\n\t}\n\t// 读取y坐标上的高度\n\tif err = binary.Read(sess, binary.BigEndian, &that.Height); err != nil {\n\t\treturn err\n\t}\n\t// 读取编码类型\n\tif err = binary.Read(sess, binary.BigEndian, &that.EncType); err != nil {\n\t\treturn err\n\t}\n\tthat.Enc = sess.NewEncoding(that.EncType)\n\tif that.Enc == nil {\n\t\treturn fmt.Errorf(\"不支持的编码类型: %s\", that.EncType)\n\t}\n\treturn that.Enc.Read(sess, that)\n}\n\n// 写入矩形数据\nfunc (that *Rectangle) Write(sess ISession) error {\n\tvar err error\n\n\tif err = binary.Write(sess, binary.BigEndian, that.X); err != nil {\n\t\treturn err\n\t}\n\tif err = binary.Write(sess, binary.BigEndian, that.Y); err != nil {\n\t\treturn err\n\t}\n\tif err = binary.Write(sess, binary.BigEndian, that.Width); err != nil {\n\t\treturn err\n\t}\n\tif err = binary.Write(sess, binary.BigEndian, that.Height); err != nil {\n\t\treturn err\n\t}\n\tif err = binary.Write(sess, binary.BigEndian, that.EncType); err != nil {\n\t\treturn err\n\t}\n\n\t// 通过预定义的编码格式写入\n\treturn that.Enc.Write(sess, that)\n}\n\nfunc (that *Rectangle) Clone() *Rectangle {\n\tr := &Rectangle{\n\t\tX:       that.X,\n\t\tY:       that.Y,\n\t\tWidth:   that.Width,\n\t\tHeight:  that.Height,\n\t\tEncType: that.EncType,\n\t\tEnc:     that.Enc.Clone(true),\n\t}\n\treturn r\n}\n"
  },
  {
    "path": "rfb/security.go",
    "content": "package rfb\n\n// SecurityType 安全认证类型\ntype SecurityType uint8\n\n//go:generate stringer -type=SecurityType\n\nconst (\n\tSecTypeUnknown  SecurityType = SecurityType(0)  // 未知认证类型\n\tSecTypeNone     SecurityType = SecurityType(1)  // 不需要认证\n\tSecTypeVNC      SecurityType = SecurityType(2)  // vnc密码认证\n\tSecTypeTight    SecurityType = SecurityType(16) // tight vnc主导的认证模式\n\tSecTypeVeNCrypt SecurityType = SecurityType(19) // VeNCrypt 通用认证类型\n)\n\n// SecuritySubType 认证子类型\ntype SecuritySubType uint32\n\n//go:generate stringer -type=SecuritySubType\n\n// SecSubTypeUnknown 未知的子类型认证\nconst (\n\tSecSubTypeUnknown SecuritySubType = SecuritySubType(0)\n)\n\n// VeNCrypt 安全认证会有两种认证版本0.1和0.2\n// 以下表示0.1\nconst (\n\tSecSubTypeVeNCrypt01Unknown   SecuritySubType = SecuritySubType(0)\n\tSecSubTypeVeNCrypt01Plain     SecuritySubType = SecuritySubType(19)\n\tSecSubTypeVeNCrypt01TLSNone   SecuritySubType = SecuritySubType(20)\n\tSecSubTypeVeNCrypt01TLSVNC    SecuritySubType = SecuritySubType(21)\n\tSecSubTypeVeNCrypt01TLSPlain  SecuritySubType = SecuritySubType(22)\n\tSecSubTypeVeNCrypt01X509None  SecuritySubType = SecuritySubType(23)\n\tSecSubTypeVeNCrypt01X509VNC   SecuritySubType = SecuritySubType(24)\n\tSecSubTypeVeNCrypt01X509Plain SecuritySubType = SecuritySubType(25)\n)\n\n// 以下表示0.2版本的类型\nconst (\n\tSecSubTypeVeNCrypt02Unknown   SecuritySubType = SecuritySubType(0)\n\tSecSubTypeVeNCrypt02Plain     SecuritySubType = SecuritySubType(256)\n\tSecSubTypeVeNCrypt02TLSNone   SecuritySubType = SecuritySubType(257)\n\tSecSubTypeVeNCrypt02TLSVNC    SecuritySubType = SecuritySubType(258)\n\tSecSubTypeVeNCrypt02TLSPlain  SecuritySubType = SecuritySubType(259)\n\tSecSubTypeVeNCrypt02X509None  SecuritySubType = SecuritySubType(260)\n\tSecSubTypeVeNCrypt02X509VNC   SecuritySubType = SecuritySubType(261)\n\tSecSubTypeVeNCrypt02X509Plain SecuritySubType = SecuritySubType(262)\n)\n\n// ISecurityHandler 认证方式的接口\ntype ISecurityHandler interface {\n\tType() SecurityType\n\tSubType() SecuritySubType\n\tAuth(ISession) error\n}\n"
  },
  {
    "path": "rfb/securitysubtype_string.go",
    "content": "// Code generated by \"stringer -type=SecuritySubType\"; DO NOT EDIT.\n\npackage rfb\n\nimport \"strconv\"\n\nfunc _() {\n\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n\t// Re-run the stringer command to generate them again.\n\tvar x [1]struct{}\n\t_ = x[SecSubTypeUnknown-0]\n\t_ = x[SecSubTypeVeNCrypt01Unknown-0]\n\t_ = x[SecSubTypeVeNCrypt01Plain-19]\n\t_ = x[SecSubTypeVeNCrypt01TLSNone-20]\n\t_ = x[SecSubTypeVeNCrypt01TLSVNC-21]\n\t_ = x[SecSubTypeVeNCrypt01TLSPlain-22]\n\t_ = x[SecSubTypeVeNCrypt01X509None-23]\n\t_ = x[SecSubTypeVeNCrypt01X509VNC-24]\n\t_ = x[SecSubTypeVeNCrypt01X509Plain-25]\n\t_ = x[SecSubTypeVeNCrypt02Unknown-0]\n\t_ = x[SecSubTypeVeNCrypt02Plain-256]\n\t_ = x[SecSubTypeVeNCrypt02TLSNone-257]\n\t_ = x[SecSubTypeVeNCrypt02TLSVNC-258]\n\t_ = x[SecSubTypeVeNCrypt02TLSPlain-259]\n\t_ = x[SecSubTypeVeNCrypt02X509None-260]\n\t_ = x[SecSubTypeVeNCrypt02X509VNC-261]\n\t_ = x[SecSubTypeVeNCrypt02X509Plain-262]\n}\n\nconst (\n\t_SecuritySubType_name_0 = \"SecSubTypeUnknown\"\n\t_SecuritySubType_name_1 = \"SecSubTypeVeNCrypt01PlainSecSubTypeVeNCrypt01TLSNoneSecSubTypeVeNCrypt01TLSVNCSecSubTypeVeNCrypt01TLSPlainSecSubTypeVeNCrypt01X509NoneSecSubTypeVeNCrypt01X509VNCSecSubTypeVeNCrypt01X509Plain\"\n\t_SecuritySubType_name_2 = \"SecSubTypeVeNCrypt02PlainSecSubTypeVeNCrypt02TLSNoneSecSubTypeVeNCrypt02TLSVNCSecSubTypeVeNCrypt02TLSPlainSecSubTypeVeNCrypt02X509NoneSecSubTypeVeNCrypt02X509VNCSecSubTypeVeNCrypt02X509Plain\"\n)\n\nvar (\n\t_SecuritySubType_index_1 = [...]uint8{0, 25, 52, 78, 106, 134, 161, 190}\n\t_SecuritySubType_index_2 = [...]uint8{0, 25, 52, 78, 106, 134, 161, 190}\n)\n\nfunc (i SecuritySubType) String() string {\n\tswitch {\n\tcase i == 0:\n\t\treturn _SecuritySubType_name_0\n\tcase 19 <= i && i <= 25:\n\t\ti -= 19\n\t\treturn _SecuritySubType_name_1[_SecuritySubType_index_1[i]:_SecuritySubType_index_1[i+1]]\n\tcase 256 <= i && i <= 262:\n\t\ti -= 256\n\t\treturn _SecuritySubType_name_2[_SecuritySubType_index_2[i]:_SecuritySubType_index_2[i+1]]\n\tdefault:\n\t\treturn \"SecuritySubType(\" + strconv.FormatInt(int64(i), 10) + \")\"\n\t}\n}\n"
  },
  {
    "path": "rfb/securitytype_string.go",
    "content": "// Code generated by \"stringer -type=SecurityType\"; DO NOT EDIT.\n\npackage rfb\n\nimport \"strconv\"\n\nfunc _() {\n\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n\t// Re-run the stringer command to generate them again.\n\tvar x [1]struct{}\n\t_ = x[SecTypeUnknown-0]\n\t_ = x[SecTypeNone-1]\n\t_ = x[SecTypeVNC-2]\n\t_ = x[SecTypeTight-16]\n\t_ = x[SecTypeVeNCrypt-19]\n}\n\nconst (\n\t_SecurityType_name_0 = \"SecTypeUnknownSecTypeNoneSecTypeVNC\"\n\t_SecurityType_name_1 = \"SecTypeTight\"\n\t_SecurityType_name_2 = \"SecTypeVeNCrypt\"\n)\n\nvar (\n\t_SecurityType_index_0 = [...]uint8{0, 14, 25, 35}\n)\n\nfunc (i SecurityType) String() string {\n\tswitch {\n\tcase i <= 2:\n\t\treturn _SecurityType_name_0[_SecurityType_index_0[i]:_SecurityType_index_0[i+1]]\n\tcase i == 16:\n\t\treturn _SecurityType_name_1\n\tcase i == 19:\n\t\treturn _SecurityType_name_2\n\tdefault:\n\t\treturn \"SecurityType(\" + strconv.FormatInt(int64(i), 10) + \")\"\n\t}\n}\n"
  },
  {
    "path": "rfb/session.go",
    "content": "package rfb\n\nimport (\n\t\"github.com/gogf/gf/v2/container/gmap\"\n\t\"io\"\n)\n\n// ISession vnc连接的接口\ntype ISession interface {\n\tio.ReadWriteCloser\n\tConn() io.ReadWriteCloser\n\tStart()\n\tFlush() error          // 清空缓冲区\n\tWait() <-chan struct{} // 等待会话处理结束\n\tInit(...Option) error\n\tOptions() Options\n\tSetPixelFormat(PixelFormat)\n\tSetColorMap(ColorMap)\n\tSetWidth(uint16)\n\tSetHeight(uint16)\n\tSetDesktopName([]byte)\n\tProtocolVersion() string             // 获取当前的rfb协议\n\tSetProtocolVersion(string)           // 设置rfb协议\n\tSetSecurityHandler(ISecurityHandler) // 设置安全认证处理方法\n\tSecurityHandler() ISecurityHandler   // 获取当前安全认证的处理方法\n\tEncodings() []IEncoding              // 获取该会话支持的图像编码类型\n\tSetEncodings([]EncodingType) error   // 设置该链接支持的图像编码类型\n\tNewEncoding(EncodingType) IEncoding\n\tSwap() *gmap.Map // 获取会话的自定义存储数据\n\tType() SessionType\n}\n\ntype SessionType uint8\n\n//go:generate stringer -type=SessionType\n\nconst (\n\tClientSessionType   SessionType = 0\n\tServerSessionType   SessionType = 1\n\tRecorderSessionType SessionType = 2\n\tPlayerSessionType   SessionType = 3\n\tCanvasSessionType   SessionType = 4\n)\n"
  },
  {
    "path": "rfb/session_string.go",
    "content": "// Code generated by \"stringer -type=SessionType\"; DO NOT EDIT.\n\npackage rfb\n\nimport \"strconv\"\n\nfunc _() {\n\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n\t// Re-run the stringer command to generate them again.\n\tvar x [1]struct{}\n\t_ = x[ClientSessionType-0]\n\t_ = x[ServerSessionType-1]\n\t_ = x[RecorderSessionType-2]\n\t_ = x[PlayerSessionType-3]\n\t_ = x[CanvasSessionType-4]\n}\n\nconst _SessionType_name = \"ClientSessionTypeServerSessionTypeRecorderSessionTypePlayerSessionTypeCanvasSessionType\"\n\nvar _SessionType_index = [...]uint8{0, 17, 34, 53, 70, 87}\n\nfunc (i SessionType) String() string {\n\tif i >= SessionType(len(_SessionType_index)-1) {\n\t\treturn \"SessionType(\" + strconv.FormatInt(int64(i), 10) + \")\"\n\t}\n\treturn _SessionType_name[_SessionType_index[i]:_SessionType_index[i+1]]\n}\n"
  },
  {
    "path": "rfb/target_config.go",
    "content": "package rfb\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype TargetConfig struct {\n\tNetwork  string        // 网络协议\n\tTimeout  time.Duration // 超时时间\n\tHost     string        // vnc服务端地址\n\tPort     int           // vnc服务端端口\n\tPassword []byte        // vnc服务端密码\n}\n\nfunc (that TargetConfig) Addr() string {\n\treturn fmt.Sprintf(\"%s:%d\", that.Host, that.Port)\n}\n\nfunc (that TargetConfig) GetNetwork() string {\n\tif len(that.Network) == 0 {\n\t\treturn \"tcp\"\n\t}\n\treturn that.Network\n}\n\nfunc (that TargetConfig) GetTimeout() time.Duration {\n\tif that.Timeout == 0 {\n\t\treturn 10 * time.Second\n\t}\n\treturn that.Timeout\n}\n"
  },
  {
    "path": "security/security_none.go",
    "content": "package security\n\nimport \"github.com/vprix/vncproxy/rfb\"\n\n// ClientAuthNone vnc客户端认证\ntype ClientAuthNone struct{}\n\n// ServerAuthNone 服务端认证\ntype ServerAuthNone struct{}\n\nvar _ rfb.ISecurityHandler = new(ClientAuthNone)\nvar _ rfb.ISecurityHandler = new(ServerAuthNone)\n\nfunc (*ClientAuthNone) Type() rfb.SecurityType {\n\treturn rfb.SecTypeNone\n}\nfunc (*ClientAuthNone) SubType() rfb.SecuritySubType {\n\treturn rfb.SecSubTypeUnknown\n}\n\nfunc (*ClientAuthNone) Auth(rfb.ISession) error {\n\treturn nil\n}\n\nfunc (*ServerAuthNone) Type() rfb.SecurityType {\n\treturn rfb.SecTypeNone\n}\n\nfunc (*ServerAuthNone) SubType() rfb.SecuritySubType {\n\treturn rfb.SecSubTypeUnknown\n}\n\nfunc (*ServerAuthNone) Auth(rfb.ISession) error {\n\treturn nil\n}\n"
  },
  {
    "path": "security/security_tight.go",
    "content": "package security\n"
  },
  {
    "path": "security/security_vencryptplain.go",
    "content": "package security\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\ntype ClientAuthVeNCrypt02Plain struct {\n\tUsername []byte\n\tPassword []byte\n}\n\nfunc (*ClientAuthVeNCrypt02Plain) Type() rfb.SecurityType {\n\treturn rfb.SecTypeVeNCrypt\n}\n\nfunc (*ClientAuthVeNCrypt02Plain) SubType() rfb.SecuritySubType {\n\treturn rfb.SecSubTypeVeNCrypt02Plain\n}\n\nfunc (auth *ClientAuthVeNCrypt02Plain) Auth(session rfb.ISession) error {\n\t// 发送认证版本号\n\tif err := binary.Write(session, binary.BigEndian, []uint8{0, 2}); err != nil {\n\t\treturn err\n\t}\n\tif err := session.Flush(); err != nil {\n\t\treturn err\n\t}\n\tvar (\n\t\tmajor, minor uint8\n\t)\n\t// 对比版本号\n\tif err := binary.Read(session, binary.BigEndian, &major); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &minor); err != nil {\n\t\treturn err\n\t}\n\tres := uint8(1)\n\tif major == 0 && minor == 2 {\n\t\tres = uint8(0)\n\t}\n\tif err := binary.Write(session, binary.BigEndian, res); err != nil {\n\t\treturn err\n\t}\n\tif err := session.Flush(); err != nil {\n\t\treturn err\n\t}\n\t// 选择认证子类型,只支持 SecSubTypeVeNCrypt02Plain 用户名密码认证\n\tif err := binary.Write(session, binary.BigEndian, uint8(1)); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Write(session, binary.BigEndian, auth.SubType()); err != nil {\n\t\treturn err\n\t}\n\tif err := session.Flush(); err != nil {\n\t\treturn err\n\t}\n\tvar secType rfb.SecuritySubType\n\tif err := binary.Read(session, binary.BigEndian, &secType); err != nil {\n\t\treturn err\n\t}\n\t// 客户端选择的认证类型服务端不支持\n\tif secType != auth.SubType() {\n\t\tif err := binary.Write(session, binary.BigEndian, uint8(1)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := session.Flush(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn fmt.Errorf(\"invalid sectype\")\n\t}\n\t// 服务端未设置用户名密码认证数据\n\tif len(auth.Password) == 0 || len(auth.Username) == 0 {\n\t\treturn fmt.Errorf(\"Security Handshake failed; no username and/or password provided for VeNCryptAuth. \")\n\t}\n\tvar (\n\t\tuLength, pLength uint32\n\t)\n\t// 获取用户名和密码长度\n\tif err := binary.Read(session, binary.BigEndian, &uLength); err != nil {\n\t\treturn err\n\t}\n\tif err := binary.Read(session, binary.BigEndian, &pLength); err != nil {\n\t\treturn err\n\t}\n\n\t// 获取用户名和密码内容\n\tusername := make([]byte, uLength)\n\tpassword := make([]byte, pLength)\n\tif err := binary.Read(session, binary.BigEndian, &username); err != nil {\n\t\treturn err\n\t}\n\n\tif err := binary.Read(session, binary.BigEndian, &password); err != nil {\n\t\treturn err\n\t}\n\t// 对比用户名密码是否正确，如果不正确则报错\n\tif !bytes.Equal(auth.Username, username) || !bytes.Equal(auth.Password, password) {\n\t\treturn fmt.Errorf(\"invalid username/password\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "security/security_vnc.go",
    "content": "package security\n\nimport (\n\t\"bytes\"\n\t\"crypto/des\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/util/grand\"\n\t\"github.com/vprix/vncproxy/rfb\"\n)\n\n// ChallengeLen 随机认证串的长度\nconst ChallengeLen = 16\n\n// ServerAuthVNC vnc服务端使用vnc auth认证方式\ntype ServerAuthVNC struct {\n\tChallenge []byte\n\tPassword  []byte\n\tCrypted   []byte\n}\n\nvar _ rfb.ISecurityHandler = new(ServerAuthVNC)\nvar _ rfb.ISecurityHandler = new(ClientAuthVNC)\n\nfunc (*ServerAuthVNC) Type() rfb.SecurityType {\n\treturn rfb.SecTypeVNC\n}\nfunc (*ServerAuthVNC) SubType() rfb.SecuritySubType {\n\treturn rfb.SecSubTypeUnknown\n}\n\n// 写入随机字符串\nfunc (that *ServerAuthVNC) writeChallenge(session rfb.ISession) error {\n\tif err := binary.Write(session, binary.BigEndian, that.Challenge); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n\nfunc (that *ServerAuthVNC) ReadChallenge(session rfb.ISession) error {\n\tvar crypted [ChallengeLen]byte\n\tif err := binary.Read(session, binary.BigEndian, &crypted); err != nil {\n\t\treturn err\n\t}\n\tthat.Crypted = crypted[:]\n\treturn nil\n}\n\nfunc (that *ServerAuthVNC) Auth(session rfb.ISession) error {\n\n\tif len(that.Challenge) != ChallengeLen {\n\t\tthat.Challenge = grand.B(ChallengeLen)\n\t}\n\n\tif err := that.writeChallenge(session); err != nil {\n\t\treturn err\n\t}\n\tif err := that.ReadChallenge(session); err != nil {\n\t\treturn err\n\t}\n\t// 加密随机认证串，并把加密后的串与客户端穿过来的串进行对比，如果对比一致，则说明密码一致\n\tencrypted, err := AuthVNCEncode(that.Password, that.Challenge)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !bytes.Equal(encrypted, that.Crypted) {\n\t\treturn fmt.Errorf(\"密码错误\")\n\t}\n\treturn nil\n}\n\n// ClientAuthVNC vnc 客户端使用vnc auth认证方式\ntype ClientAuthVNC struct {\n\tChallenge []byte\n\tPassword  []byte\n}\n\nfunc (*ClientAuthVNC) Type() rfb.SecurityType {\n\treturn rfb.SecTypeVNC\n}\nfunc (*ClientAuthVNC) SubType() rfb.SecuritySubType {\n\treturn rfb.SecSubTypeUnknown\n}\n\nfunc (that *ClientAuthVNC) Auth(session rfb.ISession) error {\n\tif len(that.Password) == 0 {\n\t\treturn fmt.Errorf(\"安全认证失败，因为没有传入VNCAuth认证方式所用的密码\")\n\t}\n\tvar challenge [ChallengeLen]byte\n\tif err := binary.Read(session, binary.BigEndian, &challenge); err != nil {\n\t\treturn err\n\t}\n\t// 使用密码对认证串加密\n\tencrypted, err := AuthVNCEncode(that.Password, challenge[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\t// 发送加密后的认证串\n\tif err = binary.Write(session, binary.BigEndian, encrypted); err != nil {\n\t\treturn err\n\t}\n\treturn session.Flush()\n}\n\n// AuthVNCEncode 加密随机认证串\nfunc AuthVNCEncode(password []byte, challenge []byte) ([]byte, error) {\n\tif len(challenge) != ChallengeLen {\n\t\treturn nil, fmt.Errorf(\"随机认证串的长度不正确，正确的应该是16字节\")\n\t}\n\t// 截取密码的前八位，因为只有前八位才有用\n\tkey := make([]byte, 8)\n\tcopy(key, password)\n\n\t// 对密码的每个字节进行翻转\n\tfor i := range key {\n\t\tkey[i] = (key[i]&0x55)<<1 | (key[i]&0xAA)>>1 // Swap adjacent bits\n\t\tkey[i] = (key[i]&0x33)<<2 | (key[i]&0xCC)>>2 // Swap adjacent pairs\n\t\tkey[i] = (key[i]&0x0F)<<4 | (key[i]&0xF0)>>4 // Swap the 2 halves\n\t}\n\n\t// 使用密码对随即认证串进行加密\n\tcipher, err := des.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor i := 0; i < len(challenge); i += cipher.BlockSize() {\n\t\tcipher.Encrypt(challenge[i:i+cipher.BlockSize()], challenge[i:i+cipher.BlockSize()])\n\t}\n\n\treturn challenge, nil\n}\n"
  },
  {
    "path": "session/canvas.go",
    "content": "package session\n\nimport (\n\t\"github.com/gogf/gf/v2/container/gmap\"\n\t\"github.com/vprix/vncproxy/canvas\"\n\t\"github.com/vprix/vncproxy/encodings\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"io\"\n)\n\ntype CanvasSession struct {\n\tcanvas *canvas.VncCanvas\n\n\toptions   rfb.Options     // 客户端配置信息\n\tprotocol  string          //协议版本\n\tencodings []rfb.IEncoding // 支持的编码列\n\tswap      *gmap.Map\n}\n\n// NewCanvasSession 创建客户端会话\nfunc NewCanvasSession(opts ...rfb.Option) *CanvasSession {\n\tsess := &CanvasSession{\n\t\tswap: gmap.New(true),\n\t}\n\tsess.configure(opts...)\n\treturn sess\n}\n\n// Init 初始化参数\nfunc (that *CanvasSession) Init(opts ...rfb.Option) error {\n\tthat.configure(opts...)\n\treturn nil\n}\n\nfunc (that *CanvasSession) configure(opts ...rfb.Option) {\n\tfor _, o := range opts {\n\t\to(&that.options)\n\t}\n\tif that.options.PixelFormat.BPP == 0 {\n\t\tthat.options.PixelFormat = rfb.PixelFormat32bit\n\t}\n\tif that.options.QuitCh == nil {\n\t\tthat.options.QuitCh = make(chan struct{})\n\t}\n\tif that.options.ErrorCh == nil {\n\t\tthat.options.ErrorCh = make(chan error, 32)\n\t}\n\tif that.options.Input == nil {\n\t\tthat.options.Input = make(chan rfb.Message)\n\t}\n\tif that.options.Output == nil {\n\t\tthat.options.Output = make(chan rfb.Message)\n\t}\n\tif len(that.options.Handlers) == 0 {\n\t\tthat.options.Handlers = DefaultClientHandlers\n\t}\n\tif len(that.options.Messages) == 0 {\n\t\tthat.options.Messages = messages.DefaultServerMessages\n\t}\n\tif len(that.options.Encodings) == 0 {\n\t\tthat.options.Encodings = encodings.DefaultEncodings\n\t}\n}\n\nfunc (that *CanvasSession) Start() {\n\tthat.canvas = canvas.NewVncCanvas(int(that.options.Width), int(that.options.Height))\n\tthat.canvas.DrawCursor = that.options.DrawCursor\n}\n\n// Conn 获取会话底层的网络链接\nfunc (that *CanvasSession) Conn() io.ReadWriteCloser {\n\treturn that.canvas\n}\n\n// Options 获取配置信息\nfunc (that *CanvasSession) Options() rfb.Options {\n\treturn that.options\n}\n\n// ProtocolVersion 获取会话使用的协议版本\nfunc (that *CanvasSession) ProtocolVersion() string {\n\treturn that.protocol\n}\n\n// SetProtocolVersion 设置支持的协议版本\nfunc (that *CanvasSession) SetProtocolVersion(pv string) {\n\tthat.protocol = pv\n}\n\n// Encodings 获取当前支持的编码格式\nfunc (that *CanvasSession) Encodings() []rfb.IEncoding {\n\treturn that.encodings\n}\n\n// SetEncodings 设置编码格式\nfunc (that *CanvasSession) SetEncodings(encs []rfb.EncodingType) error {\n\n\tmsg := &messages.SetEncodings{\n\t\tEncNum:    uint16(len(encs)),\n\t\tEncodings: encs,\n\t}\n\t//if logger.IsDebug() {\n\t//\tlogger.Debugf(\"[Proxy客户端->VNC服务端] 消息类型:%s,消息内容:%s\", msg.Type(), msg.String())\n\t//}\n\treturn msg.Write(that)\n}\n\nfunc (that *CanvasSession) Flush() error {\n\treturn nil\n}\n\n// Wait 等待会话处理完成\nfunc (that *CanvasSession) Wait() <-chan struct{} {\n\treturn that.options.QuitCh\n}\n\n// SecurityHandler 返回安全认证处理方法\nfunc (that *CanvasSession) SecurityHandler() rfb.ISecurityHandler {\n\treturn nil\n}\n\n// SetSecurityHandler 设置安全认证处理方法\nfunc (that *CanvasSession) SetSecurityHandler(_ rfb.ISecurityHandler) {\n}\n\n// NewEncoding 通过编码类型判断是否支持编码对象\nfunc (that *CanvasSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding {\n\tfor _, enc := range that.encodings {\n\t\tif enc.Type() == typ && enc.Supported(that) {\n\t\t\treturn enc.Clone()\n\t\t}\n\t}\n\treturn nil\n}\n\n// Read 从链接中读取数据\nfunc (that *CanvasSession) Read(buf []byte) (int, error) {\n\treturn that.canvas.Read(buf)\n}\n\n// Write 写入数据到链接\nfunc (that *CanvasSession) Write(buf []byte) (int, error) {\n\treturn that.canvas.Write(buf)\n}\n\n// Close 关闭会话\nfunc (that *CanvasSession) Close() error {\n\tif that.options.QuitCh != nil {\n\t\tclose(that.options.QuitCh)\n\t\tthat.options.QuitCh = nil\n\t}\n\treturn that.canvas.Close()\n}\n\n// Swap session存储的临时变量\nfunc (that *CanvasSession) Swap() *gmap.Map {\n\treturn that.swap\n}\n\n// Type session类型\nfunc (that *CanvasSession) Type() rfb.SessionType {\n\treturn rfb.CanvasSessionType\n}\n\n// SetPixelFormat 设置像素格式\nfunc (that *CanvasSession) SetPixelFormat(pf rfb.PixelFormat) {\n\tthat.options.PixelFormat = pf\n}\n\n// SetColorMap 设置颜色地图\nfunc (that *CanvasSession) SetColorMap(cm rfb.ColorMap) {\n\tthat.options.ColorMap = cm\n}\n\n// SetWidth 设置桌面宽度\nfunc (that *CanvasSession) SetWidth(width uint16) {\n\tthat.options.Width = width\n}\n\n// SetHeight 设置桌面高度\nfunc (that *CanvasSession) SetHeight(height uint16) {\n\tthat.options.Height = height\n}\n\n// SetDesktopName 设置桌面名称\nfunc (that *CanvasSession) SetDesktopName(name []byte) {\n\tthat.options.DesktopName = name\n}\n"
  },
  {
    "path": "session/client.go",
    "content": "package session\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/container/gmap\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/encodings\"\n\t\"github.com/vprix/vncproxy/handler\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"io\"\n)\n\nvar (\n\tDefaultClientHandlers = []rfb.IHandler{\n\t\t&handler.ClientVersionHandler{},\n\t\t&handler.ClientSecurityHandler{},\n\t\t&handler.ClientClientInitHandler{},\n\t\t&handler.ClientServerInitHandler{},\n\t\t&handler.ClientMessageHandler{},\n\t}\n)\n\n// ClientSession proxy 客户端\ntype ClientSession struct {\n\tc  io.ReadWriteCloser // 网络链接\n\tbr *bufio.Reader\n\tbw *bufio.Writer\n\n\t// 客户端配置信息\n\toptions rfb.Options\n\n\t//协议版本\n\tprotocol string\n\t// 最终选择的安全认证方式\n\tsecurityHandler rfb.ISecurityHandler\n\n\t//交换区\n\tswap *gmap.Map\n}\n\nvar _ rfb.ISession = new(ClientSession)\n\n// NewClient 创建客户端会话\nfunc NewClient(opts ...rfb.Option) *ClientSession {\n\tsess := &ClientSession{\n\t\tswap: gmap.New(true),\n\t}\n\tsess.configure(opts...)\n\n\treturn sess\n}\n\n// Init 初始化参数\nfunc (that *ClientSession) Init(opts ...rfb.Option) error {\n\tthat.configure(opts...)\n\treturn nil\n}\n\nfunc (that *ClientSession) configure(opts ...rfb.Option) {\n\tfor _, o := range opts {\n\t\to(&that.options)\n\t}\n\tif that.options.PixelFormat.BPP == 0 {\n\t\tthat.options.PixelFormat = rfb.PixelFormat32bit\n\t}\n\tif that.options.QuitCh == nil {\n\t\tthat.options.QuitCh = make(chan struct{})\n\t}\n\tif that.options.ErrorCh == nil {\n\t\tthat.options.ErrorCh = make(chan error, 32)\n\t}\n\tif that.options.Input == nil {\n\t\tthat.options.Input = make(chan rfb.Message)\n\t}\n\tif that.options.Output == nil {\n\t\tthat.options.Output = make(chan rfb.Message)\n\t}\n\tif len(that.options.Handlers) == 0 {\n\t\tthat.options.Handlers = DefaultClientHandlers\n\t}\n\tif len(that.options.Messages) == 0 {\n\t\tthat.options.Messages = messages.DefaultServerMessages\n\t}\n\tif len(that.options.Encodings) == 0 {\n\t\tthat.options.Encodings = encodings.DefaultEncodings\n\t}\n}\n\nfunc (that *ClientSession) Start() {\n\tvar err error\n\tthat.c, err = that.options.GetConn(that)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tthat.br = bufio.NewReader(that.c)\n\tthat.bw = bufio.NewWriter(that.c)\n\n\tif len(that.options.Handlers) == 0 {\n\t\tthat.options.Handlers = DefaultClientHandlers\n\t}\n\tfor _, h := range that.options.Handlers {\n\t\tif err := h.Handle(that); err != nil {\n\t\t\tthat.options.ErrorCh <- fmt.Errorf(\"握手失败，请检查服务是否启动: %v\", err)\n\t\t\terr = that.Close()\n\t\t\tif err != nil {\n\t\t\t\tthat.options.ErrorCh <- fmt.Errorf(\"关闭client失败: %v\", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Conn 获取会话底层的网络链接\nfunc (that *ClientSession) Conn() io.ReadWriteCloser {\n\treturn that.c\n}\n\n// Options 获取配置信息\nfunc (that *ClientSession) Options() rfb.Options {\n\treturn that.options\n}\n\n// ProtocolVersion 获取会话使用的协议版本\nfunc (that *ClientSession) ProtocolVersion() string {\n\treturn that.protocol\n}\n\n// SetProtocolVersion 设置支持的协议版本\nfunc (that *ClientSession) SetProtocolVersion(pv string) {\n\tthat.protocol = pv\n}\n\n// Encodings 获取当前支持的编码格式\nfunc (that *ClientSession) Encodings() []rfb.IEncoding {\n\treturn that.options.Encodings\n}\n\n// SetEncodings 设置编码格式\nfunc (that *ClientSession) SetEncodings(encs []rfb.EncodingType) error {\n\n\tmsg := &messages.SetEncodings{\n\t\tEncNum:    uint16(len(encs)),\n\t\tEncodings: encs,\n\t}\n\tif logger.IsDebug() {\n\t\tlogger.Debugf(context.TODO(), \"[Proxy客户端->VNC服务端] 消息类型:%s,消息内容:%s\", rfb.ClientMessageType(msg.Type()), msg.String())\n\t}\n\treturn msg.Write(that)\n}\n\nfunc (that *ClientSession) Flush() error {\n\treturn that.bw.Flush()\n}\n\n// Wait 等待会话处理完成\nfunc (that *ClientSession) Wait() <-chan struct{} {\n\treturn that.options.QuitCh\n}\n\n// SecurityHandler 返回安全认证处理方法\nfunc (that *ClientSession) SecurityHandler() rfb.ISecurityHandler {\n\treturn that.securityHandler\n}\n\n// SetSecurityHandler 设置安全认证处理方法\nfunc (that *ClientSession) SetSecurityHandler(securityHandler rfb.ISecurityHandler) {\n\tthat.securityHandler = securityHandler\n}\n\n// NewEncoding 获取编码对象\nfunc (that *ClientSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding {\n\tfor _, enc := range that.options.Encodings {\n\t\tif enc.Type() == typ && enc.Supported(that) {\n\t\t\treturn enc.Clone()\n\t\t}\n\t}\n\treturn nil\n}\n\n// Read 从链接中读取数据\nfunc (that *ClientSession) Read(buf []byte) (int, error) {\n\treturn that.br.Read(buf)\n}\n\n// Write 写入数据到链接\nfunc (that *ClientSession) Write(buf []byte) (int, error) {\n\treturn that.bw.Write(buf)\n}\n\n// Close 关闭会话\nfunc (that *ClientSession) Close() error {\n\tif that.options.QuitCh != nil {\n\t\tthat.options.QuitCh <- struct{}{}\n\t}\n\treturn that.c.Close()\n}\n\n// Swap session存储的临时变量\nfunc (that *ClientSession) Swap() *gmap.Map {\n\treturn that.swap\n}\n\n// Type session类型\nfunc (that *ClientSession) Type() rfb.SessionType {\n\treturn rfb.ClientSessionType\n}\n\n// SetPixelFormat 设置像素格式\nfunc (that *ClientSession) SetPixelFormat(pf rfb.PixelFormat) {\n\tthat.options.PixelFormat = pf\n}\n\n// SetColorMap 设置颜色地图\nfunc (that *ClientSession) SetColorMap(cm rfb.ColorMap) {\n\tthat.options.ColorMap = cm\n}\n\n// SetWidth 设置桌面宽度\nfunc (that *ClientSession) SetWidth(width uint16) {\n\tthat.options.Width = width\n}\n\n// SetHeight 设置桌面高度\nfunc (that *ClientSession) SetHeight(height uint16) {\n\tthat.options.Height = height\n}\n\n// SetDesktopName 设置桌面名称\nfunc (that *ClientSession) SetDesktopName(name []byte) {\n\tthat.options.DesktopName = name\n}\n"
  },
  {
    "path": "session/player.go",
    "content": "package session\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"github.com/gogf/gf/v2/container/gmap\"\n\t\"github.com/vprix/vncproxy/encodings\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"io\"\n)\n\ntype PlayerSession struct {\n\tc  io.ReadWriteCloser\n\tbr *bufio.Reader\n\tbw *bufio.Writer\n\n\toptions         rfb.Options          // 配置信息\n\tprotocol        string               //协议版本\n\tsecurityHandler rfb.ISecurityHandler // 安全认证方式\n\n\tswap *gmap.Map\n}\n\nfunc NewPlayerSession(opts ...rfb.Option) *PlayerSession {\n\tsess := &PlayerSession{\n\t\tswap: gmap.New(true),\n\t}\n\tsess.configure(opts...)\n\treturn sess\n}\n\n// Init 初始化参数\nfunc (that *PlayerSession) Init(opts ...rfb.Option) error {\n\tthat.configure(opts...)\n\treturn nil\n}\n\nfunc (that *PlayerSession) configure(opts ...rfb.Option) {\n\tfor _, o := range opts {\n\t\to(&that.options)\n\t}\n\tif that.options.PixelFormat.BPP == 0 {\n\t\tthat.options.PixelFormat = rfb.PixelFormat32bit\n\t}\n\tif that.options.QuitCh == nil {\n\t\tthat.options.QuitCh = make(chan struct{})\n\t}\n\tif that.options.ErrorCh == nil {\n\t\tthat.options.ErrorCh = make(chan error, 32)\n\t}\n\tif that.options.Input == nil {\n\t\tthat.options.Input = make(chan rfb.Message)\n\t}\n\tif that.options.Output == nil {\n\t\tthat.options.Output = make(chan rfb.Message)\n\t}\n\tif len(that.options.Messages) == 0 {\n\t\tthat.options.Messages = messages.DefaultClientMessage\n\t}\n\tif len(that.options.Encodings) == 0 {\n\t\tthat.options.Encodings = encodings.DefaultEncodings\n\t}\n}\n\nfunc (that *PlayerSession) Start() {\n\tvar err error\n\tthat.c, err = that.options.GetConn(that)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\n\tthat.br = bufio.NewReader(that.c)\n\tthat.bw = bufio.NewWriter(that.c)\n\tversion := make([]byte, len(RBSVersion))\n\t_, err = that.br.Read(version)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\t// 读取rfb协议\n\tversion = make([]byte, len(rfb.ProtoVersion38))\n\t_, err = that.br.Read(version)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tthat.protocol = string(version)\n\tvar secTypeNone int32\n\terr = binary.Read(that.br, binary.BigEndian, &secTypeNone)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tvar fbWeight uint16\n\terr = binary.Read(that.br, binary.BigEndian, &fbWeight)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tthat.SetWidth(fbWeight)\n\n\tvar fbHeight uint16\n\terr = binary.Read(that.br, binary.BigEndian, &fbHeight)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tthat.SetHeight(fbHeight)\n\n\tvar pixelFormat rfb.PixelFormat\n\terr = binary.Read(that.br, binary.BigEndian, &pixelFormat)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tthat.SetPixelFormat(pixelFormat)\n\tvar desktopNameSize uint32\n\terr = binary.Read(that.br, binary.BigEndian, &desktopNameSize)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tdesktopName := make([]byte, desktopNameSize)\n\t_, err = that.Read(desktopName)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tthat.SetDesktopName(desktopName)\n\treturn\n}\n\n// Conn 获取会话底层的网络链接\nfunc (that *PlayerSession) Conn() io.ReadWriteCloser {\n\treturn that.c\n}\n\n// Options 获取配置信息\nfunc (that *PlayerSession) Options() rfb.Options {\n\treturn that.options\n}\n\n// ProtocolVersion 获取会话使用的协议版本\nfunc (that *PlayerSession) ProtocolVersion() string {\n\treturn that.protocol\n}\n\n// SetProtocolVersion 设置支持的协议版本\nfunc (that *PlayerSession) SetProtocolVersion(pv string) {\n\tthat.protocol = pv\n}\n\n// Encodings 获取当前支持的编码格式\nfunc (that *PlayerSession) Encodings() []rfb.IEncoding {\n\treturn that.options.Encodings\n}\n\n// SetEncodings 设置编码格式\nfunc (that *PlayerSession) SetEncodings(_ []rfb.EncodingType) error {\n\n\treturn nil\n}\n\nfunc (that *PlayerSession) Flush() error {\n\treturn that.bw.Flush()\n}\n\n// Wait 等待会话处理完成\nfunc (that *PlayerSession) Wait() <-chan struct{} {\n\treturn that.options.QuitCh\n}\n\n// SecurityHandler 返回安全认证处理方法\nfunc (that *PlayerSession) SecurityHandler() rfb.ISecurityHandler {\n\treturn nil\n}\n\n// SetSecurityHandler 设置安全认证处理方法\nfunc (that *PlayerSession) SetSecurityHandler(_ rfb.ISecurityHandler) {\n}\n\n// NewEncoding 通过编码类型判断是否支持编码对象\nfunc (that *PlayerSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding {\n\tfor _, enc := range that.options.Encodings {\n\t\tif enc.Type() == typ && enc.Supported(that) {\n\t\t\treturn enc.Clone()\n\t\t}\n\t}\n\treturn nil\n}\n\n// Read 从链接中读取数据\nfunc (that *PlayerSession) Read(buf []byte) (int, error) {\n\treturn that.br.Read(buf)\n}\n\n// Write 写入数据到链接\nfunc (that *PlayerSession) Write(buf []byte) (int, error) {\n\treturn that.bw.Write(buf)\n}\n\n// Close 关闭会话\nfunc (that *PlayerSession) Close() error {\n\tif that.options.QuitCh != nil {\n\t\tthat.options.QuitCh <- struct{}{}\n\t}\n\treturn that.c.Close()\n}\n\nfunc (that *PlayerSession) Swap() *gmap.Map {\n\treturn that.swap\n}\nfunc (that *PlayerSession) Type() rfb.SessionType {\n\treturn rfb.PlayerSessionType\n}\n\n// SetPixelFormat 设置像素格式\nfunc (that *PlayerSession) SetPixelFormat(pf rfb.PixelFormat) {\n\tthat.options.PixelFormat = pf\n}\n\n// SetColorMap 设置颜色地图\nfunc (that *PlayerSession) SetColorMap(cm rfb.ColorMap) {\n\tthat.options.ColorMap = cm\n}\n\n// SetWidth 设置桌面宽度\nfunc (that *PlayerSession) SetWidth(width uint16) {\n\tthat.options.Width = width\n}\n\n// SetHeight 设置桌面高度\nfunc (that *PlayerSession) SetHeight(height uint16) {\n\tthat.options.Height = height\n}\n\n// SetDesktopName 设置桌面名称\nfunc (that *PlayerSession) SetDesktopName(name []byte) {\n\tthat.options.DesktopName = name\n}\n"
  },
  {
    "path": "session/recorder.go",
    "content": "package session\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"github.com/gogf/gf/v2/container/gmap\"\n\t\"github.com/vprix/vncproxy/encodings\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"io\"\n)\n\nconst RBSVersion = \"RBS 001.001\\n\"\n\ntype RecorderSession struct {\n\tc  io.ReadWriteCloser\n\tbw *bufio.Writer\n\n\toptions  rfb.Options // 客户端配置信息\n\tprotocol string      //协议版本\n\n\tswap *gmap.Map\n}\n\nvar _ rfb.ISession = new(RecorderSession)\n\n// NewRecorder 创建客户端会话\nfunc NewRecorder(opts ...rfb.Option) *RecorderSession {\n\trecorder := &RecorderSession{\n\t\tswap: gmap.New(true),\n\t}\n\trecorder.configure(opts...)\n\treturn recorder\n}\n\n// Init 初始化参数\nfunc (that *RecorderSession) Init(opts ...rfb.Option) error {\n\tthat.configure(opts...)\n\treturn nil\n}\n\nfunc (that *RecorderSession) configure(opts ...rfb.Option) {\n\tfor _, o := range opts {\n\t\to(&that.options)\n\t}\n\tif that.options.PixelFormat.BPP == 0 {\n\t\tthat.options.PixelFormat = rfb.PixelFormat32bit\n\t}\n\tif that.options.QuitCh == nil {\n\t\tthat.options.QuitCh = make(chan struct{})\n\t}\n\tif that.options.ErrorCh == nil {\n\t\tthat.options.ErrorCh = make(chan error, 32)\n\t}\n\tif that.options.Input == nil {\n\t\tthat.options.Input = make(chan rfb.Message)\n\t}\n\tif that.options.Output == nil {\n\t\tthat.options.Output = make(chan rfb.Message)\n\t}\n\tif len(that.options.Handlers) == 0 {\n\t\tthat.options.Handlers = DefaultClientHandlers\n\t}\n\tif len(that.options.Messages) == 0 {\n\t\tthat.options.Messages = messages.DefaultServerMessages\n\t}\n\tif len(that.options.Encodings) == 0 {\n\t\tthat.options.Encodings = encodings.DefaultEncodings\n\t}\n}\nfunc (that *RecorderSession) Start() {\n\tvar err error\n\tthat.c, err = that.options.GetConn(that)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\n\tthat.bw = bufio.NewWriter(that.c)\n\t_, err = that.Write([]byte(RBSVersion))\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\t_, err = that.Write([]byte(that.ProtocolVersion()))\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\terr = binary.Write(that.bw, binary.BigEndian, int32(rfb.SecTypeNone))\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\terr = binary.Write(that.bw, binary.BigEndian, int16(that.options.Width))\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\terr = binary.Write(that.bw, binary.BigEndian, int16(that.options.Height))\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\terr = binary.Write(that.bw, binary.BigEndian, that.options.PixelFormat)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tnameSize := len(that.options.DesktopName)\n\terr = binary.Write(that.bw, binary.BigEndian, uint32(nameSize))\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\t_, err = that.Write(that.options.DesktopName)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\terr = that.Flush()\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\treturn\n}\n\n// Conn 获取会话底层的网络链接\nfunc (that *RecorderSession) Conn() io.ReadWriteCloser {\n\treturn that.c\n}\n\n// Options 获取配置信息\nfunc (that *RecorderSession) Options() rfb.Options {\n\treturn that.options\n}\n\n// ProtocolVersion 获取会话使用的协议版本\nfunc (that *RecorderSession) ProtocolVersion() string {\n\treturn that.protocol\n}\n\n// SetProtocolVersion 设置支持的协议版本\nfunc (that *RecorderSession) SetProtocolVersion(pv string) {\n\tthat.protocol = pv\n}\n\n// Encodings 获取当前支持的编码格式\nfunc (that *RecorderSession) Encodings() []rfb.IEncoding {\n\treturn that.options.Encodings\n}\n\n// SetEncodings 设置编码格式\nfunc (that *RecorderSession) SetEncodings(_ []rfb.EncodingType) error {\n\treturn nil\n}\n\nfunc (that *RecorderSession) Flush() error {\n\treturn that.bw.Flush()\n}\n\n// Wait 等待会话处理完成\nfunc (that *RecorderSession) Wait() <-chan struct{} {\n\treturn that.options.QuitCh\n}\n\n// SecurityHandler 返回安全认证处理方法\nfunc (that *RecorderSession) SecurityHandler() rfb.ISecurityHandler {\n\treturn nil\n}\n\n// SetSecurityHandler 设置安全认证处理方法\nfunc (that *RecorderSession) SetSecurityHandler(_ rfb.ISecurityHandler) {\n}\n\n// NewEncoding 通过编码类型判断是否支持编码对象\nfunc (that *RecorderSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding {\n\tfor _, enc := range that.options.Encodings {\n\t\tif enc.Type() == typ && enc.Supported(that) {\n\t\t\treturn enc.Clone()\n\t\t}\n\t}\n\treturn nil\n}\n\n// Read 从链接中读取数据\nfunc (that *RecorderSession) Read(_ []byte) (int, error) {\n\treturn 0, nil\n}\n\n// Write 写入数据到链接\nfunc (that *RecorderSession) Write(buf []byte) (int, error) {\n\treturn that.bw.Write(buf)\n}\n\n// Close 关闭会话\nfunc (that *RecorderSession) Close() error {\n\tif that.options.QuitCh != nil {\n\t\tthat.options.QuitCh <- struct{}{}\n\t}\n\treturn that.c.Close()\n}\n\n// Swap session存储的临时变量\nfunc (that *RecorderSession) Swap() *gmap.Map {\n\treturn that.swap\n}\n\n// Type session类型\nfunc (that *RecorderSession) Type() rfb.SessionType {\n\treturn rfb.RecorderSessionType\n}\n\n// SetPixelFormat 设置像素格式\nfunc (that *RecorderSession) SetPixelFormat(pf rfb.PixelFormat) {\n\tthat.options.PixelFormat = pf\n}\n\n// SetColorMap 设置颜色地图\nfunc (that *RecorderSession) SetColorMap(cm rfb.ColorMap) {\n\tthat.options.ColorMap = cm\n}\n\n// SetWidth 设置桌面宽度\nfunc (that *RecorderSession) SetWidth(width uint16) {\n\tthat.options.Width = width\n}\n\n// SetHeight 设置桌面高度\nfunc (that *RecorderSession) SetHeight(height uint16) {\n\tthat.options.Height = height\n}\n\n// SetDesktopName 设置桌面名称\nfunc (that *RecorderSession) SetDesktopName(name []byte) {\n\tthat.options.DesktopName = name\n}\n"
  },
  {
    "path": "session/server.go",
    "content": "package session\n\nimport (\n\t\"bufio\"\n\t\"github.com/gogf/gf/v2/container/gmap\"\n\t\"github.com/vprix/vncproxy/encodings\"\n\t\"github.com/vprix/vncproxy/handler\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"io\"\n)\n\nvar (\n\tDefaultServerHandlers = []rfb.IHandler{\n\t\t&handler.ServerVersionHandler{},\n\t\t&handler.ServerSecurityHandler{},\n\t\t&handler.ServerClientInitHandler{},\n\t\t&handler.ServerServerInitHandler{},\n\t\t&handler.ServerMessageHandler{},\n\t}\n)\n\ntype ServerSession struct {\n\tc  io.ReadWriteCloser\n\tbr *bufio.Reader\n\tbw *bufio.Writer\n\n\toptions         rfb.Options          // 配置信息\n\tprotocol        string               //协议版本\n\tencodings       []rfb.IEncoding      // 支持的编码列\n\tsecurityHandler rfb.ISecurityHandler // 安全认证方式\n\n\tswap *gmap.Map\n}\n\nvar _ rfb.ISession = new(ServerSession)\n\nfunc NewServerSession(opts ...rfb.Option) *ServerSession {\n\tsess := &ServerSession{\n\t\tswap: gmap.New(true),\n\t}\n\tsess.configure(opts...)\n\n\treturn sess\n}\n\n// Init 初始化参数\nfunc (that *ServerSession) Init(opts ...rfb.Option) error {\n\tthat.configure(opts...)\n\treturn nil\n}\n\nfunc (that *ServerSession) configure(opts ...rfb.Option) {\n\tfor _, o := range opts {\n\t\to(&that.options)\n\t}\n\tif that.options.PixelFormat.BPP == 0 {\n\t\tthat.options.PixelFormat = rfb.PixelFormat32bit\n\t}\n\tif that.options.QuitCh == nil {\n\t\tthat.options.QuitCh = make(chan struct{})\n\t}\n\tif that.options.ErrorCh == nil {\n\t\tthat.options.ErrorCh = make(chan error, 32)\n\t}\n\tif that.options.Input == nil {\n\t\tthat.options.Input = make(chan rfb.Message)\n\t}\n\tif that.options.Output == nil {\n\t\tthat.options.Output = make(chan rfb.Message)\n\t}\n\tif len(that.options.Messages) == 0 {\n\t\tthat.options.Messages = messages.DefaultClientMessage\n\t}\n\tif len(that.options.Encodings) == 0 {\n\t\tthat.options.Encodings = encodings.DefaultEncodings\n\t}\n}\n\nfunc (that *ServerSession) Start() {\n\tvar err error\n\tthat.c, err = that.options.GetConn(that)\n\tif err != nil {\n\t\tthat.options.ErrorCh <- err\n\t\treturn\n\t}\n\tthat.br = bufio.NewReader(that.c)\n\tthat.bw = bufio.NewWriter(that.c)\n\n\tif len(that.options.Handlers) == 0 {\n\t\tthat.options.Handlers = DefaultServerHandlers\n\t}\n\tfor _, h := range that.options.Handlers {\n\t\tif err = h.Handle(that); err != nil {\n\t\t\tthat.options.ErrorCh <- err\n\t\t\terr = that.Close()\n\t\t\tif err != nil {\n\t\t\t\tthat.options.ErrorCh <- err\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\nfunc (that *ServerSession) Conn() io.ReadWriteCloser {\n\treturn that.c\n}\nfunc (that *ServerSession) Options() rfb.Options {\n\treturn that.options\n}\n\n// ProtocolVersion 获取会话使用的协议版本\nfunc (that *ServerSession) ProtocolVersion() string {\n\treturn that.protocol\n}\n\n// SetProtocolVersion 设置支持的协议版本\nfunc (that *ServerSession) SetProtocolVersion(pv string) {\n\tthat.protocol = pv\n}\n\n// Encodings 获取当前支持的编码格式\nfunc (that *ServerSession) Encodings() []rfb.IEncoding {\n\treturn that.encodings\n}\n\n// SetEncodings 设置编码格式\nfunc (that *ServerSession) SetEncodings(encs []rfb.EncodingType) error {\n\tes := make(map[rfb.EncodingType]rfb.IEncoding)\n\tfor _, enc := range that.options.Encodings {\n\t\tes[enc.Type()] = enc\n\t}\n\tfor _, encType := range encs {\n\t\tif enc, ok := es[encType]; ok {\n\t\t\tthat.encodings = append(that.encodings, enc)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (that *ServerSession) Flush() error {\n\treturn that.bw.Flush()\n}\n\n// Wait 等待会话处理完成\nfunc (that *ServerSession) Wait() <-chan struct{} {\n\treturn that.options.QuitCh\n}\n\n// SecurityHandler 返回安全认证处理方法\nfunc (that *ServerSession) SecurityHandler() rfb.ISecurityHandler {\n\treturn that.securityHandler\n}\n\n// SetSecurityHandler 设置安全认证处理方法\nfunc (that *ServerSession) SetSecurityHandler(securityHandler rfb.ISecurityHandler) {\n\tthat.securityHandler = securityHandler\n}\n\n// NewEncoding 通过编码类型判断是否支持编码对象\nfunc (that *ServerSession) NewEncoding(typ rfb.EncodingType) rfb.IEncoding {\n\tfor _, enc := range that.encodings {\n\t\tif enc.Type() == typ && enc.Supported(that) {\n\t\t\treturn enc.Clone()\n\t\t}\n\t}\n\treturn nil\n}\n\n// Read 从链接中读取数据\nfunc (that *ServerSession) Read(buf []byte) (int, error) {\n\treturn that.br.Read(buf)\n}\n\n// Write 写入数据到链接\nfunc (that *ServerSession) Write(buf []byte) (int, error) {\n\treturn that.bw.Write(buf)\n}\n\n// Close 关闭会话\nfunc (that *ServerSession) Close() error {\n\tif that.options.QuitCh != nil {\n\t\tthat.options.QuitCh <- struct{}{}\n\t}\n\treturn that.c.Close()\n}\n\n// Swap session存储的临时变量\nfunc (that *ServerSession) Swap() *gmap.Map {\n\treturn that.swap\n}\n\n// Type session类型\nfunc (that *ServerSession) Type() rfb.SessionType {\n\treturn rfb.ServerSessionType\n}\n\n// SetPixelFormat 设置像素格式\nfunc (that *ServerSession) SetPixelFormat(pf rfb.PixelFormat) {\n\tthat.options.PixelFormat = pf\n}\n\n// SetColorMap 设置颜色地图\nfunc (that *ServerSession) SetColorMap(cm rfb.ColorMap) {\n\tthat.options.ColorMap = cm\n}\n\n// SetWidth 设置桌面宽度\nfunc (that *ServerSession) SetWidth(width uint16) {\n\tthat.options.Width = width\n}\n\n// SetHeight 设置桌面高度\nfunc (that *ServerSession) SetHeight(height uint16) {\n\tthat.options.Height = height\n}\n\n// SetDesktopName 设置桌面名称\nfunc (that *ServerSession) SetDesktopName(name []byte) {\n\tthat.options.DesktopName = name\n}\n"
  },
  {
    "path": "vnc/player.go",
    "content": "package vnc\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/container/gtype\"\n\t\"github.com/gogf/gf/v2/os/gfile\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/handler\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/session\"\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype Player struct {\n\tsvrSession    *session.ServerSession // vnc客户端连接到proxy的会话\n\tplayerSession *session.PlayerSession\n\terrorCh       chan error\n\tclosed        *gtype.Bool\n\tsyncOnce      sync.Once\n}\n\nfunc NewPlayer(filePath string, svrSession *session.ServerSession) *Player {\n\tplayerSession := session.NewPlayerSession(\n\t\trfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\tif !gfile.Exists(filePath) {\n\t\t\t\treturn nil, fmt.Errorf(\"要读取的文件[%s]不存在\", filePath)\n\t\t\t}\n\t\t\treturn gfile.OpenFile(filePath, os.O_RDONLY, 0644)\n\t\t}),\n\t)\n\n\treturn &Player{\n\t\terrorCh:       make(chan error, 32),\n\t\tsvrSession:    svrSession,\n\t\tplayerSession: playerSession,\n\t\tclosed:        gtype.NewBool(false),\n\t}\n}\n\n// Start 启动\nfunc (that *Player) Start() error {\n\n\terr := that.svrSession.Init(rfb.OptHandlers([]rfb.IHandler{\n\t\t&handler.ServerVersionHandler{},\n\t\t&handler.ServerSecurityHandler{},\n\t\tthat, // 把链接到vnc服务端的逻辑加入\n\t\t&handler.ServerClientInitHandler{},\n\t\t&handler.ServerServerInitHandler{},\n\t\t&handler.ServerMessageHandler{},\n\t}...))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tthat.svrSession.Start()\n\terr = <-that.errorCh\n\treturn err\n}\n\n// Handle 建立远程链接\nfunc (that *Player) Handle(sess rfb.ISession) error {\n\tthat.playerSession.Start()\n\tthat.svrSession = sess.(*session.ServerSession)\n\tthat.svrSession.SetWidth(that.playerSession.Options().Width)\n\tthat.svrSession.SetHeight(that.playerSession.Options().Height)\n\tthat.svrSession.SetDesktopName(that.playerSession.Options().DesktopName)\n\tthat.svrSession.SetPixelFormat(that.playerSession.Options().PixelFormat)\n\n\tgo that.handleIO()\n\treturn nil\n}\n\nfunc (that *Player) handleIO() {\n\tfor that.closed.Val() == false {\n\t\tselect {\n\t\tcase <-that.svrSession.Wait():\n\t\t\treturn\n\t\tcase <-that.playerSession.Wait():\n\t\t\treturn\n\t\tcase err := <-that.svrSession.Options().ErrorCh:\n\t\t\tthat.errorCh <- err\n\t\t\tthat.Close()\n\t\tcase err := <-that.playerSession.Options().ErrorCh:\n\t\t\tthat.errorCh <- err\n\t\t\tthat.Close()\n\t\tcase msg := <-that.svrSession.Options().Output:\n\t\t\tif logger.IsDebug() {\n\t\t\t\tlogger.Debugf(context.TODO(), \"收到vnc客户端发送过来的消息,%s\", msg)\n\t\t\t}\n\t\t\tif msg.Type() == rfb.MessageType(rfb.FramebufferUpdateRequest) {\n\t\t\t\tthat.syncOnce.Do(func() {\n\t\t\t\t\tgo that.readRbs()\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (that *Player) readRbs() {\n\tfor that.closed.Val() == false {\n\t\t// 从会话中读取消息类型\n\t\tvar messageType rfb.ServerMessageType\n\t\tif err := binary.Read(that.playerSession, binary.BigEndian, &messageType); err != nil {\n\t\t\tthat.playerSession.Options().ErrorCh <- err\n\t\t\treturn\n\t\t}\n\t\tmsg := &messages.FramebufferUpdate{}\n\t\t// 读取消息内容\n\t\tparsedMsg, err := msg.Read(that.playerSession)\n\t\tif err != nil {\n\t\t\tthat.playerSession.Options().ErrorCh <- err\n\t\t\treturn\n\t\t}\n\t\tthat.svrSession.Options().Input <- parsedMsg\n\t\tvar sleep int64\n\t\t_ = binary.Read(that.playerSession, binary.BigEndian, &sleep)\n\t\tif sleep > 0 {\n\t\t\ttime.Sleep(time.Duration(sleep))\n\t\t}\n\t}\n}\n\nfunc (that *Player) Close() {\n\tthat.closed.Set(true)\n\t_ = that.svrSession.Close()\n\t_ = that.playerSession.Close()\n}\n"
  },
  {
    "path": "vnc/proxy.go",
    "content": "package vnc\n\nimport (\n\t\"github.com/gogf/gf/v2/container/gtype\"\n\t\"github.com/gogf/gf/v2/util/gconv\"\n\t\"github.com/vprix/vncproxy/handler\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/session\"\n)\n\ntype Proxy struct {\n\tremoteSession rfb.ISession // 链接到vnc远端服务的会话\n\tsvrSession    rfb.ISession // vnc客户端连接到proxy的会话\n\terrorCh       chan error\n\tclosed        *gtype.Bool\n}\n\n// NewVncProxy 生成vnc proxy服务对象\nfunc NewVncProxy(remoteSession *session.ClientSession, serverSession *session.ServerSession) *Proxy {\n\tvncProxy := &Proxy{\n\t\tsvrSession:    serverSession,\n\t\tremoteSession: remoteSession,\n\t\t// 这里选择8是随便选的,后期应该会改\n\t\terrorCh: make(chan error, 8),\n\t\tclosed:  gtype.NewBool(false),\n\t}\n\treturn vncProxy\n}\n\n// Start 启动\nfunc (that *Proxy) Start() error {\n\n\thds := []rfb.IHandler{\n\t\t&handler.ServerVersionHandler{},\n\t\t&handler.ServerSecurityHandler{},\n\t\tthat, // 把链接到vnc服务端的逻辑加入\n\t\t&handler.ServerClientInitHandler{},\n\t\t&handler.ServerServerInitHandler{},\n\t\t&handler.ServerMessageHandler{},\n\t}\n\terr := that.svrSession.Init(\n\t\trfb.OptHandlers(hds...),\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tthat.svrSession.Start()\n\terr = <-that.errorCh\n\treturn err\n}\n\nfunc (that *Proxy) handleIO() {\n\tfor that.closed.Val() == false {\n\t\tselect {\n\t\tcase msg := <-that.remoteSession.Options().ErrorCh:\n\t\t\t// 如果链接到vnc服务端的会话报错，则需要把链接到proxy的vnc客户端全部关闭\n\t\t\tthat.errorCh <- msg\n\t\t\t_ = that.svrSession.Close()\n\t\tcase msg := <-that.svrSession.Options().ErrorCh:\n\t\t\t//  链接到proxy的vnc客户端链接报错，则把错误转发给vnc proxy\n\t\t\tthat.errorCh <- msg\n\t\t\t_ = that.remoteSession.Close()\n\t\tcase msg := <-that.remoteSession.Options().Output:\n\t\t\t// 收到vnc服务端发送给proxy客户端的消息，转发给proxy服务端, proxy服务端内部会把该消息转发给vnc客户端\n\t\t\tsSessCfg := that.svrSession.Options()\n\t\t\tdisabled := false\n\t\t\t// 如果该消息禁用，则跳过不转发该消息\n\t\t\tfor _, t := range sSessCfg.DisableServerMessageType {\n\t\t\t\tif rfb.MessageType(t) == msg.Type() {\n\t\t\t\t\tdisabled = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !disabled {\n\t\t\t\tsSessCfg.Input <- msg\n\t\t\t}\n\t\tcase msg := <-that.svrSession.Options().Output:\n\t\t\t// vnc客户端发送消息到proxy服务端的时候,需要对消息进行检查\n\t\t\t// 有些消息不支持转发给vnc服务端\n\t\t\tswitch rfb.ClientMessageType(msg.Type()) {\n\t\t\tcase rfb.SetPixelFormat:\n\t\t\t\t// 发现是设置像素格式的消息，则忽略\n\t\t\t\tthat.remoteSession.SetPixelFormat(msg.(*messages.SetPixelFormat).PF)\n\t\t\t\tthat.remoteSession.Options().Input <- msg\n\t\t\t\tcontinue\n\t\t\tcase rfb.SetEncodings:\n\t\t\t\t// 设置编码格式的消息\n\t\t\t\tvar encTypes []rfb.EncodingType\n\t\t\t\t// 判断编码是否再支持的列表\n\t\t\t\tfor _, s := range that.remoteSession.Encodings() {\n\t\t\t\t\tfor _, cEnc := range msg.(*messages.SetEncodings).Encodings {\n\t\t\t\t\t\tif cEnc == s.Type() {\n\t\t\t\t\t\t\tencTypes = append(encTypes, s.Type())\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// 发送编码消息给vnc服务端\n\t\t\t\tthat.remoteSession.Options().Input <- &messages.SetEncodings{EncNum: gconv.Uint16(len(encTypes)), Encodings: encTypes}\n\t\t\tdefault:\n\t\t\t\tcliCfg := that.remoteSession.Options()\n\t\t\t\tdisabled := false\n\t\t\t\tfor _, t := range cliCfg.DisableClientMessageType {\n\t\t\t\t\tif rfb.MessageType(t) == msg.Type() {\n\t\t\t\t\t\tdisabled = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !disabled {\n\t\t\t\t\tthat.remoteSession.Options().Input <- msg\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tthat.errorCh <- nil\n}\n\n// Handle 建立远程链接\nfunc (that *Proxy) Handle(sess rfb.ISession) (err error) {\n\n\tthat.remoteSession.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tthat.svrSession = sess.(*session.ServerSession)\n\tthat.svrSession.SetWidth(that.remoteSession.Options().Width)\n\tthat.svrSession.SetHeight(that.remoteSession.Options().Height)\n\tdesktopName := that.remoteSession.Options().DesktopName\n\tif len(desktopName) <= 0 {\n\t\tdesktopName = []byte(\"vprix\")\n\t}\n\tthat.svrSession.SetDesktopName(desktopName)\n\tthat.svrSession.SetPixelFormat(that.remoteSession.Options().PixelFormat)\n\n\tgo that.handleIO()\n\treturn nil\n}\n\nfunc (that *Proxy) Close() {\n\tthat.closed.Set(true)\n\t_ = that.svrSession.Close()\n\t_ = that.remoteSession.Close()\n}\n"
  },
  {
    "path": "vnc/recorder.go",
    "content": "package vnc\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"github.com/gogf/gf/v2/container/gtype\"\n\t\"github.com/gogf/gf/v2/os/gtime\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/session\"\n)\n\ntype Recorder struct {\n\terrorCh         chan error\n\tclosed          *gtype.Bool\n\tcliSession      *session.ClientSession // 链接到vnc服务端的会话\n\trecorderSession *session.RecorderSession\n}\n\nfunc NewRecorder(recorderSess *session.RecorderSession, cliSession *session.ClientSession) *Recorder {\n\trecorder := &Recorder{\n\t\trecorderSession: recorderSess,\n\t\tcliSession:      cliSession,\n\t\terrorCh:         make(chan error, 32),\n\t\tclosed:          gtype.NewBool(false),\n\t}\n\treturn recorder\n}\n\nfunc (that *Recorder) Start() error {\n\tvar err error\n\tthat.cliSession.Start()\n\tencS := []rfb.EncodingType{\n\t\trfb.EncCursorPseudo,\n\t\trfb.EncPointerPosPseudo,\n\t\trfb.EncCopyRect,\n\t\trfb.EncZRLE,\n\t\trfb.EncHexTile,\n\t\trfb.EncZlib,\n\t\trfb.EncRRE,\n\t}\n\terr = that.cliSession.SetEncodings(encS)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// 设置参数信息\n\tthat.recorderSession.SetProtocolVersion(that.cliSession.ProtocolVersion())\n\tthat.recorderSession.SetWidth(that.cliSession.Options().Width)\n\tthat.recorderSession.SetHeight(that.cliSession.Options().Height)\n\tthat.recorderSession.SetPixelFormat(that.cliSession.Options().PixelFormat)\n\tthat.recorderSession.SetDesktopName(that.cliSession.Options().DesktopName)\n\tthat.recorderSession.Start()\n\treqMsg := messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}\n\terr = reqMsg.Write(that.cliSession)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar lastUpdate *gtime.Time\n\tfor {\n\t\tselect {\n\t\tcase msg := <-that.recorderSession.Options().Output:\n\t\t\tlogger.Debugf(context.TODO(), \"client message received.messageType:%d,message:%s\", msg.Type(), msg)\n\t\tcase msg := <-that.cliSession.Options().Output:\n\t\t\tif rfb.ServerMessageType(msg.Type()) == rfb.FramebufferUpdate {\n\t\t\t\terr = msg.Write(that.recorderSession)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif lastUpdate == nil {\n\t\t\t\t\t_ = binary.Write(that.recorderSession, binary.BigEndian, int64(0))\n\t\t\t\t} else {\n\t\t\t\t\tsecsPassed := gtime.Now().UnixNano() - lastUpdate.UnixNano()\n\t\t\t\t\t_ = binary.Write(that.recorderSession, binary.BigEndian, secsPassed)\n\t\t\t\t}\n\t\t\t\terr = that.recorderSession.Flush()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tlastUpdate = gtime.Now()\n\t\t\t\treqMsg = messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}\n\t\t\t\terr = reqMsg.Write(that.cliSession)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase <-that.cliSession.Wait():\n\t\t\treturn nil\n\t\tcase <-that.recorderSession.Wait():\n\t\t\treturn nil\n\t\tcase err = <-that.cliSession.Options().ErrorCh:\n\t\t\tthat.errorCh <- err\n\t\t\tthat.Close()\n\t\tcase err = <-that.recorderSession.Options().ErrorCh:\n\t\t\tthat.errorCh <- err\n\t\t\tthat.Close()\n\t\tcase err = <-that.errorCh:\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc (that *Recorder) Close() {\n\tthat.closed.Set(true)\n\t_ = that.cliSession.Close()\n\t_ = that.recorderSession.Close()\n}\n"
  },
  {
    "path": "vnc/screenshot.go",
    "content": "package vnc\n\nimport (\n\t\"fmt\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/security\"\n\t\"github.com/vprix/vncproxy/session\"\n\t\"golang.org/x/net/context\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\ntype Screenshot struct {\n\tcliSession    *session.ClientSession // 链接到vnc服务端的会话\n\tcanvasSession *session.CanvasSession\n\ttimeout       time.Duration\n}\n\nfunc NewScreenshot(targetCfg rfb.TargetConfig) *Screenshot {\n\tsecurityHandlers := []rfb.ISecurityHandler{\n\t\t&security.ClientAuthNone{},\n\t}\n\tif len(targetCfg.Password) > 0 {\n\t\tsecurityHandlers = []rfb.ISecurityHandler{\n\t\t\t&security.ClientAuthVNC{Password: targetCfg.Password},\n\t\t}\n\t}\n\tcanvasSession := session.NewCanvasSession()\n\tcliSession := session.NewClient(\n\t\trfb.OptSecurityHandlers(securityHandlers...),\n\t\trfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {\n\t\t\treturn net.DialTimeout(targetCfg.GetNetwork(), targetCfg.Addr(), targetCfg.GetTimeout())\n\t\t}),\n\t)\n\trecorder := &Screenshot{\n\t\tcanvasSession: canvasSession,\n\t\tcliSession:    cliSession,\n\t\ttimeout:       targetCfg.GetTimeout(),\n\t}\n\treturn recorder\n}\n\nfunc (that *Screenshot) GetImage() (io.ReadWriteCloser, error) {\n\tvar err error\n\tthat.cliSession.Start()\n\tencS := []rfb.EncodingType{\n\t\trfb.EncCursorPseudo,\n\t\trfb.EncPointerPosPseudo,\n\t\trfb.EncHexTile,\n\t\trfb.EncTight,\n\t\trfb.EncZRLE,\n\t}\n\tdefer func() {\n\t\t_ = that.cliSession.Close()\n\t}()\n\terr = that.cliSession.SetEncodings(encS)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// 设置参数信息\n\tthat.canvasSession.SetProtocolVersion(that.cliSession.ProtocolVersion())\n\tthat.canvasSession.SetWidth(that.cliSession.Options().Width)\n\tthat.canvasSession.SetHeight(that.cliSession.Options().Height)\n\tthat.canvasSession.SetPixelFormat(that.cliSession.Options().PixelFormat)\n\tthat.canvasSession.SetDesktopName(that.cliSession.Options().DesktopName)\n\tthat.canvasSession.Start()\n\tdefer func() {\n\t\t_ = that.canvasSession.Close()\n\t}()\n\treqMsg := messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}\n\terr = reqMsg.Write(that.cliSession)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tctx, cancel := context.WithTimeout(context.Background(), that.timeout)\n\tdefer cancel()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, fmt.Errorf(\"获取截图超时\")\n\t\tcase msg := <-that.cliSession.Options().Output:\n\t\t\tif rfb.ServerMessageType(msg.Type()) == rfb.FramebufferUpdate {\n\t\t\t\terr = msg.Write(that.canvasSession)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\terr = that.canvasSession.Flush()\n\t\t\t\treturn that.canvasSession.Conn(), err\n\t\t\t}\n\t\t\tif logger.IsDebug() {\n\t\t\t\tlogger.Debugf(context.TODO(), \"获取到来自vnc服务端的消息%v\", msg)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vnc/video.go",
    "content": "package vnc\n\nimport (\n\t\"context\"\n\t\"github.com/osgochina/dmicro/logger\"\n\t\"github.com/vprix/vncproxy/encodings\"\n\t\"github.com/vprix/vncproxy/messages\"\n\t\"github.com/vprix/vncproxy/rfb\"\n\t\"github.com/vprix/vncproxy/security\"\n\t\"github.com/vprix/vncproxy/session\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\ntype Video struct {\n\tcliCfg        *rfb.Options\n\ttargetCfg     rfb.TargetConfig\n\tcliSession    *session.ClientSession // 链接到vnc服务端的会话\n\tcanvasSession *session.CanvasSession\n}\n\nfunc NewVideo(cliCfg *rfb.Options, targetCfg rfb.TargetConfig) *Video {\n\tif cliCfg == nil {\n\t\tcliCfg = &rfb.Options{\n\t\t\tPixelFormat: rfb.PixelFormat32bit,\n\t\t\tMessages:    messages.DefaultServerMessages,\n\t\t\tEncodings:   encodings.DefaultEncodings,\n\t\t\tOutput:      make(chan rfb.Message),\n\t\t\tInput:       make(chan rfb.Message),\n\t\t\tErrorCh:     make(chan error),\n\t\t}\n\t}\n\tif cliCfg.Output == nil {\n\t\tcliCfg.Output = make(chan rfb.Message)\n\t}\n\tif cliCfg.Input == nil {\n\t\tcliCfg.Input = make(chan rfb.Message)\n\t}\n\tif cliCfg.ErrorCh == nil {\n\t\tcliCfg.ErrorCh = make(chan error)\n\t}\n\tif len(targetCfg.Password) > 0 {\n\t\tcliCfg.SecurityHandlers = []rfb.ISecurityHandler{\n\t\t\t&security.ClientAuthVNC{Password: targetCfg.Password},\n\t\t}\n\t} else {\n\t\tcliCfg.SecurityHandlers = []rfb.ISecurityHandler{\n\t\t\t&security.ClientAuthNone{},\n\t\t}\n\t}\n\trecorder := &Video{\n\t\t//canvasSession: session.NewCanvasSession(*cliCfg),\n\t\ttargetCfg: targetCfg,\n\t\tcliCfg:    cliCfg,\n\t}\n\treturn recorder\n}\n\nfunc (that *Video) Start() error {\n\tvar err error\n\ttimeout := 10 * time.Second\n\tif that.targetCfg.Timeout > 0 {\n\t\ttimeout = that.targetCfg.Timeout\n\t}\n\tnetwork := \"tcp\"\n\tif len(that.targetCfg.Network) > 0 {\n\t\tnetwork = that.targetCfg.Network\n\t}\n\tthat.cliCfg.GetConn = func(sess rfb.ISession) (io.ReadWriteCloser, error) {\n\t\treturn net.DialTimeout(network, that.targetCfg.Addr(), timeout)\n\t}\n\tthat.cliSession = session.NewClient()\n\n\tthat.cliSession.Start()\n\tencS := []rfb.EncodingType{\n\t\trfb.EncCursorPseudo,\n\t\trfb.EncPointerPosPseudo,\n\t\trfb.EncCopyRect,\n\t\trfb.EncTight,\n\t\trfb.EncZRLE,\n\t\trfb.EncHexTile,\n\t\trfb.EncZlib,\n\t\trfb.EncRRE,\n\t}\n\terr = that.cliSession.SetEncodings(encS)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// 设置参数信息\n\tthat.canvasSession.SetProtocolVersion(that.cliSession.ProtocolVersion())\n\tthat.canvasSession.SetWidth(that.cliSession.Options().Width)\n\tthat.canvasSession.SetHeight(that.cliSession.Options().Height)\n\tthat.canvasSession.SetPixelFormat(that.cliSession.Options().PixelFormat)\n\tthat.canvasSession.SetDesktopName(that.cliSession.Options().DesktopName)\n\tthat.canvasSession.Start()\n\treqMsg := messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}\n\terr = reqMsg.Write(that.cliSession)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor {\n\t\tselect {\n\t\tcase msg := <-that.cliCfg.Output:\n\t\t\tlogger.Debugf(context.TODO(), \"client message received.messageType:%d,message:%s\", msg.Type(), msg)\n\t\tcase msg := <-that.cliCfg.Input:\n\t\t\tif rfb.ServerMessageType(msg.Type()) == rfb.FramebufferUpdate {\n\t\t\t\terr = msg.Write(that.canvasSession)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = that.canvasSession.Flush()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treqMsg = messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}\n\t\t\t\terr = reqMsg.Write(that.cliSession)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase err = <-that.cliCfg.ErrorCh:\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc (that *Video) Close() {\n\t_ = that.cliSession.Close()\n\t_ = that.canvasSession.Close()\n\n}\n"
  }
]