├── .gitignore
├── LICENSE
├── README.md
├── bin
└── .keep
├── build
├── canvas
├── canvas.go
└── rgb_image.go
├── cmd
├── player
│ ├── main.go
│ ├── tcpServer.go
│ └── wsServer.go
├── proxy
│ ├── main.go
│ ├── tcpServer.go
│ └── wsServer.go
├── recorder
│ ├── main.go
│ └── recorder.go
├── screenshot
│ └── main.go
└── video
│ └── main.go
├── docs
├── .keep
├── .nojekyll
├── README.md
├── changelog.md
├── favicon.ico
├── images
│ ├── 5bb8dbe702ce04b0bdde8c26583b152.jpg
│ ├── rfc6143-glassory-1.png
│ ├── rfc6143-glassory-2.png
│ ├── rfc6143-glassory-3.png
│ ├── rfc6143-readme-1.png
│ └── rfc6143-transfer-pixel-format-1.png
├── index.html
├── logo.svg
├── overview.md
├── player
│ ├── .keep
│ └── README.md
├── proxy
│ ├── .keep
│ └── README.md
├── questions.md
├── recorder
│ ├── .keep
│ └── README.md
├── rfc6143
│ ├── GLOSSORY.md
│ ├── README.md
│ ├── handshake
│ │ ├── README.md
│ │ ├── initial.md
│ │ ├── protocol-version.md
│ │ └── security-type.md
│ └── transfer
│ │ ├── README.md
│ │ ├── display.md
│ │ ├── encoding
│ │ ├── README.md
│ │ ├── copy-rect.md
│ │ ├── raw.md
│ │ ├── rise-and-run-length.md
│ │ ├── set-encoding.md
│ │ ├── tight-png.md
│ │ ├── tiled-run-length.md
│ │ └── zlib-run-length.md
│ │ ├── input
│ │ ├── README.md
│ │ ├── clipboard.md
│ │ ├── keyboard.md
│ │ └── mouse.md
│ │ ├── pixel-format.md
│ │ └── set-color-map.md
├── screenshot
│ ├── .keep
│ └── README.md
├── summary.md
└── video
│ ├── .keep
│ └── README.md
├── encodings
├── default_encoding.go
├── encoding.go
├── encoding_copyrect.go
├── encoding_corre.go
├── encoding_h264.go
├── encoding_hextile.go
├── encoding_jpeg.go
├── encoding_jrle.go
├── encoding_raw.go
├── encoding_rre.go
├── encoding_tight.go
├── encoding_tightpng.go
├── encoding_trle.go
├── encoding_zlib.go
├── encoding_zrle.go
├── pseudo_cursor.go
├── pseudo_cursor_with_alpha.go
├── pseudo_desktop_name.go
├── pseudo_desktop_size.go
├── pseudo_extended_desktop_size.go
├── pseudo_fence.go
├── pseudo_last_rect.go
├── pseudo_led_state.go
├── pseudo_pointer_pos.go
└── pseudo_x_cursor.go
├── go.mod
├── handler
├── ClientClientInitHandler.go
├── ClientMessageHandler.go
├── ClientSecurityHandler.go
├── ClientServerInitHandler.go
├── ClientVersionHandler.go
├── ServerClientInitHandler.go
├── ServerMessageHandler.go
├── ServerSecurityHandler.go
├── ServerServerInitHandler.go
└── ServerVersionHandler.go
├── internal
├── dbuffer
│ ├── buffer.go
│ └── pool.go
└── syncPool
│ └── sync_pool.go
├── messages
├── clientClientCutText.go
├── clientClientFence.go
├── clientEnableContinuousUpdates.go
├── clientFramebufferUpdateRequest.go
├── clientKeyEvent.go
├── clientPointerEvent.go
├── clientQEMUExtKeyEvent.go
├── clientSetDesktopSize.go
├── clientSetEncodings.go
├── clientSetPixelFormat.go
├── default_message.go
├── serverBell.go
├── serverEndOfContinuousUpdates.go
├── serverFramebufferUpdate.go
├── serverInit.go
├── serverServerCutText.go
├── serverServerFence.go
└── serverSetColorMapEntries.go
├── rfb
├── color_map.go
├── desktop.go
├── encoding.go
├── encodingtype.go
├── encodingtype_string.go
├── handler.go
├── keys.go
├── keys_string.go
├── message.go
├── message_type_client_string.go
├── message_type_server_string.go
├── messagetype.go
├── options.go
├── pixel_format.go
├── rectangle.go
├── security.go
├── securitysubtype_string.go
├── securitytype_string.go
├── session.go
├── session_string.go
└── target_config.go
├── security
├── security_none.go
├── security_tight.go
├── security_vencryptplain.go
└── security_vnc.go
├── session
├── canvas.go
├── client.go
├── player.go
├── recorder.go
└── server.go
└── vnc
├── player.go
├── proxy.go
├── recorder.go
├── screenshot.go
└── video.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
--------------------------------------------------------------------------------
/bin/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/bin/.keep
--------------------------------------------------------------------------------
/build:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | export GOPROXY=https://proxy.golang.com.cn,direct
5 |
6 |
7 | help() {
8 | echo "使用方式:"
9 | echo " build.sh [-s app_name] [-v version] [-g go_bin]"
10 | echo "参数详解:"
11 | echo " app_name 需要编译的应用名称,选项: proxy,player,recorder,video,screenshot.默认是所有应用,多个应用可以逗号分割"
12 | echo " version 编译后的文件版本号,默认为当前git的commit id"
13 | echo " go_bin 使用的golang程序"
14 | exit
15 | }
16 |
17 | getOutFile(){
18 | build_name=$1
19 | output_dir=$2
20 | output_file="${output_dir}"/${build_name}
21 | }
22 |
23 |
24 |
25 | while getopts 's:v:g:h' OPT; do
26 | case $OPT in
27 | s) app_names="$OPTARG";;
28 | v) build_version="$OPTARG";;
29 | g) goBin="$OPTARG";;
30 | h) help;;
31 | ?) help;;
32 | esac
33 | done
34 |
35 |
36 | ## 获取当前环境
37 | ## shellcheck disable=SC2046
38 | cd $(dirname "$0")/ || exit 1;
39 |
40 |
41 | # 如果go bin 不存在,则去环境变量中查找
42 | if [ ! -x "$goBin" ]; then
43 | goBin=$(which go)
44 | fi
45 | if [ ! -x "$goBin" ]; then
46 | echo "No goBin found."
47 | exit 2
48 | fi
49 |
50 |
51 | # 编译时间
52 | build_date=$(date +"%Y-%m-%d %H:%M:%S")
53 | # 编译时候当前git的commit id
54 | build_git=$(git rev-parse --short HEAD)
55 | # 编译的golang版本
56 | go_version=$(${goBin} version)
57 | #编译版本
58 | if [ -z "$build_version" ]; then
59 | build_version="$build_git"
60 | fi
61 |
62 | if [ -z "$app_names" ]; then
63 | app_names="proxy,player,recorder,video,screenshot"
64 | fi
65 |
66 |
67 | echo "start to build project $app_names" "$build_date"
68 | # shellcheck disable=SC2154
69 | echo "$go_version"
70 | pwd
71 | root_dir="$(pwd)"
72 |
73 | ldflags=()
74 |
75 | # 链接时设置变量值
76 | ldflags+=("-X" "\"main.BuildVersion=${build_version}\"")
77 | ldflags+=("-X" "\"github.com/osgochina/dmicro/easyservice.BuildVersion=${build_version}\"")
78 | ldflags+=("-X" "\"github.com/osgochina/dmicro/easyservice.BuildGoVersion=${go_version}\"")
79 | ldflags+=("-X" "\"github.com/osgochina/dmicro/easyservice.BuildGitCommitId=${build_git}\"")
80 | ldflags+=("-X" "\"github.com/osgochina/dmicro/easyservice.BuildTime=${build_date}\"")
81 |
82 |
83 | for app_name in $(echo $app_names | sed "s/,/ /g")
84 | do
85 | getOutFile $app_name "$root_dir/bin"
86 | cd "$root_dir/cmd/$app_name/"
87 | echo "进入[$(pwd)]目录"
88 | ${goBin} build -v -ldflags "${ldflags[*]}" -o "${output_file}" || exit 1
89 | echo "build $app_name done."
90 | done
--------------------------------------------------------------------------------
/canvas/rgb_image.go:
--------------------------------------------------------------------------------
1 | package canvas
2 |
3 | import (
4 | "image"
5 | "image/color"
6 | )
7 |
8 | type RGBColor struct {
9 | R, G, B uint8
10 | }
11 |
12 | func (that RGBColor) RGBA() (r, g, b, a uint32) {
13 | return uint32(that.R), uint32(that.G), uint32(that.B), 1
14 | }
15 |
16 | type RGBImage struct {
17 | // Pix holds the image's pixels, in R, G, B, A order. The pixel at
18 | // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3].
19 | Pix []uint8
20 | // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
21 | Stride int
22 | // Rect is the image's bounds.
23 | Rect image.Rectangle
24 | }
25 |
26 | func (that RGBImage) ColorModel() color.Model {
27 | return nil
28 | }
29 |
30 | func (that RGBImage) Bounds() image.Rectangle {
31 | return that.Rect
32 | }
33 |
34 | func (that RGBImage) At(x, y int) color.Color {
35 | col := that.RGBAt(x, y)
36 | return color.RGBA{R: col.R, G: col.G, B: col.B, A: 1}
37 | }
38 |
39 | func (that *RGBImage) RGBAt(x, y int) *RGBColor {
40 | if !(image.Point{X: x, Y: y}.In(that.Rect)) {
41 | return &RGBColor{}
42 | }
43 | i := that.PixOffset(x, y)
44 | return &RGBColor{that.Pix[i+0], that.Pix[i+1], that.Pix[i+2]}
45 | }
46 |
47 | func (that *RGBImage) PixOffset(x, y int) int {
48 | return (y-that.Rect.Min.Y)*that.Stride + (x-that.Rect.Min.X)*3
49 | }
50 |
51 | func (that RGBImage) Set(x, y int, c color.Color) {
52 | if !(image.Point{X: x, Y: y}.In(that.Rect)) {
53 | return
54 | }
55 | i := that.PixOffset(x, y)
56 | c1 := color.RGBAModel.Convert(c).(color.RGBA)
57 | that.Pix[i+0] = c1.R
58 | that.Pix[i+1] = c1.G
59 | that.Pix[i+2] = c1.B
60 | }
61 |
62 | func (that *RGBImage) SetRGB(x, y int, c color.RGBA) {
63 | if !(image.Point{X: x, Y: y}.In(that.Rect)) {
64 | return
65 | }
66 | i := that.PixOffset(x, y)
67 | that.Pix[i+0] = c.R
68 | that.Pix[i+1] = c.G
69 | that.Pix[i+2] = c.B
70 | }
71 |
72 | func NewRGBImage(r image.Rectangle) *RGBImage {
73 | w, h := r.Dx(), r.Dy()
74 | buf := make([]uint8, 3*w*h)
75 | return &RGBImage{buf, 3 * w, r}
76 | }
77 |
--------------------------------------------------------------------------------
/cmd/player/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gogf/gf/v2/os/gcfg"
6 | "github.com/gogf/gf/v2/os/gfile"
7 | "github.com/gogf/gf/v2/text/gstr"
8 | "github.com/osgochina/dmicro/easyservice"
9 | "github.com/osgochina/dmicro/logger"
10 | "golang.org/x/net/context"
11 | "os"
12 | )
13 |
14 | var (
15 | helpContent = gstr.TrimLeft(`
16 | USAGE
17 | ./server [start|stop|quit] [tcpServer|wsServer] [OPTION]
18 | OPTION
19 | --rbsFile 使用的rbs文件地址 必传
20 | --tcpHost 本地监听的tcp协议地址 默认0.0.0.0
21 | --tcpPort 本地监听的tcp协议端口 默认8989
22 | --proxyPassword 连接到proxy的密码 不传入密码则使用auth none
23 | --wsHost 启动websocket服务的本地地址 默认 0.0.0.0
24 | --wsPort 启动websocket服务的本地端口 默认8988
25 | --wsPath 启动websocket服务的url path 默认'/'
26 | --debug 是否开启debug 默认debug=false
27 | -d,--daemon 使用守护进程模式启动
28 | --pid 设置pid文件的地址,默认是/tmp/[server].pid
29 | -h,--help 获取帮助信息
30 | -v,--version 获取编译版本信息
31 |
32 | EXAMPLES
33 | /path/to/server
34 | /path/to/server start --env=dev --debug=true --pid=/tmp/server.pid
35 | /path/to/server start -c=config.product.toml
36 | /path/to/server start tcpServer,wsServer --config=config.product.toml
37 | /path/to/server start wsServer --rbsFile=/path/to/foo.rbs
38 | --wsHost=0.0.0.0
39 | --wsPort=8988
40 | --proxyPassword=12345612
41 | --debug
42 | /path/to/server start tcpServer --rbsFile=/path/to/foo.rbs
43 | --tcpHost=0.0.0.0
44 | --tcpPort=8989
45 | --proxyPassword=12345612
46 | --debug
47 | /path/to/server stop
48 | /path/to/server quit
49 | /path/to/server reload
50 | /path/to/server version
51 | /path/to/server help
52 | `)
53 | )
54 |
55 | func main() {
56 | easyservice.Authors = "ClownFish"
57 | easyservice.SetHelpContent(helpContent)
58 | easyservice.SetOptions(
59 | map[string]bool{
60 | "tcpHost": true, //本地监听的tcp协议地址 默认0.0.0.0
61 | "tcpPort": true, //本地监听的tcp协议端口 默认8989
62 | "proxyPassword": true, //连接到proxy的密码 不传入密码则使用auth none
63 | "wsHost": true, //启动websocket服务的本地地址 默认 0.0.0.0
64 | "wsPort": true, //启动websocket服务的本地端口 默认8988
65 | "wsPath": true, //启动websocket服务的url path 默认'/'
66 | "rbsFile": true, // 使用的rbs文件地址 必传
67 | })
68 |
69 | easyservice.Setup(func(svr *easyservice.EasyService) {
70 | //注册服务停止时要执行法方法
71 | svr.BeforeStop(func(service *easyservice.EasyService) bool {
72 | fmt.Println("Vnc player server stop")
73 | return true
74 | })
75 | cfg := svr.Config()
76 | rbsFile := svr.CmdParser().GetOpt("rbsFile", "")
77 | if len(rbsFile.String()) <= 0 || !gfile.Exists(rbsFile.String()) {
78 | svr.Help()
79 | os.Exit(0)
80 | }
81 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("rbsFile", rbsFile.String())
82 |
83 | logger.SetDebug(cfg.MustGet(context.TODO(), "Debug").Bool())
84 |
85 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("tcpHost", svr.CmdParser().GetOpt("tcpHost", "0.0.0.0").String())
86 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("tcpPort", svr.CmdParser().GetOpt("tcpPort", 8989).Int())
87 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("proxyPassword", svr.CmdParser().GetOpt("proxyPassword", "").String())
88 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("wsHost", svr.CmdParser().GetOpt("wsHost", "0.0.0.0").String())
89 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("wsPort", svr.CmdParser().GetOpt("wsPort", 8988).Int())
90 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("wsPath", svr.CmdParser().GetOpt("wsPath", "/").String())
91 |
92 | if svr.SandboxNames().ContainsI("tcpserver") {
93 | svr.AddSandBox(NewTcpSandBox(cfg))
94 | return
95 | }
96 | if svr.SandboxNames().ContainsI("wsserver") {
97 | svr.AddSandBox(NewWSSandBox(cfg))
98 | return
99 | }
100 | svr.AddSandBox(NewTcpSandBox(cfg))
101 | svr.AddSandBox(NewWSSandBox(cfg))
102 | })
103 | }
104 |
--------------------------------------------------------------------------------
/cmd/player/tcpServer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gogf/gf/v2/os/gcfg"
6 | "github.com/gogf/gf/v2/os/glog"
7 | "github.com/osgochina/dmicro/drpc"
8 | "github.com/osgochina/dmicro/drpc/status"
9 | "github.com/osgochina/dmicro/easyservice"
10 | "github.com/vprix/vncproxy/rfb"
11 | "github.com/vprix/vncproxy/security"
12 | "github.com/vprix/vncproxy/session"
13 | "github.com/vprix/vncproxy/vnc"
14 | "golang.org/x/net/context"
15 | "io"
16 | "net"
17 | )
18 |
19 | // TcpSandBox Tcp的服务
20 | type TcpSandBox struct {
21 | id int
22 | name string
23 | cfg *gcfg.Config
24 | service *easyservice.EasyService
25 | lis net.Listener
26 | closed chan struct{}
27 | }
28 |
29 | // NewTcpSandBox 创建一个默认的服务沙盒
30 | func NewTcpSandBox(cfg *gcfg.Config) *TcpSandBox {
31 | id := easyservice.GetNextSandBoxId()
32 | sBox := &TcpSandBox{
33 | id: id,
34 | name: fmt.Sprintf("tcp_%d", id),
35 | cfg: cfg,
36 | closed: make(chan struct{}),
37 | }
38 | return sBox
39 | }
40 |
41 | func (that *TcpSandBox) ID() int {
42 | return that.id
43 | }
44 |
45 | func (that *TcpSandBox) Name() string {
46 | return that.name
47 | }
48 |
49 | func (that *TcpSandBox) Setup() error {
50 | var err error
51 | addr := fmt.Sprintf("%s:%d", that.cfg.MustGet(context.TODO(), "tcpHost"), that.cfg.MustGet(context.TODO(), "tcpPort"))
52 | that.lis, err = net.Listen("tcp", addr)
53 | if err != nil {
54 | glog.Fatalf(context.TODO(), "Error listen. %v", err)
55 | }
56 | fmt.Printf("Tcp proxy started! listening %s . vnc server %s:%d\n", that.lis.Addr().String(), that.cfg.MustGet(context.TODO(), "vncHost"), that.cfg.MustGet(context.TODO(), "vncPort"))
57 | securityHandlers := []rfb.ISecurityHandler{&security.ServerAuthNone{}}
58 | if len(that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()) > 0 {
59 | securityHandlers = append(securityHandlers, &security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()})
60 | }
61 | for {
62 | conn, err := that.lis.Accept()
63 | if err != nil {
64 | select {
65 | case <-that.closed:
66 | return drpc.ErrListenClosed
67 | default:
68 | }
69 | return err
70 | }
71 | go func(c net.Conn) {
72 | defer func() {
73 | //捕获错误,并且继续执行
74 | if p := recover(); p != nil {
75 | err = fmt.Errorf("panic:%v\n%s", p, status.PanicStackTrace())
76 | }
77 | }()
78 | svrSession := session.NewServerSession(
79 | rfb.OptSecurityHandlers(securityHandlers...),
80 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {
81 | return c, nil
82 | }),
83 | )
84 | play := vnc.NewPlayer(that.cfg.MustGet(context.TODO(), "rbsFile").String(), svrSession)
85 | err = play.Start()
86 | if err != nil {
87 | glog.Warning(context.TODO(), err)
88 | return
89 | }
90 | glog.Info(context.TODO(), "play finished")
91 | }(conn)
92 |
93 | }
94 | }
95 |
96 | func (that *TcpSandBox) Shutdown() error {
97 | close(that.closed)
98 | return that.lis.Close()
99 | }
100 |
101 | func (that *TcpSandBox) Service() *easyservice.EasyService {
102 | return that.service
103 | }
104 |
--------------------------------------------------------------------------------
/cmd/player/wsServer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gogf/gf/v2/frame/g"
6 | "github.com/gogf/gf/v2/net/ghttp"
7 | "github.com/gogf/gf/v2/os/gcfg"
8 | "github.com/gogf/gf/v2/os/glog"
9 | "github.com/osgochina/dmicro/easyservice"
10 | "github.com/vprix/vncproxy/rfb"
11 | "github.com/vprix/vncproxy/security"
12 | "github.com/vprix/vncproxy/session"
13 | "github.com/vprix/vncproxy/vnc"
14 | "golang.org/x/net/context"
15 | "golang.org/x/net/websocket"
16 | "io"
17 | )
18 |
19 | // WSSandBox Tcp的服务
20 | type WSSandBox struct {
21 | id int
22 | name string
23 | cfg *gcfg.Config
24 | service *easyservice.EasyService
25 | svr *ghttp.Server
26 | }
27 |
28 | // NewWSSandBox 创建一个默认的服务沙盒
29 | func NewWSSandBox(cfg *gcfg.Config) *WSSandBox {
30 | id := easyservice.GetNextSandBoxId()
31 | sBox := &WSSandBox{
32 | id: id,
33 | name: fmt.Sprintf("ws_%d", id),
34 | cfg: cfg,
35 | }
36 | return sBox
37 | }
38 |
39 | func (that *WSSandBox) ID() int {
40 | return that.id
41 | }
42 |
43 | func (that *WSSandBox) Name() string {
44 | return that.name
45 | }
46 |
47 | func (that *WSSandBox) Setup() error {
48 |
49 | that.svr = g.Server()
50 | that.svr.BindHandler(that.cfg.MustGet(context.TODO(), "wsPath", "/").String(), func(r *ghttp.Request) {
51 | h := websocket.Handler(func(conn *websocket.Conn) {
52 | conn.PayloadType = websocket.BinaryFrame
53 |
54 | securityHandlers := []rfb.ISecurityHandler{&security.ServerAuthNone{}}
55 | if len(that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()) > 0 {
56 | securityHandlers = []rfb.ISecurityHandler{&security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()}}
57 | }
58 | svrSession := session.NewServerSession(
59 | rfb.OptSecurityHandlers(securityHandlers...),
60 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {
61 | return conn, nil
62 | }),
63 | )
64 | play := vnc.NewPlayer(that.cfg.MustGet(context.TODO(), "rfbFile").String(), svrSession)
65 | err := play.Start()
66 | if err != nil {
67 | glog.Warning(context.TODO(), err)
68 | return
69 | }
70 | glog.Info(context.TODO(), "play session end")
71 | })
72 | h.ServeHTTP(r.Response.Writer, r.Request)
73 | })
74 | that.svr.SetAddr(fmt.Sprintf("%s:%d", that.cfg.MustGet(context.TODO(), "wsHost").String(), that.cfg.MustGet(context.TODO(), "wsPort").Int()))
75 | return that.svr.Start()
76 | }
77 |
78 | func (that *WSSandBox) Shutdown() error {
79 | return that.svr.Shutdown()
80 | }
81 |
82 | func (that *WSSandBox) Service() *easyservice.EasyService {
83 | return that.service
84 | }
85 |
--------------------------------------------------------------------------------
/cmd/proxy/tcpServer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gogf/gf/v2/container/gmap"
6 | "github.com/gogf/gf/v2/os/gcfg"
7 | "github.com/gogf/gf/v2/os/glog"
8 | "github.com/osgochina/dmicro/drpc"
9 | "github.com/osgochina/dmicro/drpc/status"
10 | "github.com/osgochina/dmicro/easyservice"
11 | "github.com/vprix/vncproxy/rfb"
12 | "github.com/vprix/vncproxy/security"
13 | "github.com/vprix/vncproxy/session"
14 | "github.com/vprix/vncproxy/vnc"
15 | "golang.org/x/net/context"
16 | "io"
17 | "net"
18 | "time"
19 | )
20 |
21 | // TcpSandBox Tcp的服务
22 | type TcpSandBox struct {
23 | id int
24 | name string
25 | cfg *gcfg.Config
26 | service *easyservice.EasyService
27 | lis net.Listener
28 | closed chan struct{}
29 | proxyHub *gmap.StrAnyMap
30 | }
31 |
32 | // NewTcpSandBox 创建一个默认的服务沙盒
33 | func NewTcpSandBox(cfg *gcfg.Config) *TcpSandBox {
34 | id := easyservice.GetNextSandBoxId()
35 | sBox := &TcpSandBox{
36 | id: id,
37 | name: fmt.Sprintf("tcp_%d", id),
38 | cfg: cfg,
39 | closed: make(chan struct{}),
40 | proxyHub: gmap.NewStrAnyMap(true),
41 | }
42 | return sBox
43 | }
44 |
45 | func (that *TcpSandBox) ID() int {
46 | return that.id
47 | }
48 |
49 | func (that *TcpSandBox) Name() string {
50 | return that.name
51 | }
52 |
53 | func (that *TcpSandBox) Setup() error {
54 | var err error
55 | addr := fmt.Sprintf("%s:%d", that.cfg.MustGet(context.TODO(), "tcpHost").String(), that.cfg.MustGet(context.TODO(), "tcpPort").Int())
56 | that.lis, err = net.Listen("tcp", addr)
57 | if err != nil {
58 | glog.Fatalf(context.TODO(), "Error listen. %v", err)
59 | }
60 | fmt.Printf("Tcp proxy started! listening %s . vnc server %s:%d\n", that.lis.Addr().String(), that.cfg.MustGet(context.TODO(), "vncHost").String(), that.cfg.MustGet(context.TODO(), "vncPort"))
61 | securityHandlers := []rfb.ISecurityHandler{
62 | &security.ServerAuthNone{},
63 | }
64 | if len(that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()) > 0 {
65 | securityHandlers = append(securityHandlers, &security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()})
66 | }
67 | targetCfg := rfb.TargetConfig{
68 | Host: that.cfg.MustGet(context.TODO(), "vncHost").String(),
69 | Port: that.cfg.MustGet(context.TODO(), "vncPort").Int(),
70 | Password: that.cfg.MustGet(context.TODO(), "vncPassword").Bytes(),
71 | }
72 | for {
73 | conn, err := that.lis.Accept()
74 | if err != nil {
75 | select {
76 | case <-that.closed:
77 | return drpc.ErrListenClosed
78 | default:
79 | }
80 | return err
81 | }
82 | go func(c net.Conn) {
83 | defer func() {
84 | //捕获错误,并且继续执行
85 | if p := recover(); p != nil {
86 | err = fmt.Errorf("panic:%v\n%s", p, status.PanicStackTrace())
87 | }
88 | }()
89 | svrSess := session.NewServerSession(
90 | rfb.OptDesktopName([]byte("Vprix VNC Proxy")),
91 | rfb.OptHeight(768),
92 | rfb.OptWidth(1024),
93 | rfb.OptSecurityHandlers(securityHandlers...),
94 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {
95 | return c, nil
96 | }),
97 | )
98 | timeout := 10 * time.Second
99 | network := "tcp"
100 | cliSess := session.NewClient(
101 | rfb.OptSecurityHandlers([]rfb.ISecurityHandler{&security.ClientAuthVNC{Password: targetCfg.Password}}...),
102 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {
103 | return net.DialTimeout(network, targetCfg.Addr(), timeout)
104 | }),
105 | )
106 | p := vnc.NewVncProxy(cliSess, svrSess)
107 | remoteKey := c.RemoteAddr().String()
108 | that.proxyHub.Set(remoteKey, p)
109 | err = p.Start()
110 | if err != nil {
111 | glog.Warning(context.TODO(), err)
112 | return
113 | }
114 | glog.Info(context.TODO(), "proxy session closed")
115 | }(conn)
116 |
117 | }
118 | }
119 |
120 | func (that *TcpSandBox) Shutdown() error {
121 | close(that.closed)
122 | return that.lis.Close()
123 | }
124 |
125 | func (that *TcpSandBox) Service() *easyservice.EasyService {
126 | return that.service
127 | }
128 |
--------------------------------------------------------------------------------
/cmd/proxy/wsServer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/gogf/gf/v2/container/gmap"
7 | "github.com/gogf/gf/v2/frame/g"
8 | "github.com/gogf/gf/v2/net/ghttp"
9 | "github.com/gogf/gf/v2/os/gcfg"
10 | "github.com/gogf/gf/v2/os/glog"
11 | "github.com/osgochina/dmicro/easyservice"
12 | "github.com/vprix/vncproxy/rfb"
13 | "github.com/vprix/vncproxy/security"
14 | "github.com/vprix/vncproxy/session"
15 | "github.com/vprix/vncproxy/vnc"
16 | "golang.org/x/net/websocket"
17 | "io"
18 | "net"
19 | )
20 |
21 | // WSSandBox Tcp的服务
22 | type WSSandBox struct {
23 | id int
24 | name string
25 | cfg *gcfg.Config
26 | service *easyservice.EasyService
27 | svr *ghttp.Server
28 | proxyHub *gmap.StrAnyMap
29 | }
30 |
31 | // NewWSSandBox 创建一个默认的服务沙盒
32 | func NewWSSandBox(cfg *gcfg.Config) *WSSandBox {
33 | id := easyservice.GetNextSandBoxId()
34 | sBox := &WSSandBox{
35 | id: id,
36 | name: fmt.Sprintf("ws_%d", id),
37 | cfg: cfg,
38 | proxyHub: gmap.NewStrAnyMap(true),
39 | }
40 | return sBox
41 | }
42 |
43 | func (that *WSSandBox) ID() int {
44 | return that.id
45 | }
46 |
47 | func (that *WSSandBox) Name() string {
48 | return that.name
49 | }
50 |
51 | func (that *WSSandBox) Setup() error {
52 |
53 | that.svr = g.Server()
54 | that.svr.BindHandler(that.cfg.MustGet(context.TODO(), "wsPath", "/").String(), func(r *ghttp.Request) {
55 | h := websocket.Handler(func(conn *websocket.Conn) {
56 | conn.PayloadType = websocket.BinaryFrame
57 | securityHandlers := []rfb.ISecurityHandler{
58 | &security.ServerAuthNone{},
59 | }
60 | if len(that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()) > 0 {
61 | securityHandlers = append(securityHandlers, &security.ServerAuthVNC{Password: that.cfg.MustGet(context.TODO(), "proxyPassword").Bytes()})
62 | }
63 | targetCfg := rfb.TargetConfig{
64 | Host: that.cfg.MustGet(context.TODO(), "vncHost").String(),
65 | Port: that.cfg.MustGet(context.TODO(), "vncPort").Int(),
66 | Password: that.cfg.MustGet(context.TODO(), "vncPassword").Bytes(),
67 | }
68 | var err error
69 | svrSess := session.NewServerSession(
70 | rfb.OptDesktopName([]byte("Vprix VNC Proxy")),
71 | rfb.OptHeight(768),
72 | rfb.OptWidth(1024),
73 | rfb.OptSecurityHandlers(securityHandlers...),
74 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {
75 | return conn, nil
76 | }),
77 | )
78 | cliSess := session.NewClient(
79 | rfb.OptSecurityHandlers([]rfb.ISecurityHandler{&security.ClientAuthVNC{Password: targetCfg.Password}}...),
80 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {
81 | return net.DialTimeout(targetCfg.GetNetwork(), targetCfg.Addr(), targetCfg.GetTimeout())
82 | }),
83 | )
84 | p := vnc.NewVncProxy(cliSess, svrSess)
85 | remoteKey := conn.RemoteAddr().String()
86 | that.proxyHub.Set(remoteKey, p)
87 | err = p.Start()
88 | if err != nil {
89 | glog.Warning(context.TODO(), err)
90 | return
91 | }
92 | glog.Info(context.TODO(), "proxy session end")
93 | })
94 | h.ServeHTTP(r.Response.Writer, r.Request)
95 | })
96 | that.svr.SetAddr(fmt.Sprintf("%s:%d", that.cfg.MustGet(context.TODO(), "wsHost").String(), that.cfg.MustGet(context.TODO(), "wsPort").Int()))
97 | return that.svr.Start()
98 | }
99 |
100 | func (that *WSSandBox) Shutdown() error {
101 | return that.svr.Shutdown()
102 | }
103 |
104 | func (that *WSSandBox) Service() *easyservice.EasyService {
105 | return that.service
106 | }
107 |
--------------------------------------------------------------------------------
/cmd/recorder/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gogf/gf/v2/os/gcfg"
6 | "github.com/gogf/gf/v2/text/gstr"
7 | "github.com/osgochina/dmicro/easyservice"
8 | "github.com/osgochina/dmicro/logger"
9 | "golang.org/x/net/context"
10 | "os"
11 | )
12 |
13 | var (
14 | helpContent = gstr.TrimLeft(`
15 | USAGE
16 | ./recorder [start|stop|quit] [OPTION]
17 | OPTION
18 | --rbsFile 使用的rbs文件地址 必传
19 | --vncHost 要连接的vnc服务端地址 必传
20 | --vncPort 要连接的vnc服务端端口 必传
21 | --vncPassword 要连接的vnc服务端密码 不传则使用auth none
22 | --debug 是否开启debug 默认debug=false
23 | -d,--daemon 使用守护进程模式启动
24 | --pid 设置pid文件的地址,默认是/tmp/[server].pid
25 | -h,--help 获取帮助信息
26 | -v,--version 获取编译版本信息
27 |
28 | EXAMPLES
29 | /path/to/recorder
30 | /path/to/recorder start --env=dev --debug=true --pid=/tmp/server.pid
31 | /path/to/recorder start -c=config.product.toml
32 | /path/to/recorder start --config=config.product.toml
33 | /path/to/recorder start --rbsFile=/path/to/foo.rbs
34 | --vncHost=192.168.1.2
35 | --vncPort=5901
36 | --vncPassword=vprix
37 | --debug
38 | /path/to/server stop
39 | /path/to/server quit
40 | /path/to/server reload
41 | /path/to/server version
42 | /path/to/server help
43 | `)
44 | )
45 |
46 | func main() {
47 | easyservice.Authors = "ClownFish"
48 | easyservice.SetHelpContent(helpContent)
49 | easyservice.SetOptions(
50 | map[string]bool{
51 | "rbsFile": true, // 使用的rbs文件地址 必传
52 | "vncHost": true, // 要连接的vnc服务端地址 必传
53 | "vncPort": true, // 要连接的vnc服务端端口 必传
54 | "vncPassword": true, // 要连接的vnc服务端密码 不传则使用auth none
55 | })
56 | easyservice.Setup(func(svr *easyservice.EasyService) {
57 | //注册服务停止时要执行法方法
58 | svr.BeforeStop(func(service *easyservice.EasyService) bool {
59 | fmt.Println("Vnc player server stop")
60 | return true
61 | })
62 | cfg := svr.Config()
63 | rbsFile := svr.CmdParser().GetOpt("rbsFile", "")
64 | if len(rbsFile.String()) <= 0 {
65 | svr.Help()
66 | os.Exit(0)
67 | }
68 | vncHost := svr.CmdParser().GetOpt("vncHost", "")
69 | if len(vncHost.String()) <= 0 {
70 | svr.Help()
71 | os.Exit(0)
72 | }
73 | vncPort := svr.CmdParser().GetOpt("vncPort", 0)
74 | if vncPort.Int() <= 0 {
75 | svr.Help()
76 | os.Exit(0)
77 | }
78 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("rbsFile", rbsFile.String())
79 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncHost", vncHost.String())
80 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPort", vncPort.Int())
81 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPassword", svr.CmdParser().GetOpt("vncPassword", ""))
82 |
83 | logger.SetDebug(cfg.MustGet(context.TODO(), "Debug").Bool())
84 |
85 | svr.AddSandBox(NewRecorderSandBox(cfg))
86 | })
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/cmd/recorder/recorder.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gogf/gf/v2/os/gcfg"
6 | "github.com/gogf/gf/v2/os/gfile"
7 | "github.com/gogf/gf/v2/os/gtime"
8 | "github.com/osgochina/dmicro/easyservice"
9 | "github.com/osgochina/dmicro/logger"
10 | "github.com/vprix/vncproxy/encodings"
11 | "github.com/vprix/vncproxy/messages"
12 | "github.com/vprix/vncproxy/rfb"
13 | "github.com/vprix/vncproxy/security"
14 | "github.com/vprix/vncproxy/session"
15 | "github.com/vprix/vncproxy/vnc"
16 | "golang.org/x/net/context"
17 | "io"
18 | "net"
19 | "os"
20 | "time"
21 | )
22 |
23 | // RecorderSandBox 记录服务
24 | type RecorderSandBox struct {
25 | id int
26 | name string
27 | cfg *gcfg.Config
28 | service *easyservice.EasyService
29 | recorder *vnc.Recorder
30 | closed chan struct{}
31 | }
32 |
33 | // NewRecorderSandBox 创建一个默认的服务沙盒
34 | func NewRecorderSandBox(cfg *gcfg.Config) *RecorderSandBox {
35 | id := easyservice.GetNextSandBoxId()
36 | sBox := &RecorderSandBox{
37 | id: id,
38 | name: fmt.Sprintf("tcp_%d", id),
39 | cfg: cfg,
40 | closed: make(chan struct{}),
41 | }
42 | return sBox
43 | }
44 |
45 | func (that *RecorderSandBox) ID() int {
46 | return that.id
47 | }
48 |
49 | func (that *RecorderSandBox) Name() string {
50 | return that.name
51 | }
52 |
53 | func (that *RecorderSandBox) Setup() error {
54 | saveFilePath := that.cfg.MustGet(context.TODO(), "rbsFile").String()
55 | targetCfg := rfb.TargetConfig{
56 | Network: "tcp",
57 | Host: that.cfg.MustGet(context.TODO(), "vncHost").String(),
58 | Port: that.cfg.MustGet(context.TODO(), "vncPort").Int(),
59 | Password: that.cfg.MustGet(context.TODO(), "vncPassword").Bytes(),
60 | Timeout: 10 * time.Second,
61 | }
62 | var securityHandlers = []rfb.ISecurityHandler{
63 | &security.ClientAuthNone{},
64 | }
65 | if len(targetCfg.Password) > 0 {
66 | securityHandlers = []rfb.ISecurityHandler{
67 | &security.ClientAuthVNC{Password: targetCfg.Password},
68 | }
69 | }
70 | // 创建会话
71 | recorderSess := session.NewRecorder(
72 | rfb.OptEncodings(encodings.DefaultEncodings...),
73 | rfb.OptMessages(messages.DefaultServerMessages...),
74 | rfb.OptPixelFormat(rfb.PixelFormat32bit),
75 | rfb.OptGetConn(func(iSession rfb.ISession) (io.ReadWriteCloser, error) {
76 | if gfile.Exists(saveFilePath) {
77 | saveFilePath = fmt.Sprintf("%s%s%s_%d%s",
78 | gfile.Dir(saveFilePath),
79 | gfile.Separator,
80 | gfile.Name(gfile.Basename(saveFilePath)),
81 | gtime.Now().Unix(),
82 | gfile.Ext(gfile.Basename(saveFilePath)),
83 | )
84 | }
85 | return gfile.OpenFile(saveFilePath, os.O_RDWR|os.O_CREATE, 0644)
86 | }),
87 | )
88 | cliSession := session.NewClient(
89 | rfb.OptEncodings(encodings.DefaultEncodings...),
90 | rfb.OptMessages(messages.DefaultServerMessages...),
91 | rfb.OptPixelFormat(rfb.PixelFormat32bit),
92 | rfb.OptGetConn(func(iSession rfb.ISession) (io.ReadWriteCloser, error) {
93 | return net.DialTimeout(targetCfg.Network, targetCfg.Addr(), targetCfg.Timeout)
94 | }),
95 | rfb.OptSecurityHandlers(securityHandlers...),
96 | )
97 | that.recorder = vnc.NewRecorder(recorderSess, cliSession)
98 | err := that.recorder.Start()
99 | if err != nil {
100 | logger.Fatal(context.TODO(), err)
101 | }
102 | return err
103 | }
104 |
105 | func (that *RecorderSandBox) Shutdown() error {
106 | close(that.closed)
107 | that.recorder.Close()
108 | return nil
109 | }
110 |
111 | func (that *RecorderSandBox) Service() *easyservice.EasyService {
112 | return that.service
113 | }
114 |
--------------------------------------------------------------------------------
/cmd/screenshot/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "fmt"
7 | "github.com/gogf/gf/v2/os/gcfg"
8 | "github.com/gogf/gf/v2/os/gfile"
9 | "github.com/gogf/gf/v2/text/gstr"
10 | "github.com/osgochina/dmicro/easyservice"
11 | "github.com/osgochina/dmicro/logger"
12 | "github.com/vprix/vncproxy/rfb"
13 | "github.com/vprix/vncproxy/vnc"
14 | "image/draw"
15 | "image/jpeg"
16 | "os"
17 | "time"
18 | )
19 |
20 | var (
21 | helpContent = gstr.TrimLeft(`
22 | USAGE
23 | ./server [start|stop|quit] [OPTION]
24 | OPTION
25 | --imageFile 要生成的截图地址,暂时只支持jpeg格式 必传
26 | --vncHost 要连接的vnc服务端地址 必传
27 | --vncPort 要连接的vnc服务端端口 必传
28 | --vncPassword 要连接的vnc服务端密码 不传则使用auth none
29 | --debug 是否开启debug 默认debug=false
30 | -h,--help 获取帮助信息
31 | -v,--version 获取编译版本信息
32 |
33 | EXAMPLES
34 | /path/to/server
35 | /path/to/server start --env=dev --debug=true
36 | /path/to/server start -c=config.product.toml
37 | /path/to/server start --config=config.product.toml
38 | /path/to/server start --imageFile=/path/to/foo.jpeg
39 | --vncHost=192.168.1.2
40 | --vncPort=5901
41 | --vncPassword=vprix
42 | --debug
43 | /path/to/server version
44 | /path/to/server help
45 | `)
46 | )
47 |
48 | func main() {
49 | easyservice.Authors = "ClownFish"
50 | easyservice.SetHelpContent(helpContent)
51 | easyservice.SetOptions(
52 | map[string]bool{
53 | "imageFile": true, // 要生成的截图地址,暂时只支持jpeg格式 必传
54 | "vncHost": true, // 要连接的vnc服务端地址 必传
55 | "vncPort": true, // 要连接的vnc服务端端口 必传
56 | "vncPassword": true, // 要连接的vnc服务端密码 不传则使用auth none
57 | })
58 |
59 | easyservice.Setup(func(svr *easyservice.EasyService) {
60 | //注册服务停止时要执行法方法
61 | svr.BeforeStop(func(service *easyservice.EasyService) bool {
62 | fmt.Println("Vnc player server stop")
63 | return true
64 | })
65 | cfg := svr.Config()
66 | rbsFile := svr.CmdParser().GetOpt("imageFile", "")
67 | if len(rbsFile.String()) <= 0 {
68 | svr.Help()
69 | os.Exit(0)
70 | }
71 | vncHost := svr.CmdParser().GetOpt("vncHost", "")
72 | if len(vncHost.String()) <= 0 {
73 | svr.Help()
74 | os.Exit(0)
75 | }
76 | vncPort := svr.CmdParser().GetOpt("vncPort", 0)
77 | if vncPort.Int() <= 0 {
78 | svr.Help()
79 | os.Exit(0)
80 | }
81 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("rbsFile", rbsFile.String())
82 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncHost", vncHost.String())
83 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPort", vncPort.Int())
84 | _ = cfg.GetAdapter().(*gcfg.AdapterFile).Set("vncPassword", svr.CmdParser().GetOpt("vncPassword", "").String())
85 |
86 | logger.SetDebug(cfg.MustGet(context.TODO(), "Debug").Bool())
87 |
88 | v := vnc.NewScreenshot(
89 | rfb.TargetConfig{
90 | Network: "tcp",
91 | Host: vncHost.String(),
92 | Port: vncPort.Int(),
93 | Password: svr.CmdParser().GetOpt("vncPassword", "").Bytes(),
94 | Timeout: 5 * time.Second,
95 | },
96 | )
97 | img, err := v.GetImage()
98 | if err != nil {
99 | logger.Fatal(context.TODO(), err)
100 | }
101 |
102 | j := &bytes.Buffer{}
103 | err = jpeg.Encode(j, img.(draw.Image), &jpeg.Options{Quality: 100})
104 | if err != nil {
105 | fmt.Println(err)
106 | }
107 | err = gfile.PutBytes(rbsFile.String(), j.Bytes())
108 | if err != nil {
109 | fmt.Println(err)
110 | }
111 | os.Exit(0)
112 | })
113 | }
114 |
--------------------------------------------------------------------------------
/cmd/video/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func main() {
4 | // 暂未实现
5 | //v := vnc.NewVideo(nil,
6 | // rfb.TargetConfig{
7 | // Network: "tcp",
8 | // Host: "127.0.0.1",
9 | // Port: 5901,
10 | // Password: []byte("@abc1234"),
11 | // Timeout: 10 * time.Second,
12 | // },
13 | //)
14 | //go func() {
15 | // err := v.Start()
16 | // if err != nil {
17 | // logger.Fatal(err)
18 | // }
19 | //}()
20 | //for {
21 | // err := <-v.Error()
22 | // logger.Error(err)
23 | //}
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/docs/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/.keep
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/changelog.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/changelog.md
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/images/5bb8dbe702ce04b0bdde8c26583b152.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/images/5bb8dbe702ce04b0bdde8c26583b152.jpg
--------------------------------------------------------------------------------
/docs/images/rfc6143-glassory-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/images/rfc6143-glassory-1.png
--------------------------------------------------------------------------------
/docs/images/rfc6143-glassory-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/images/rfc6143-glassory-2.png
--------------------------------------------------------------------------------
/docs/images/rfc6143-glassory-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/images/rfc6143-glassory-3.png
--------------------------------------------------------------------------------
/docs/images/rfc6143-readme-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/images/rfc6143-readme-1.png
--------------------------------------------------------------------------------
/docs/images/rfc6143-transfer-pixel-format-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/images/rfc6143-transfer-pixel-format-1.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | VncProxy - RFB协议 - 使用文档
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
20 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/docs/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
25 |
--------------------------------------------------------------------------------
/docs/overview.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/overview.md
--------------------------------------------------------------------------------
/docs/player/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/player/.keep
--------------------------------------------------------------------------------
/docs/player/README.md:
--------------------------------------------------------------------------------
1 | ## Player
2 |
3 | 代码路径在`./cmd/player`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译
4 |
5 | ### 获取帮助信息
6 | ```shell
7 | # 查看帮助信息
8 | $ ./player --help
9 |
10 | # 查看版本信息
11 | $ ./player version
12 | ```
13 |
14 | ### 启动Player Tcp服务
15 |
16 | ```shell
17 |
18 | # rbsFile 要保存的rbs文件路径(必填)
19 | # tcpHost 本地监听的tcp协议地址 默认0.0.0.0
20 | # tcpPort 本地监听的tcp协议端口 默认8989
21 | # proxyPassword 连接到proxy的密码 不传入密码则使用auth none
22 | # debug 使用debug模式启动服务
23 |
24 | $ ./player start tcpServer --rbsFile=/path/to/foo.rbs
25 | --tcpHost=0.0.0.0
26 | --tcpPort=8989
27 | --proxyPassword=12345612
28 | --debug
29 | ```
30 |
31 | ### 启动Player WS服务
32 |
33 | ```shell
34 |
35 | # rbsFile 要保存的rbs文件路径(必填)
36 | # wsHost 启动websocket服务的本地地址 默认 0.0.0.0
37 | # wsPort 启动websocket服务的本地端口 默认8988
38 | # wsPath 启动websocket服务的url path 默认'/'
39 | # proxyPassword 连接到proxy的密码 不传入密码则使用auth none
40 | # debug 使用debug模式启动服务
41 |
42 | $ ./player start wsServer --rbsFile=/path/to/foo.rbs
43 | --wsHost=0.0.0.0
44 | --wsPort=8989
45 | --wsPath=/
46 | --proxyPassword=12345612
47 | --debug
48 | ```
--------------------------------------------------------------------------------
/docs/proxy/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/proxy/.keep
--------------------------------------------------------------------------------
/docs/proxy/README.md:
--------------------------------------------------------------------------------
1 | ## Proxy
2 |
3 | 代码路径在`./cmd/proxy`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译
4 |
5 | ### 获取帮助信息
6 | ```shell
7 | # 查看帮助信息
8 | $ ./proxy --help
9 |
10 | # 查看版本信息
11 | $ ./proxy version
12 |
13 | ```
14 | ### 启动tcp服务
15 |
16 | ```shell
17 |
18 | # 启动tcp server接受vnc viewer的连接
19 | # vncHost vnc服务器host
20 | # vncPort vnc服务器port
21 | # vncPassword vnc服务器密码
22 | # tcpHost 本地监听的地址
23 | # tcpPort 本地监听的端口
24 | # proxyPassword vnc连接的密码
25 | # debug 使用debug模式启动服务
26 |
27 | $ ./proxy start tcpServer --vncHost=192.168.1.2 \
28 | --vncPort=5901 \
29 | --vncPassword=vprix \
30 | --tcpHost=0.0.0.0 \
31 | --tcpPort=8989 \
32 | --proxyPassword=12345612 \
33 | --debug
34 | ```
35 | ### 启动WebSocket服务
36 |
37 | ```shell
38 |
39 | # 启动ws server接受novnc的连接
40 | # vncHost vnc服务器host
41 | # vncPort vnc服务器port
42 | # vncPassword vnc服务器密码
43 | # wsHost 本地监听的地址
44 | # wsPort 本地监听的端口
45 | # wsPath websocket连接的地址
46 | # proxyPassword vnc连接的密码
47 | # debug 使用debug模式启动服务
48 |
49 | $ ./proxy start wsServer --vncHost=192.168.1.2 \
50 | --vncPort=5901 \
51 | --vncPassword=vprix \
52 | --wsHost=0.0.0.0 \
53 | --wsPort=8988 \
54 | --wsPath=/websockify \
55 | --proxyPassword=12345612 \
56 | --debug
57 | ```
--------------------------------------------------------------------------------
/docs/questions.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/questions.md
--------------------------------------------------------------------------------
/docs/recorder/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/recorder/.keep
--------------------------------------------------------------------------------
/docs/recorder/README.md:
--------------------------------------------------------------------------------
1 | ## Recorder
2 |
3 | 代码路径在`./cmd/recorder`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译
4 |
5 | ### 获取帮助信息
6 | ```shell
7 | # 查看帮助信息
8 | $ ./recorder --help
9 |
10 | # 查看版本信息
11 | $ ./recorder version
12 |
13 | ```
14 | ### 启动Recorder服务
15 |
16 | ```shell
17 |
18 | # rbsFile 要保存的rbs文件路径(必填)
19 | # vncHost vnc服务器host
20 | # vncPort vnc服务器port
21 | # vncPassword vnc服务器密码
22 | # debug 使用debug模式启动服务
23 |
24 | $ ./recorder start --rbsFile=/path/to/foo.rbs
25 | --vncHost=192.168.1.2
26 | --vncPort=5901
27 | --vncPassword=vprix
28 | --debug
29 | ```
--------------------------------------------------------------------------------
/docs/rfc6143/GLOSSORY.md:
--------------------------------------------------------------------------------
1 | # 帧缓冲
2 |
3 | 帧缓冲器也称为帧缓冲或者显存,是用来存储渲染数据的地方。帧缓冲的每一个存储单位对应一个像素,它是屏幕显示画面的直接映象,又称为位映射图(Bit Map)。
4 |
5 | 
6 |
7 | # 调色板
8 |
9 | 传统的帧缓冲器支持的色彩模式很广泛。受限于昂贵的内存,大多数早期的帧缓冲器使用的是1位、2位、4位或 8位的色深。小的色深导致不能产生完整的色彩范围。其解决方法是为帧缓冲器增加一个查找表(lookup table,LUT),把帧缓冲器中存储的每个“颜色”作为一个索引。这就是所谓的索引色(indexed color)模式。
10 |
11 | 
12 |
13 | # 游程编码
14 |
15 | 游程编码(run-length encoding,RLE)是一种比较简单的压缩算法,其基本思想是将重复且连续出现多次的字符使用(连续出现次数,某个字符)来描述。
16 |
17 | 举例来说,字符串"AAAABBBCCDEEEE",由4个A、3个B、2个C、1个D、4个E组成,游程编码将其压缩为4A3B2C1D4E,由14个字符转成10个字符,压缩比 71.4%。
18 |
19 | 游程编码的优点是将重复性高的数据压缩成小单位;若数据出现频率不高,压缩结果可能比原始数据大。例如:"ABCDE",压缩结果为"1A1B1C1D1E",由5个字符转成10个字符,压缩比 200%。
20 |
21 | 
22 |
--------------------------------------------------------------------------------
/docs/rfc6143/README.md:
--------------------------------------------------------------------------------
1 | # 远程帧缓冲协议
2 |
3 | 该文档原著为:https://github.com/vincenthcui/rfc6143
4 |
5 | ## RFB 协议
6 |
7 | RFB (Remote Framebuffer Protocol) 远程帧缓冲协议,是一种允许用户通过网络连接控制远端计算机的七层网络协议。
8 | 在 RFB 协议中,用户通过本地鼠标、键盘输入,经由远端计算机计算后,将图形用户界面(GUI)回传本地进行输出。
9 |
10 | 
11 |
12 | ### 协议特点
13 |
14 | 协议设计有以下几个特点:
15 |
16 | - 瘦客户端。客户端职责简单清晰,无状态
17 | - 运行在弱网络环境下
18 | - 跨操作系统兼容性
19 |
20 | ## 协议版本
21 |
22 | RFB 协议有三个公开版本,分别是 3.3、3.7和3.8,3.8 是稳定版本。
23 |
24 | | 版本 | 发布时间 | 协议差异 |
25 | |:-------------------:|:----------:|:---------:|
26 | | Version 3.3 | 1998-01 | 服务器单向认证 |
27 | | Version 3.7 | 2003-8-12 | 关闭连接时返回原因 |
28 | | Version 3.8 (Final) | 2010-11-26 | - |
29 |
30 | 三个版本只在协议的握手阶段和初始化阶段存在差异,在数据流交换阶段保持一致。
31 |
32 | ### 协议的拓展
33 |
34 | 第三方 VNC 服务端和客户端拓展了 3.8 版本协议,提供更多的认证方式,优化传输效率。
35 |
36 | - Tight
37 | - RealVNC
38 | - Ultra
39 | - VMWare
40 |
41 | ## RFB 的发展历史
42 |
43 | - 2002 年,AT&T 关闭其位于英国剑桥的 Olivetti 研究实验室。
44 | - 2002 年,VNC 技术发明者 Tristan Richardson 合伙成立 RealVNC 公司,向商业公司提供企业级远程访问软件。
45 | - 2003年8月12日,Richardson 公开 RFB 协议的 3.7 版本。
46 | - 2010年11月26日,发布稳定协议版本 v3.8。
47 |
48 | > RFB 是 IETF 公开的开源通信协议
49 | >
50 | > RFB® 和 VNC® 是 RealVNC 公司的注册商标。
51 |
52 | ## 公开协议版本及资料
53 |
54 | - [RFC 6143: The Remote Framebuffer Protocol (describes Version 3.8)](https://tools.ietf.org/html/rfc6143)
55 | - [The RFB Protocol - Community Version](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst)
56 | - [The RFB Protocol - Version 3.8 (2010-11-26)](https://web.archive.org/web/20160410055332/http://www.realvnc.com/docs/rfbproto.pdf)
57 | - [The RFB Protocol - Version 3.7 (2003-08-12)](https://web.archive.org/web/20040325204925/http://www.realvnc.com/docs/rfbproto.pdf)
58 |
--------------------------------------------------------------------------------
/docs/rfc6143/handshake/README.md:
--------------------------------------------------------------------------------
1 | # 握手
2 |
3 | RFB 协议有四个阶段:
4 |
5 | ```mermaid
6 | graph LR
7 | protocol["协议握手"]
8 | auth["认证"]
9 | initial["初始化"]
10 | transfer("数据交互")
11 | subgraph 握手
12 | protocol --> auth
13 | auth --> initial
14 | end
15 | initial --> transfer
16 | transfer --> transfer
17 | ```
18 |
19 | - [协议握手](/rfc6143/handshake/protocol-version.md):对协议版本达成共识
20 | - [认证](/rfc6143/handshake/security-type.md):认证客户端身份
21 | - [初始化](/rfc6143/handshake/initial.md):交换像素格式等背景数据
22 | - [数据交互](/rfc6143/transfer/README.md):传输交互事件,更新图像帧
23 |
--------------------------------------------------------------------------------
/docs/rfc6143/handshake/initial.md:
--------------------------------------------------------------------------------
1 | # 初始化
2 |
3 | 收 SecurityResult 后,客户端应当发送 [ClientInit](#客户端初始化) 数据包,收到后,服务端发送 [ServerInit](#服务端初始化) 包。
4 |
5 | ```mermaid
6 | sequenceDiagram
7 | participant Client
8 | participant Server
9 | Server->>Client: SecurityResult(success)
10 | Client->>Server: ClientInit
11 | Server->>Client: ServerInit
12 | ```
13 |
14 | ## 客户端初始化
15 |
16 | 客户端初始化需要声明是否的共享屏幕。
17 |
18 | ```
19 | +--------------+--------------+-------------+
20 | | No. of bytes | Type [Value] | Description |
21 | +--------------+--------------+-------------+
22 | | 1 | U8 | shared-flag |
23 | +--------------+--------------+-------------+
24 | ```
25 |
26 | - shared-flag: 是否与其他客户端共享连接。如果是 1,允许服务端保持/加入其他客户端的连接。如果是0,服务端应该主动断开与其他客户端的连接。
27 |
28 | ## 服务端初始化
29 |
30 | 收到 ClientInit 消息后,服务端发送 ServerInit 消息,声明帧缓冲区大小、像素格式以及桌面的名称。
31 |
32 | ```
33 | +--------------+--------------+------------------------------+
34 | | No. of bytes | Type [Value] | Description |
35 | +--------------+--------------+------------------------------+
36 | | 2 | U16 | framebuffer-width in pixels |
37 | | 2 | U16 | framebuffer-height in pixels |
38 | | 16 | PIXEL_FORMAT | server-pixel-format |
39 | | 4 | U32 | name-length |
40 | | name-length | U8 array | name-string |
41 | +--------------+--------------+------------------------------+
42 | ```
43 |
44 | - framebuffer-width in pixels: 屏幕宽度
45 | - framebuffer-height in pixels: 屏幕高度
46 | - server-pixel-format: 服务器默认像素格式
47 | - name-length/name-string: 桌面的名字
48 |
49 | 如果客户端无法响应服务端指定的 pixel-format,可以主动发起 [SetPixelFormat](/display/pixel-format.md#SetPixelFormat) 重新设置。
50 |
--------------------------------------------------------------------------------
/docs/rfc6143/handshake/protocol-version.md:
--------------------------------------------------------------------------------
1 |
2 | # 协议握手
3 |
4 | 协议握手对客户端和服务端通信过程使用的协议版本达成共识,有可能的情况下,对不同协议版本实现向前兼容。
5 |
6 | ## 握手过程
7 |
8 | 建立 TCP 连接后,服务器首先向客户端发送版本 X,收到 X 后,客户端向服务器发送不高于 X 的版本 Y。
9 |
10 | RFB 有三个公开可选版本 3.3/3.7/3.8。
11 |
12 | 部分客户端或浏览器变种可能会发送其他的协议版本,统一将非标协议认定为 3.3(协议认为未公开协议版本没有实现3.7/3.8 中引入的特殊握手流程)。
13 |
14 | ```mermaid
15 | sequenceDiagram
16 | participant Client
17 | participant Server
18 | Server->>Client: ProtocolVersion
19 | Client->>Server: ProtocolVersion
20 | ```
21 |
22 | ## 协议报文
23 |
24 | 协议消息由标识符、主次版本组成,其结构体如下:
25 |
26 | ```
27 | +--------------+--------------+--------------+
28 | | No. of bytes | Type [Value] | Description |
29 | +--------------+--------------+--------------+
30 | | 3 | U8 array | protocol |
31 | | 1 | U8 [32] | blank |
32 | | 3 | U8 array | major version|
33 | | 1 | U8 [42] | pot |
34 | | 3 | U8 array | minor version|
35 | +--------------+--------------+--------------+
36 | ```
37 |
38 | 对于 3.8 版本协议,其发送协议头部如下:
39 |
40 | ```
41 | RFB 003.008\n (hex 52 46 42 20 30 30 33 2e 30 30 38 0a)
42 | ```
43 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/README.md:
--------------------------------------------------------------------------------
1 | # 数据交互
2 |
3 | 数据交互阶段,客户端向服务端发送鼠标、键盘事件,或请求更新图像。
4 |
5 | ```mermaid
6 | graph LR
7 | A("鼠标事件")
8 | B("键盘事件")
9 | C("更新图像")
10 | ```
11 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/display.md:
--------------------------------------------------------------------------------
1 | # 显示协议
2 |
3 | 客户端可能处于弱网络环境,或只有较低性能的渲染设备。如果服务端不加限制的向客户端发送像素画面,很容易造成客户端卡死或网络堵塞。
4 |
5 | 在 RFB 协议中,当且仅当客户端主动请求显示数据时,服务端才会将 [FramebufferUpdate](#FramebufferUpdate) 发往客户端。响应 [FramebufferUpdateRequest](#FramebufferUpdateRequest) 往往需要返回多条 FramebufferUpdate。
6 |
7 |
8 | ```mermaid
9 | sequenceDiagram
10 | participant Client
11 | participant Server
12 | Client->>Server: FramebufferUpdateRequest
13 | loop no change
14 | Client->>Server: FramebufferUpdateRequest
15 |
16 | end
17 | Server->>Client: FramebufferUpdate
18 | Server->>Client: FramebufferUpdate
19 | Server->>Client: FramebufferUpdate
20 | ```
21 |
22 | ## FramebufferUpdateRequest
23 |
24 | FramebufferUpdateRequest 告知服务端,客户端希望得到指定区域的内容。
25 |
26 | ```
27 | +--------------+--------------+--------------+
28 | | No. of bytes | Type [Value] | Description |
29 | +--------------+--------------+--------------+
30 | | 1 | U8 [3] | message-type |
31 | | 1 | U8 | incremental |
32 | | 2 | U16 | x-position |
33 | | 2 | U16 | y-position |
34 | | 2 | U16 | width |
35 | | 2 | U16 | height |
36 | +--------------+--------------+--------------+
37 | ```
38 |
39 | - message-type: 消息类型,固定 `3`
40 | - incremental: 是否是增量请求。
41 | - x-position/y-position: 区域的起始坐标
42 | - width/height: 区域的长度和宽度
43 |
44 | incremental 通常为非 0 值,服务器只需要发有变化的图像信息。当客户端丢失了缓存的帧缓冲信息,或者刚建立连接,需要完整的图像信息时,将 incremental 置为 0,获取全量信息。
45 |
46 | ## FramebufferUpdate
47 |
48 | FramebufferUpdate 由一组矩形图像(rectangles of pixel)组成,客户端收到 FramebufferUpdate 消息后,将消息内的矩形填充到帧缓冲对应区域,完成图像展示。
49 |
50 | ```
51 | +--------------+--------------+----------------------+
52 | | No. of bytes | Type [Value] | Description |
53 | +--------------+--------------+----------------------+
54 | | 1 | U8 [0] | message-type |
55 | | 1 | | padding |
56 | | 2 | U16 | number-of-rectangles |
57 | +--------------+--------------+----------------------+
58 | ```
59 |
60 | - message-type: 消息类型,固定 0
61 | - number-of-rectangles: 矩形的数量
62 |
63 | ### FramebufferUpdateRectangle
64 |
65 | FramebufferUpdate 携带 `number-of-rectangles` 数量的矩形信息,每个矩形都有头部信息
66 |
67 | ```
68 | +--------------+--------------+---------------+
69 | | No. of bytes | Type [Value] | Description |
70 | +--------------+--------------+---------------+
71 | | 2 | U16 | x-position |
72 | | 2 | U16 | y-position |
73 | | 2 | U16 | width |
74 | | 2 | U16 | height |
75 | | 4 | S32 | encoding-type |
76 | +--------------+--------------+---------------+
77 | ```
78 |
79 | - x-position/y-position: 矩形起始坐标
80 | - width/height: 矩形宽度和高度
81 | - encoding-type: 编码类型
82 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/encoding/README.md:
--------------------------------------------------------------------------------
1 | # 编码
2 |
3 | ”编码“ 是像素数据的表达方式。在发往客户端前,服务器经常将发送的图形数据进行编码、压缩,提高图像传输效率。
4 |
5 | ```
6 | +--------+-----------------------------+
7 | | Number | Name |
8 | +--------+-----------------------------+
9 | | 0 | Raw |
10 | | 1 | CopyRect |
11 | | 2 | RRE |
12 | | 5 | Hextile |
13 | | 15 | TRLE |
14 | | 16 | ZRLE |
15 | | -239 | Cursor pseudo-encoding |
16 | | -223 | DesktopSize pseudo-encoding |
17 | +--------+-----------------------------+
18 | ```
19 |
20 | - [Raw](/rfc6143/transfer/encoding/raw.md): 原始位图编码,即不编码
21 | - [CopyRect](/rfc6143/transfer/encoding/copy-rect.md): 从帧缓冲复制
22 | - [RRE](/rfc6143/transfer/encoding/rise-and-run-length.md): rise-and-run-length 二维游程编码
23 | - Hextile: RRE 的变种,图块游程编码
24 | - [TRLE](/rfc6143/transfer/encoding/tiled-run-length.md): 图块游程编码
25 | - [ZRLE](/rfc6143/transfer/encoding/zlib-run-length.md): Zlib Run-Length Encoding,zlib 压缩的游程编码
26 | - Cursor pseudo-encoding: 鼠标指针伪编码
27 | - DesktopSize pseudo-encoding: 桌面分辨率伪编码
28 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/encoding/copy-rect.md:
--------------------------------------------------------------------------------
1 | # 镜像编码 Copy Rect
2 |
3 | CopyRect 指示客户端,从已有帧缓冲区域复制到新区域。这种编码常用于窗口拖动、页面滚动等场景。
4 |
5 | 报文只说明起始坐标,区域的长度和宽度由 [FramebufferUpdateRectangle](/rfc6143/transfer/display.md#FramebufferUpdateRectangle) 指定。
6 |
7 | ```
8 | +--------------+--------------+----------------+
9 | | No. of bytes | Type [Value] | Description |
10 | +--------------+--------------+----------------+
11 | | 2 | U16 | src-x-position |
12 | | 2 | U16 | src-y-position |
13 | +--------------+--------------+----------------+
14 | ```
15 |
16 | - src-x-position/src-y-position: 源图像的起点坐标
17 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/encoding/raw.md:
--------------------------------------------------------------------------------
1 | # Raw 原始编码
2 |
3 | Raw 是最简单也是最原始的编码,直接向客户端传递位图信息,不进行编码优化。
4 | 在 Raw 格式中,像素按从左到右,从上到下的顺序排列成一维数组。
5 |
6 | > 协议要求客户端必须支持 Raw 类型编码。
7 | >
8 | > 协议要求服务器必须传输 Raw 类型编码,除非客户端另有要求。
9 |
10 | ```
11 | +----------------------------+--------------+-------------+
12 | | No. of bytes | Type [Value] | Description |
13 | +----------------------------+--------------+-------------+
14 | | width*height*bytesPerPixel | PIXEL array | pixels |
15 | +----------------------------+--------------+-------------+
16 | ```
17 |
18 | - pixels: 像素数组
19 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/encoding/rise-and-run-length.md:
--------------------------------------------------------------------------------
1 | # 上升和游程编码 Rise-and-run-length
2 |
3 | RRE 是游程编码的二维变种。基本思想是将大的矩形拆分为子矩形,每个子矩形有单值的像素组成,所有小矩形的并集构成原始矩形区域。
4 |
5 | 编码由背景像素值 Vb 、计数 N ,以及 N 个子矩形列表组成。子矩形由元组 表示,其中 v 是像素值(v != Vb),x/y/w/h 表示子矩形相对主矩形的坐标,和大小。
6 |
7 | 绘制时,客户端先以背景像素值填充矩形,再绘制每个子矩形,叠加出原始图像。
8 |
9 | ```
10 | +---------------+--------------+-------------------------+
11 | | No. of bytes | Type [Value] | Description |
12 | +---------------+--------------+-------------------------+
13 | | 4 | U32 | number-of-subrectangles |
14 | | bytesPerPixel | PIXEL | background-pixel-value |
15 | +---------------+--------------+-------------------------+
16 | ```
17 |
18 | - number-of-subrectangles: 子矩形数量
19 | - background-pixel-value: 矩形背景色
20 |
21 | 对于子矩形
22 |
23 | ```
24 | +---------------+--------------+---------------------+
25 | | No. of bytes | Type [Value] | Description |
26 | +---------------+--------------+---------------------+
27 | | bytesPerPixel | PIXEL | subrect-pixel-value |
28 | | 2 | U16 | x-position |
29 | | 2 | U16 | y-position |
30 | | 2 | U16 | width |
31 | | 2 | U16 | height |
32 | +---------------+--------------+---------------------+
33 | ```
34 |
35 | - subrect-pixel-value: 子矩形色值
36 | - x-position/y-position: 与背景矩行的**相对位置**
37 | - width/height: 子矩形宽度和高度
38 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/encoding/set-encoding.md:
--------------------------------------------------------------------------------
1 | # 设置编码
2 |
3 | 客户端用 SetEncoding 消息告知服务端,接受哪些像素[编码](/rfc6143/transfer/encoding/README.md)。
4 |
5 | 除了用于解析像素的编码外,客户端可以发送伪编码,向服务端请求拓展功能。如果服务端不识别此编码,可以直接忽略。客户端在未收到服务端明确的”支持“回复前,应当默认服务端不支持伪编码。
6 |
7 | 数据结构如下:
8 |
9 | ```
10 | +-----------------------+--------------+---------------------+
11 | | No. of bytes | Type [Value] | Description |
12 | +-----------------------+--------------+---------------------+
13 | | 1 | U8 [2] | message-type |
14 | | 1 | | padding |
15 | | 2 | U16 | number-of-encodings |
16 | | 4*number-of-encodings | S32 array | encoding-types |
17 | +-----------------------+--------------+---------------------+
18 | ```
19 |
20 | - message-type: 消息类型,固定为 `2`
21 | - number-of-encodings: 编码数量
22 | - encoding-types: [编码标识符](/rfc6143/transfer/encoding/README.md)
23 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/encoding/zlib-run-length.md:
--------------------------------------------------------------------------------
1 | # zlib 压缩游程编码
2 |
3 | ZRLE 融合游程编码、Zlib 压缩算法、图块压缩算法对传输图像进行压缩。
4 |
5 | ZRLE 使用 Zlib 进行编码和解码,要求流数据严格有序。
6 |
7 | ```
8 | +--------------+--------------+-------------+
9 | | No. of bytes | Type [Value] | Description |
10 | +--------------+--------------+-------------+
11 | | 4 | U32 | length |
12 | | length | U8 array | zlibData |
13 | +--------------+--------------+-------------+
14 | ```
15 |
16 | - length: 流(stream)长度
17 | - zlibData: 流数据
18 |
19 | ZlibData 解压后,是从左到右,从上到下排列的图块数据。图块大小是 64x64,其他跟 TRLE 一致。对于长度和宽度不足的图形,其最后一排和最后一列的宽/高是实际尺寸,不做补齐。
20 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/input/README.md:
--------------------------------------------------------------------------------
1 | # 输入协议
2 |
3 | RFB 协议支持鼠标和键盘两种输入设备,同时支持剪贴板进行远程复制粘贴。
4 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/input/keyboard.md:
--------------------------------------------------------------------------------
1 | # 键盘事件
2 |
3 | 在正常理解中,键盘事件的处理应该是简单明了的。参考以下协议报文
4 |
5 | ```
6 | +--------------+--------------+--------------+
7 | | No. of bytes | Type [Value] | Description |
8 | +--------------+--------------+--------------+
9 | | 1 | U8 [4] | message-type |
10 | | 1 | U8 | down-flag |
11 | | 2 | | padding |
12 | | 4 | U32 | key |
13 | +--------------+--------------+--------------+
14 | ```
15 |
16 | - message-type: 固定为 `0x4`
17 | - down-flag: `1` 表示键位按下,`0` 表示弹起
18 | - pending: 对齐字节,方便解析
19 | - key: 表示具体的键位
20 |
21 | 其中 key 的值在 X 系统中有[明确定义](https://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.html#keysym_encoding)
22 |
23 | ```
24 | +-----------------+--------------------+
25 | | Key name | Keysym value (hex) |
26 | +-----------------+--------------------+
27 | | BackSpace | 0xff08 |
28 | | Tab | 0xff09 |
29 | | Return or Enter | 0xff0d |
30 | | Escape | 0xff1b |
31 | | Insert | 0xff63 |
32 | | Delete | 0xffff |
33 | | Home | 0xff50 |
34 | | End | 0xff57 |
35 | | Page Up | 0xff55 |
36 | | Page Down | 0xff56 |
37 | | Left | 0xff51 |
38 | | Up | 0xff52 |
39 | | Right | 0xff53 |
40 | | Down | 0xff54 |
41 | | F1 | 0xffbe |
42 | | F2 | 0xffbf |
43 | | F3 | 0xffc0 |
44 | | F4 | 0xffc1 |
45 | | ... | ... |
46 | | F12 | 0xffc9 |
47 | | Shift (left) | 0xffe1 |
48 | | Shift (right) | 0xffe2 |
49 | | Control (left) | 0xffe3 |
50 | | Control (right) | 0xffe4 |
51 | | Meta (left) | 0xffe7 |
52 | | Meta (right) | 0xffe8 |
53 | | Alt (left) | 0xffe9 |
54 | | Alt (right) | 0xffea |
55 | +-----------------+--------------------+
56 | ```
57 |
58 | ## 组合键
59 |
60 | 组合键指 `Ctrl + Alt + Del` 或 `Shift + 3` 等组合按键。受不同的操作系统、键盘布局影响,组合键是按键事件中容易发生歧义的一环。
61 |
62 | RFB 基本遵循以下规则:
63 |
64 | - 如果客户端 key 在 `keysym` 中存在,服务端应该遵循 `keysym` 的指示,尽可能的忽略客户端传递 `Shift`、`CpasLock` 等键位,在需要时,应该主动补充/忽略 `Shift` 等键位。例如,在 US 键盘布局中,`#` 需要按下 `Shift + 3`,但是在 UK 布局中不需要。这就意味着用户在输入 `#` 的时候不会输入 `Shift`。这种情况下,服务端应该主动模拟一个 `Shift` 状态,防止输入的键位是 `3`。同理,如果 key 输入的键位是 `A`,服务端统一要模拟一个 `Shift`,保证输入的是 `A` 而不是 `a`。
65 | - 如果客户端 key 在 `keysym` 中不存在(例如 `Ctrl + A`),服务端应该遵循客户端指示,客户端应该主动在 `A` 前发送 `Ctrl` 的按键。
66 | - 如果客户端通过 `Ctrl + Alt + Q` 来输入 `@`,客户端应该在发送 `Ctrl`/`Alt`/`@`后,主动发送`Ctrl`/`Alt`的弹起事件。
67 | - 对于 `BackTab`,常见的有三种实现,`ISO_Left_Tab` `BackTab` 和 `Shift + Tab`。RFB 协议优先使用 `Shift + Tab`,但对于其他的键位,服务端和客户端应当尽量提供兼容。
68 | - 优先使用 `ASCII` 而不是 `unicode`
69 | - 对于 `Ctrl + Alt + Del` 等无法被客户端操作系统拦截的按键(系统拦截有更高优先级),客户端应该提供操作按钮
70 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/input/mouse.md:
--------------------------------------------------------------------------------
1 | # 鼠标事件
2 |
3 | 鼠标指针即鼠标操作事件,分移动事件和点击事件两种。在 RFB 协议中,使用 PointerEvent 表示客户端触发一次鼠标事件。
4 |
5 | ## 协议报文
6 |
7 | 鼠标事件的报文如下
8 |
9 | ```
10 | +--------------+--------------+--------------+
11 | | No. of bytes | Type [Value] | Description |
12 | +--------------+--------------+--------------+
13 | | 1 | U8 [5] | message-type |
14 | | 1 | U8 | button-mask |
15 | | 2 | U16 | x-position |
16 | | 2 | U16 | y-position |
17 | +--------------+--------------+--------------+
18 | ```
19 |
20 | - message-type: 无符号整形,固定 `0x5`
21 | - button-mask: 8 位掩码,表示键位状态,`1`为按下,`0`为弹起
22 | - x-position: 当前 X 坐标
23 | - y-position: 当前 Y 坐标
24 |
25 | button-mask 的 Bit0/1/2 位置分别代表鼠标的左键、中建、右键。使用滚轮的鼠标,每向上滑动一次,会发送一个 Bit3 的按下和弹起事件;每向下滑动一次,会发送一个 Bit4 的按下和弹起事件。
26 |
27 | ## USB/PS/2 鼠标协议
28 |
29 | 在 PS/2 或 USB 鼠标协议中,有类似 RFB 协议的表达方式。
30 |
31 | ```
32 | ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
33 | │ │ Bit7 │ Bit6 │ Bit5 │ Bit4 │ Bit3 │ Bit2 │ Bit1 │ Bit0 │
34 | ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤
35 | │Byte 1│Y Over│X Over│X Sign│Y Sign│ 0x1 │ Mid │Right │ Left │
36 | ├──────┼──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┤
37 | │Byte 2│ X Movement │
38 | ├──────┼───────────────────────────────────────────────────────┤
39 | │Byte 3│ Y Movement │
40 | ├──────┼───────────────────────────────────────────────────────┤
41 | │Byte 4│ Z Movement │
42 | └──────┴───────────────────────────────────────────────────────┘
43 | ```
44 |
45 | 鼠标协议和 RFB 协议有两点不同:
46 |
47 | - 鼠标协议的 Bit0/1/2 和 RFB 协议的 Bit0/2/1 对应
48 | - 鼠标协议的中轮转动,需要转换为 RFB 协议的按下/弹起事件
49 |
50 | > 根据 [wiki](https://en.wikipedia.org/wiki/RFB_protocol#Limitations) 描述,RFB 协议只支持8个鼠标键位,左中右和滑轮占用5个,只有3个键位给特殊按键(例如:多功能游戏鼠标)
51 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/pixel-format.md:
--------------------------------------------------------------------------------
1 | # 像素格式
2 |
3 | [帧缓冲](/rfc6143/GLOSSORY.md#帧缓冲)由像素构成。
4 |
5 | 
6 |
7 | ## PixelFormat
8 |
9 | RFB 协议中,使用 16 字节结构体 PIXEL_FORMAT 描述像素的格式。
10 |
11 | ```
12 | +--------------+--------------+-----------------+
13 | | No. of bytes | Type [Value] | Description |
14 | +--------------+--------------+-----------------+
15 | | 1 | U8 | bits-per-pixel |
16 | | 1 | U8 | depth |
17 | | 1 | U8 | big-endian-flag |
18 | | 1 | U8 | true-color-flag |
19 | | 2 | U16 | red-max |
20 | | 2 | U16 | green-max |
21 | | 2 | U16 | blue-max |
22 | | 1 | U8 | red-shift |
23 | | 1 | U8 | green-shift |
24 | | 1 | U8 | blue-shift |
25 | | 3 | | padding |
26 | +--------------+--------------+-----------------+
27 | ```
28 |
29 | - bits-per-pixel: 像素的位数,位数越大,色彩越丰富。只支持[8|16|32]
30 | - depth: 色深,像素中表示色彩的位数
31 | - big-endian-flag: 多字节像素的字节序,非零即大端序
32 | - true-color-flag: 1 表示真彩色,pixel 的值表示 RGB 颜色;0 表示调色板,pexel 的值表示颜色在调色板的偏移量
33 | - -max/-shift: 获取红蓝绿三色的位移量和长度,max=2^N-1,N是颜色的位数
34 |
35 | ```
36 | BigEndian: Blue Shift Green Shift Red Shift
37 | │ │ │
38 | ▼ ▼ ▼
39 | ┌──────────────────┬─────────────────┬─────────────────┐
40 | │ BLUE MAX │ GREEN MAX │ RED MAX │
41 | └──────────────────┴─────────────────┴─────────────────┘
42 | ```
43 |
44 | > bits-per-pixel 必须大于或等于 depth
45 |
46 | ## SetPixelFormat
47 |
48 | 客户端发送 SetPixelFormat,声明需要的的像素格式(画面质量)。此消息覆盖 [ServerInit](/rfc6143/handshake/initial.md#服务端初始化) 消息中服务端声明的初始化像素格式。
49 |
50 | 当 true-color-flag 为 0 时,服务端必须发送 SetColorMapEntries,声明使用的颜色表。客户端发送 SetPixelFormat 后,需清空本地缓存的颜色表,无论颜色表中是否有内容。
51 |
52 | ```
53 | +--------------+--------------+--------------+
54 | | No. of bytes | Type [Value] | Description |
55 | +--------------+--------------+--------------+
56 | | 1 | U8 [0] | message-type |
57 | | 3 | | padding |
58 | | 16 | PIXEL_FORMAT | pixel-format |
59 | +--------------+--------------+--------------+
60 | ```
61 |
62 | - message-type: 消息类型,固定为 0
63 | - pixel-format: [PixelFormat](#pixelformat) 结构
64 |
--------------------------------------------------------------------------------
/docs/rfc6143/transfer/set-color-map.md:
--------------------------------------------------------------------------------
1 | # 设置颜色表
2 |
3 | 当 PIXEL_FORMAT 的 true-color-flag 字段被设置为 0 时,服务端使用颜色表表示像素的颜色。
4 | `SetColorMapEntries` 用于设置颜色表的内容。
5 |
6 | ```
7 | +--------------+--------------+------------------+
8 | | No. of bytes | Type [Value] | Description |
9 | +--------------+--------------+------------------+
10 | | 1 | U8 [1] | message-type |
11 | | 1 | | padding |
12 | | 2 | U16 | first-color |
13 | | 2 | U16 | number-of-colors |
14 | +--------------+--------------+------------------+
15 | ```
16 |
17 | - message-type: 消息类型,固定是 `1`
18 | - first-color: [未知](https://github.com/rfbproto/rfbproto/issues/42)
19 | - number-of-colors: 颜色的数量
20 |
21 | ## 色值
22 |
23 | 颜色表的值总是 3 个 16 bits,代表红、绿、蓝三种颜色,每个颜色的范围是 0-65535。
24 | 例如,白色的色值是 65535,65535,65535。
25 |
26 | ```
27 | +--------------+--------------+-------------+
28 | | No. of bytes | Type [Value] | Description |
29 | +--------------+--------------+-------------+
30 | | 2 | U16 | red |
31 | | 2 | U16 | green |
32 | | 2 | U16 | blue |
33 | +--------------+--------------+-------------+
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/screenshot/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/screenshot/.keep
--------------------------------------------------------------------------------
/docs/screenshot/README.md:
--------------------------------------------------------------------------------
1 | ## Screenshot
2 |
3 | 代码路径在`./cmd/screenshot`,如果单独编译该组件,也可以到该目录下自行执行`go build`命令编译
4 |
5 | ### 获取帮助信息
6 | ```shell
7 | # 查看帮助信息
8 | $ ./screenshot --help
9 |
10 | # 查看版本信息
11 | $ ./screenshot version
12 | ```
13 |
14 | ### 启动Screenshot 获取vnc服务器的屏幕截图
15 |
16 | ```shell
17 |
18 | # imageFile 要生成的截图地址,暂时只支持jpeg格式(必填)
19 | # vncHost 要连接的vnc服务端地址(必填)
20 | # vncPort 要连接的vnc服务端端口(必填)
21 | # vncPassword 要连接的vnc服务端密码,不传则使用auth none
22 |
23 | $ ./screenshot --imageFile=./screen.jpeg --vncHost=127.0.0.1 --vncPort=5900 --vncPassword=12345612
24 | ```
--------------------------------------------------------------------------------
/docs/summary.md:
--------------------------------------------------------------------------------
1 | * 快速入门
2 | * [项目介绍](README.md)
3 | * [快速开始](overview.md)
4 | * [常见问题](questions.md)
5 | * [版本更新记录](changelog.md)
6 |
7 | * 代理(Proxy)
8 | - [使用方式](proxy/README.md)
9 | * 屏幕录像(Recorder)
10 | - [使用方式](recorder/README.md)
11 | * 屏幕录像播放(Player)
12 | - [使用方式](player/README.md)
13 | * 截屏(Screenshot)
14 | - [使用方式](screenshot/README.md)
15 | * 屏幕录像转视频(Video)
16 |
17 | * RFB协议详解
18 | - [远程帧缓冲协议](rfc6143/README.md)
19 | - [术语表](rfc6143/GLOSSORY.md)
20 | - [握手](rfc6143/handshake/README.md)
21 | - [协议版本握手](rfc6143/handshake/protocol-version.md)
22 | - [安全握手](rfc6143/handshake/security-type.md)
23 | - [初始化](rfc6143/handshake/initial.md)
24 | - [数据交互](rfc6143/transfer/README.md)
25 | - [显示协议](rfc6143/transfer/display.md)
26 | - [像素格式](rfc6143/transfer/pixel-format.md)
27 | - [输入协议](rfc6143/transfer/input/README.md)
28 | - [鼠标事件](rfc6143/transfer/input/mouse.md)
29 | - [键盘事件](rfc6143/transfer/input/keyboard.md)
30 | - [剪贴板](rfc6143/transfer/input/clipboard.md)
31 | - [编码](rfc6143/transfer/encoding/README.md)
32 | - [设置编码](rfc6143/transfer/encoding/set-encoding.md)
33 | - [Raw 原始编码](rfc6143/transfer/encoding/raw.md)
34 | - [CopyRect 镜像编码](rfc6143/transfer/encoding/copy-rect.md)
35 | - [RRE 上升游程编码](rfc6143/transfer/encoding/rise-and-run-length.md)
36 | - [TRLE 图块游程编码](rfc6143/transfer/encoding/tiled-run-length.md)
37 | - [ZRLE Zlib游程编码](rfc6143/transfer/encoding/zlib-run-length.md)
38 |
39 |
--------------------------------------------------------------------------------
/docs/video/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/video/.keep
--------------------------------------------------------------------------------
/docs/video/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vprix/vncproxy/25746a9325226cfd6498f7ec6b16ef120b4ed950/docs/video/README.md
--------------------------------------------------------------------------------
/encodings/default_encoding.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import "github.com/vprix/vncproxy/rfb"
4 |
5 | var (
6 | DefaultEncodings = []rfb.IEncoding{
7 | &ZRLEEncoding{},
8 | &TightEncoding{},
9 | &HexTileEncoding{},
10 | &TightPngEncoding{},
11 | &RREEncoding{},
12 | &ZLibEncoding{},
13 | &CopyRectEncoding{},
14 | &CoRREEncoding{},
15 | &RawEncoding{},
16 | &CursorPseudoEncoding{},
17 | &DesktopNamePseudoEncoding{},
18 | &DesktopSizePseudoEncoding{},
19 | &CursorPosPseudoEncoding{},
20 | &ExtendedDesktopSizePseudo{},
21 | &CursorWithAlphaPseudoEncoding{},
22 | &LedStatePseudo{},
23 | &LastRectPseudo{},
24 | &FencePseudo{},
25 | &XCursorPseudoEncoding{},
26 | }
27 | )
28 |
--------------------------------------------------------------------------------
/encodings/encoding.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "github.com/vprix/vncproxy/internal/dbuffer"
7 | "github.com/vprix/vncproxy/rfb"
8 | "io"
9 | )
10 |
11 | func ReadUint8(r io.Reader) (uint8, error) {
12 | var myUint uint8
13 | if err := binary.Read(r, binary.BigEndian, &myUint); err != nil {
14 | return 0, err
15 | }
16 |
17 | return myUint, nil
18 | }
19 |
20 | func ReadUint16(r io.Reader) (uint16, error) {
21 | var myUint uint16
22 | if err := binary.Read(r, binary.BigEndian, &myUint); err != nil {
23 | return 0, err
24 | }
25 |
26 | return myUint, nil
27 | }
28 |
29 | func ReadUint32(r io.Reader) (uint32, error) {
30 | var myUint uint32
31 | if err := binary.Read(r, binary.BigEndian, &myUint); err != nil {
32 | return 0, err
33 | }
34 |
35 | return myUint, nil
36 | }
37 |
38 | func ReadBytes(count int, r io.Reader) ([]byte, error) {
39 | buff := dbuffer.GetByteBuffer()
40 | defer dbuffer.ReleaseByteBuffer(buff)
41 | buff.ChangeLen(count)
42 | lengthRead, err := io.ReadFull(r, buff.B)
43 |
44 | if lengthRead != count {
45 | return nil, errors.New("ReadBytes unable to read bytes")
46 | }
47 |
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | return buff.Bytes(), nil
53 | }
54 |
55 | func ReadPixel(c io.Reader, pf *rfb.PixelFormat) ([]byte, error) {
56 | px := make([]byte, int(pf.BPP/8))
57 | if err := binary.Read(c, pf.Order(), &px); err != nil {
58 | return nil, err
59 | }
60 | return px, nil
61 | }
62 |
--------------------------------------------------------------------------------
/encodings/encoding_copyrect.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "encoding/binary"
5 | "github.com/vprix/vncproxy/rfb"
6 | )
7 |
8 | // CopyRectEncoding 该编码方式对于客户端在某些已经有了相同的象素数据的时候是非常简单和有效的。
9 | // 这种编码方式在网络中表现为x,y 坐标。让客户端知道去拷贝那一个矩形的象素数据。
10 | // 它可以应用于很多种情况。最明显的就是当用户在屏幕上移动某一个窗口的时候,还有在窗口内容滚动的时候。
11 | // 在优化画的时候不是很明显,一个比较智能的服务器可能只会发送一次,因为它知道在客户端的帧缓存里已经存在了。
12 | // 复制矩形编码并不是完全独立地发送所有的数据矩形,而是对于像素值完全相同的一组矩形,
13 | // 只发送第一个矩形全部数据,随后的矩形则只需要发送左上角X、Y坐标。
14 | // 实际上,复制矩形编码主要指的就是随后的这一系列X、Y坐标,而对于第一个矩形具体采用何种编码类型并没有限制,
15 | // 仅仅需要知道第一个矩形在帧缓冲区中的位置,以便于完成复制操作。
16 | // 因此,往往是把复制矩形编码和其它针对某一个矩形的编码类型结合使用。
17 | type CopyRectEncoding struct {
18 | SX, SY uint16
19 | }
20 |
21 | func (that *CopyRectEncoding) Type() rfb.EncodingType {
22 | return rfb.EncCopyRect
23 | }
24 |
25 | func (that *CopyRectEncoding) Supported(session rfb.ISession) bool {
26 | return true
27 | }
28 |
29 | func (that *CopyRectEncoding) Clone(data ...bool) rfb.IEncoding {
30 | obj := &CopyRectEncoding{}
31 | if len(data) > 0 && data[0] {
32 | obj.SX = that.SX
33 | obj.SY = that.SY
34 | }
35 | return obj
36 | }
37 |
38 | func (that *CopyRectEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
39 | if err := binary.Read(session, binary.BigEndian, &that.SX); err != nil {
40 | return err
41 | }
42 | if err := binary.Read(session, binary.BigEndian, &that.SY); err != nil {
43 | return err
44 | }
45 |
46 | return nil
47 | }
48 |
49 | func (that *CopyRectEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {
50 | if err := binary.Write(session, binary.BigEndian, that.SX); err != nil {
51 | return err
52 | }
53 | if err := binary.Write(session, binary.BigEndian, that.SY); err != nil {
54 | return err
55 | }
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/encodings/encoding_corre.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | // CoRREEncoding CoRRE是RRE的变体,它把发送的最大矩形限制在255×255个像素以内,用一个字节就能表示子矩形的维度。
11 | // 如果服务器想要发送一个超出限制的矩形,则只要把它划分成几个更小的RFB矩形即可。
12 | // “对于通常的桌面,这样的方式具有比RRE更好的压缩度”。
13 | // 实际上,如果进一步限制矩形的大小,就能够获得最好的压缩度。“矩形的最大值越小,决策的尺度就越好”。
14 | // 但是,如果把矩形的最大值限制得太小,就增加了矩形的数量,而由于每个RFB矩形都会有一定的开销,结果反而会使压缩度变差。
15 | // 所以应该选择一个比较恰当的数字。在目前的实现中,采用的最大值为48×48。
16 | type CoRREEncoding struct {
17 | buff *bytes.Buffer
18 | }
19 |
20 | func (that *CoRREEncoding) Type() rfb.EncodingType {
21 | return rfb.EncCoRRE
22 | }
23 |
24 | func (that *CoRREEncoding) Supported(session rfb.ISession) bool {
25 | return true
26 | }
27 |
28 | func (that *CoRREEncoding) Clone(data ...bool) rfb.IEncoding {
29 | obj := &CoRREEncoding{}
30 | if len(data) > 0 && data[0] {
31 | if that.buff != nil {
32 | obj.buff = &bytes.Buffer{}
33 | _, _ = obj.buff.Write(that.buff.Bytes())
34 | }
35 | }
36 |
37 | return obj
38 | }
39 |
40 | func (that *CoRREEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
41 |
42 | if that.buff == nil {
43 | that.buff = &bytes.Buffer{}
44 | }
45 | pf := session.Options().PixelFormat
46 | // 子矩形的数量
47 | var numOfSubRectangles uint32
48 | if err := binary.Read(session, binary.BigEndian, &numOfSubRectangles); err != nil {
49 | return err
50 | }
51 | if err := binary.Write(that.buff, binary.BigEndian, numOfSubRectangles); err != nil {
52 | return err
53 | }
54 | // (backgroundColor + (color=BPP + x=8b + y=8b + w=8b + h=8b))
55 | size := uint32(pf.BPP/8) + (uint32((pf.BPP/8)+4) * numOfSubRectangles)
56 | b, err := ReadBytes(int(size), session)
57 | if err != nil {
58 | return err
59 | }
60 | _, err = that.buff.Write(b)
61 | if err != nil {
62 | return err
63 | }
64 |
65 | return nil
66 | }
67 |
68 | func (that *CoRREEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) (err error) {
69 | if that.buff == nil {
70 | return errors.New("ByteBuffer is nil")
71 | }
72 | _, err = that.buff.WriteTo(session)
73 | that.buff.Reset()
74 | return err
75 | }
76 |
--------------------------------------------------------------------------------
/encodings/encoding_h264.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | type H264Encoding struct {
11 | buff *bytes.Buffer
12 | }
13 |
14 | var _ rfb.IEncoding = new(H264Encoding)
15 |
16 | func (that *H264Encoding) Type() rfb.EncodingType {
17 | return rfb.EncH264
18 | }
19 |
20 | func (that *H264Encoding) Supported(session rfb.ISession) bool {
21 | return true
22 | }
23 |
24 | func (that *H264Encoding) Clone(data ...bool) rfb.IEncoding {
25 | obj := &ZLibEncoding{}
26 | if len(data) > 0 && data[0] {
27 | if that.buff != nil {
28 | obj.buff = &bytes.Buffer{}
29 | _, _ = obj.buff.Write(that.buff.Bytes())
30 | }
31 | }
32 | return obj
33 | }
34 |
35 | func (that *H264Encoding) Read(session rfb.ISession, rectangle *rfb.Rectangle) error {
36 | if that.buff == nil {
37 | that.buff = &bytes.Buffer{}
38 | }
39 | size, err := ReadUint32(session)
40 | if err != nil {
41 | return err
42 | }
43 | err = binary.Write(that.buff, binary.BigEndian, size)
44 | if err != nil {
45 | return err
46 | }
47 | flags, err := ReadUint32(session)
48 | if err != nil {
49 | return err
50 | }
51 | err = binary.Write(that.buff, binary.BigEndian, flags)
52 | if err != nil {
53 | return err
54 | }
55 | b, err := ReadBytes(int(size), session)
56 | if err != nil {
57 | return err
58 | }
59 | _, err = that.buff.Write(b)
60 | if err != nil {
61 | return err
62 | }
63 | return nil
64 | }
65 |
66 | func (that *H264Encoding) Write(session rfb.ISession, rectangle *rfb.Rectangle) error {
67 | if that.buff == nil {
68 | return errors.New("ByteBuffer is nil")
69 | }
70 | _, err := that.buff.WriteTo(session)
71 | that.buff.Reset()
72 | return err
73 | }
74 |
--------------------------------------------------------------------------------
/encodings/encoding_jpeg.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
--------------------------------------------------------------------------------
/encodings/encoding_jrle.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
--------------------------------------------------------------------------------
/encodings/encoding_raw.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "github.com/vprix/vncproxy/canvas"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | // RawEncoding 采用原始地像素数据,而不进行任何的加工处理。
11 | // 在这种情况下,对于一个宽度乘以高度(即面积)为N的矩形,数据就由N个像素值组成,这些值表示按照扫描线顺序从左到右排列的每个像素。
12 | // 很明显,这种编码方式是最简单的,也是效率最低的。
13 | // RFB要求所有的客户都必须能够处理这种原始编码的数据,并且在客户没有特别指定需要某种编码方式的时候,RFB服务器就默认生成原始编码。
14 | type RawEncoding struct {
15 | buff *bytes.Buffer
16 | }
17 |
18 | var _ rfb.IEncoding = new(RawEncoding)
19 |
20 | func (that *RawEncoding) Supported(rfb.ISession) bool {
21 | return true
22 | }
23 | func (that *RawEncoding) Type() rfb.EncodingType {
24 | return rfb.EncRaw
25 | }
26 |
27 | func (that *RawEncoding) Clone(data ...bool) rfb.IEncoding {
28 | obj := &RawEncoding{}
29 | if len(data) > 0 && data[0] {
30 | if that.buff != nil {
31 | obj.buff = &bytes.Buffer{}
32 | _, _ = obj.buff.Write(that.buff.Bytes())
33 | }
34 | }
35 | return obj
36 | }
37 |
38 | func (that *RawEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error {
39 | if sess.Type() == rfb.CanvasSessionType {
40 | cv, ok := sess.Conn().(*canvas.VncCanvas)
41 | if !ok {
42 | return errors.New("canvas error")
43 | }
44 | pf := sess.Options().PixelFormat
45 | return cv.DecodeRaw(that.buff, &pf, rect)
46 | }
47 | var err error
48 | _, err = that.buff.WriteTo(sess)
49 | that.buff.Reset()
50 | return err
51 | }
52 |
53 | // Read 读取原始色彩表示
54 | func (that *RawEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
55 | if that.buff == nil {
56 | that.buff = &bytes.Buffer{}
57 | }
58 | pf := session.Options().PixelFormat
59 | // 表示单个像素是使用多少字节表示,分别为 8,16,32,对应的是1,2,4字节
60 | // 知道表示像素的字节长度,则根据宽高就能算出此次传输的总长度
61 | size := int(rect.Height) * int(rect.Width) * int(pf.BPP/8)
62 |
63 | b, err := ReadBytes(size, session)
64 | if err != nil {
65 | return err
66 | }
67 | _, err = that.buff.Write(b)
68 | if err != nil {
69 | return err
70 | }
71 | return nil
72 | }
73 |
--------------------------------------------------------------------------------
/encodings/encoding_rre.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | // RREEncoding RRE表示提升和运行长度,正如它名字暗示的那样,它实质上表示二维向量的运行长度编码。
11 | // RRE把矩形编码成可以被客户机的图形引擎翻译的格式。RRE不适合复杂的桌面,但在一些情况下比较有用。
12 | // RRE的思想就是把像素矩形的数据分成一些子区域,和一些压缩原始区域的单元。最近最佳的分区方式一般是比较容易计算的。
13 | // 编码是由像素值组成的,Vb(基本上是在矩形中最常用的像素值)和一个计数N,紧接着是N的子矩形列表,这些里面由数组组成,(x,y)是对应子矩形的坐标,
14 | // 表示子矩形上-左的坐标值,(w,h) 则表示子矩形的宽高。客户端可以通过绘制使用背景像素数据值,然后再根据子矩形来绘制原始矩形。
15 | // 二维行程编码本质上是对行程编码的一个二维模拟,而其压缩度可以保证与行程编码相同甚至更好。
16 | // 而且更重要的是,采用RRE编码的矩形被传送到客户端以后,可以立即有效地被最简单的图形引擎所还原。
17 | type RREEncoding struct {
18 | buff *bytes.Buffer
19 | }
20 |
21 | func (that *RREEncoding) Type() rfb.EncodingType {
22 | return rfb.EncRRE
23 | }
24 | func (that *RREEncoding) Supported(session rfb.ISession) bool {
25 | return true
26 | }
27 | func (that *RREEncoding) Clone(data ...bool) rfb.IEncoding {
28 | obj := &RREEncoding{}
29 | if len(data) > 0 && data[0] {
30 | if that.buff != nil {
31 | obj.buff = &bytes.Buffer{}
32 | _, _ = obj.buff.Write(that.buff.Bytes())
33 | }
34 | }
35 | return obj
36 | }
37 |
38 | func (that *RREEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
39 | if that.buff == nil {
40 | that.buff = &bytes.Buffer{}
41 | }
42 | pf := session.Options().PixelFormat
43 | // 子矩形的数量
44 | var numOfSubRectangles uint32
45 | if err := binary.Read(session, binary.BigEndian, &numOfSubRectangles); err != nil {
46 | return err
47 | }
48 | if err := binary.Write(that.buff, binary.BigEndian, numOfSubRectangles); err != nil {
49 | return err
50 | }
51 |
52 | // (backgroundColor + (color=BPP + x=16b + y=16b + w=16b + h=16b))
53 | size := uint32(pf.BPP/8) + (uint32((pf.BPP/8)+8) * numOfSubRectangles)
54 | b, err := ReadBytes(int(size), session)
55 | if err != nil {
56 | return err
57 | }
58 | _, err = that.buff.Write(b)
59 | if err != nil {
60 | return err
61 | }
62 |
63 | return nil
64 | }
65 | func (that *RREEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {
66 | if that.buff == nil {
67 | return errors.New("ByteBuffer is nil")
68 | }
69 | _, err := that.buff.WriteTo(session)
70 | that.buff.Reset()
71 | return err
72 | }
73 |
--------------------------------------------------------------------------------
/encodings/encoding_tightpng.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "fmt"
8 | "github.com/vprix/vncproxy/rfb"
9 | )
10 |
11 | type TightPngEncoding struct {
12 | buff *bytes.Buffer
13 | }
14 |
15 | func (that *TightPngEncoding) Supported(session rfb.ISession) bool {
16 | return true
17 | }
18 |
19 | func (that *TightPngEncoding) Clone(data ...bool) rfb.IEncoding {
20 | obj := &TightPngEncoding{}
21 | if len(data) > 0 && data[0] {
22 | if that.buff != nil {
23 | obj.buff = &bytes.Buffer{}
24 | _, _ = obj.buff.Write(that.buff.Bytes())
25 | }
26 | }
27 | return obj
28 | }
29 |
30 | func (that *TightPngEncoding) Type() rfb.EncodingType {
31 | return rfb.EncTightPng
32 | }
33 |
34 | func (that *TightPngEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {
35 | if that.buff == nil {
36 | return errors.New("ByteBuffer is nil")
37 | }
38 | _, err := that.buff.WriteTo(session)
39 | that.buff.Reset()
40 | return err
41 | }
42 | func (that *TightPngEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
43 | if that.buff == nil {
44 | that.buff = &bytes.Buffer{}
45 | }
46 | pf := session.Options().PixelFormat
47 | bytesPixel := calcTightBytePerPixel(&pf)
48 | compressionControl, err := ReadUint8(session)
49 | if err != nil {
50 | return nil
51 | }
52 | _ = binary.Write(that.buff, binary.BigEndian, compressionControl)
53 |
54 | compType := compressionControl >> 4 & 0x0F
55 |
56 | switch compType {
57 | case tightCompressionPNG:
58 | size, err := that.ReadCompactLen(session)
59 | if err != nil {
60 | return err
61 | }
62 | bt, err := ReadBytes(size, session)
63 | if err != nil {
64 | return err
65 | }
66 | _, _ = that.buff.Write(bt)
67 |
68 | case tightCompressionFill:
69 | bt, err := ReadBytes(bytesPixel, session)
70 | if err != nil {
71 | return err
72 | }
73 | _, _ = that.buff.Write(bt)
74 | default:
75 | return fmt.Errorf("unknown tight compression %d", compType)
76 | }
77 | return nil
78 | }
79 |
80 | // ReadCompactLen 获取动态长度
81 | func (that *TightPngEncoding) ReadCompactLen(session rfb.ISession) (int, error) {
82 | var err error
83 | part, err := ReadUint8(session)
84 | if err := binary.Write(that.buff, binary.BigEndian, part); err != nil {
85 | return 0, err
86 | }
87 | size := uint32(part & 0x7F)
88 | if (part & 0x80) == 0 {
89 | return int(size), nil
90 | }
91 | part, err = ReadUint8(session)
92 | if err := binary.Write(that.buff, binary.BigEndian, part); err != nil {
93 | return 0, err
94 | }
95 | size |= uint32(int(part)&0x7F) << 7
96 | if (part & 0x80) == 0 {
97 | return int(size), nil
98 | }
99 | part, err = ReadUint8(session)
100 | if err := binary.Write(that.buff, binary.BigEndian, part); err != nil {
101 | return 0, err
102 | }
103 | size |= uint32(int(part)&0xFF) << 14
104 |
105 | return int(size), err
106 | }
107 |
--------------------------------------------------------------------------------
/encodings/encoding_trle.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
--------------------------------------------------------------------------------
/encodings/encoding_zlib.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | type ZLibEncoding struct {
11 | buff *bytes.Buffer
12 | }
13 |
14 | func (that *ZLibEncoding) Supported(c rfb.ISession) bool {
15 | return true
16 | }
17 | func (that *ZLibEncoding) Type() rfb.EncodingType {
18 | return rfb.EncZlib
19 | }
20 |
21 | func (that *ZLibEncoding) Clone(data ...bool) rfb.IEncoding {
22 | obj := &ZLibEncoding{}
23 | if len(data) > 0 && data[0] {
24 | if that.buff != nil {
25 | obj.buff = &bytes.Buffer{}
26 | _, _ = obj.buff.Write(that.buff.Bytes())
27 | }
28 | }
29 | return obj
30 | }
31 |
32 | func (that *ZLibEncoding) Read(sess rfb.ISession, rect *rfb.Rectangle) error {
33 | if that.buff == nil {
34 | that.buff = &bytes.Buffer{}
35 | }
36 | size, err := ReadUint32(sess)
37 | if err != nil {
38 | return err
39 | }
40 | err = binary.Write(that.buff, binary.BigEndian, size)
41 | if err != nil {
42 | return err
43 | }
44 | b, err := ReadBytes(int(size), sess)
45 | if err != nil {
46 | return err
47 | }
48 | _, err = that.buff.Write(b)
49 | if err != nil {
50 | return err
51 | }
52 | return nil
53 | }
54 |
55 | func (that *ZLibEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error {
56 | if that.buff == nil {
57 | return errors.New("ByteBuffer is nil")
58 | }
59 | _, err := that.buff.WriteTo(sess)
60 | that.buff.Reset()
61 | return err
62 | }
63 |
--------------------------------------------------------------------------------
/encodings/pseudo_cursor.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "github.com/vprix/vncproxy/canvas"
7 | "github.com/vprix/vncproxy/rfb"
8 | "image"
9 | "image/color"
10 | "math"
11 | )
12 |
13 | // CursorPseudoEncoding 如果客户端请求指针/鼠标伪编码,那么就是说它有能力进行本地绘制鼠标。
14 | // 这样就可以明显改善传输性能。服务器通过发送带有伪鼠标编码的伪矩形来设置鼠标的形状作为更新的一部分。
15 | // 伪矩形的x 和y 表示鼠标的热点,宽和高表示用像素来表示鼠标的宽和高。包含宽X高像素值的数据带有位掩码。
16 | // 位掩码是由从左到右,从上到下的扫描线组成,而每一扫描线被填充为floor((width +7) / 8)。
17 | // 对应每一字节最重要的位表示最左边像素,对应1 位表示相应指针的像素是正确的。
18 | type CursorPseudoEncoding struct {
19 | buff *bytes.Buffer
20 | }
21 |
22 | func (that *CursorPseudoEncoding) Supported(session rfb.ISession) bool {
23 | return true
24 | }
25 |
26 | // Draw 绘制鼠标指针
27 | func (that *CursorPseudoEncoding) draw(cv *canvas.VncCanvas, pf rfb.PixelFormat, rect *rfb.Rectangle) error {
28 | numColors := int(rect.Height) * int(rect.Width)
29 | colors := make([]color.Color, numColors)
30 | var err error
31 | for i := 0; i < numColors; i++ {
32 | colors[i], err = cv.ReadColor(that.buff, &pf)
33 | if err != nil {
34 | return err
35 | }
36 | }
37 | // 获取掩码信息
38 | bitmask := make([]byte, int((rect.Width+7)/8*rect.Height))
39 | if err = binary.Read(that.buff, binary.BigEndian, &bitmask); err != nil {
40 | return err
41 | }
42 | scanLine := (rect.Width + 7) / 8
43 | // 生成鼠标指针的形状
44 | cursorImg := image.NewRGBA(canvas.MakeRect(0, 0, int(rect.Width), int(rect.Height)))
45 | var cursorMask [][]bool
46 | for i := 0; i < int(rect.Width); i++ {
47 | cursorMask = append(cursorMask, make([]bool, rect.Height))
48 | }
49 | // 填充鼠标指针的颜色
50 | for y := 0; y < int(rect.Height); y++ {
51 | for x := 0; x < int(rect.Width); x++ {
52 | offset := y*int(rect.Width) + x
53 | if bitmask[y*int(scanLine)+x/8]&(1< 0 {
54 | cursorImg.Set(x, y, colors[offset])
55 | cursorMask[x][y] = true
56 | }
57 | }
58 | }
59 | // 设置鼠标指针
60 | cv.CursorOffset = &image.Point{X: int(rect.X), Y: int(rect.Y)}
61 | cv.Cursor = cursorImg
62 | cv.CursorBackup = image.NewRGBA(cursorImg.Bounds())
63 | cv.CursorMask = cursorMask
64 |
65 | return nil
66 | }
67 |
68 | func (that *CursorPseudoEncoding) Clone(data ...bool) rfb.IEncoding {
69 | obj := &CursorPseudoEncoding{}
70 | if len(data) > 0 && data[0] {
71 | if that.buff != nil {
72 | obj.buff = &bytes.Buffer{}
73 | _, _ = obj.buff.Write(that.buff.Bytes())
74 | }
75 | }
76 | return obj
77 | }
78 |
79 | func (that *CursorPseudoEncoding) Type() rfb.EncodingType {
80 | return rfb.EncCursorPseudo
81 | }
82 |
83 | func (that *CursorPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
84 | if rect.Width*rect.Height == 0 {
85 | return nil
86 | }
87 | if that.buff == nil {
88 | that.buff = &bytes.Buffer{}
89 | }
90 | var bt []byte
91 | var err error
92 |
93 | bytesPixel := int(session.Options().PixelFormat.BPP / 8) //calcTightBytePerPixel(pf)
94 | bt, err = ReadBytes(int(rect.Width*rect.Height)*bytesPixel, session)
95 | if err != nil {
96 | return err
97 | }
98 | _, _ = that.buff.Write(bt)
99 | mask := ((rect.Width + 7) / 8) * rect.Height
100 | bt, err = ReadBytes(int(math.Floor(float64(mask))), session)
101 | if err != nil {
102 | return err
103 | }
104 | _, _ = that.buff.Write(bt)
105 | return nil
106 | }
107 |
108 | func (that *CursorPseudoEncoding) Write(sess rfb.ISession, rect *rfb.Rectangle) error {
109 | if that.buff == nil {
110 | return nil
111 | }
112 | if sess.Type() == rfb.CanvasSessionType {
113 | return that.draw(sess.Conn().(*canvas.VncCanvas), sess.Options().PixelFormat, rect)
114 | }
115 | var err error
116 | _, err = that.buff.WriteTo(sess)
117 | that.buff.Reset()
118 | return err
119 | }
120 |
--------------------------------------------------------------------------------
/encodings/pseudo_cursor_with_alpha.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "bytes"
5 | "github.com/vprix/vncproxy/rfb"
6 | )
7 |
8 | type CursorWithAlphaPseudoEncoding struct {
9 | buff *bytes.Buffer
10 | }
11 |
12 | func (that *CursorWithAlphaPseudoEncoding) Supported(session rfb.ISession) bool {
13 | return true
14 | }
15 |
16 | func (that *CursorWithAlphaPseudoEncoding) Clone(data ...bool) rfb.IEncoding {
17 | obj := &CursorWithAlphaPseudoEncoding{}
18 | if len(data) > 0 && data[0] {
19 | if that.buff != nil {
20 | obj.buff = &bytes.Buffer{}
21 | _, _ = obj.buff.Write(that.buff.Bytes())
22 | }
23 | }
24 | return obj
25 | }
26 |
27 | func (that *CursorWithAlphaPseudoEncoding) Type() rfb.EncodingType {
28 | return rfb.EncCursorWithAlphaPseudo
29 | }
30 |
31 | func (that *CursorWithAlphaPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
32 | if that.buff == nil {
33 | that.buff = &bytes.Buffer{}
34 | }
35 | var bt []byte
36 | var err error
37 | bt, err = ReadBytes(4, session)
38 | if err != nil {
39 | return err
40 | }
41 | _, _ = that.buff.Write(bt)
42 |
43 | if rect.Width*rect.Height > 0 {
44 | bt2, err := ReadBytes(int(rect.Width*rect.Height)*4, session)
45 | if err != nil {
46 | return err
47 | }
48 | _, _ = that.buff.Write(bt2)
49 | }
50 | return nil
51 | }
52 |
53 | func (that *CursorWithAlphaPseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {
54 | if that.buff == nil {
55 | return nil
56 | }
57 | var err error
58 | _, err = that.buff.WriteTo(session)
59 | that.buff.Reset()
60 | return err
61 | }
62 |
--------------------------------------------------------------------------------
/encodings/pseudo_desktop_name.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "encoding/binary"
5 | "github.com/vprix/vncproxy/rfb"
6 | )
7 |
8 | // DesktopNamePseudoEncoding 服务端设置桌面名字的消息
9 | type DesktopNamePseudoEncoding struct {
10 | Name []byte
11 | }
12 |
13 | func (that *DesktopNamePseudoEncoding) Supported(session rfb.ISession) bool {
14 | return true
15 | }
16 |
17 | func (that *DesktopNamePseudoEncoding) Clone(data ...bool) rfb.IEncoding {
18 | obj := &DesktopNamePseudoEncoding{}
19 | if len(data) > 0 && data[0] {
20 | obj.Name = that.Name
21 | }
22 | return obj
23 | }
24 |
25 | func (that *DesktopNamePseudoEncoding) Type() rfb.EncodingType {
26 | return rfb.EncDesktopNamePseudo
27 | }
28 |
29 | // Read 实现了编码接口
30 | func (that *DesktopNamePseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
31 | var length uint32
32 | if err := binary.Read(session, binary.BigEndian, &length); err != nil {
33 | return err
34 | }
35 | name := make([]byte, length)
36 | if err := binary.Read(session, binary.BigEndian, &name); err != nil {
37 | return err
38 | }
39 | that.Name = name
40 | return nil
41 | }
42 |
43 | func (that *DesktopNamePseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {
44 | if err := binary.Write(session, binary.BigEndian, uint32(len(that.Name))); err != nil {
45 | return err
46 | }
47 | if err := binary.Write(session, binary.BigEndian, that.Name); err != nil {
48 | return err
49 | }
50 |
51 | return session.Flush()
52 | }
53 |
--------------------------------------------------------------------------------
/encodings/pseudo_desktop_size.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "github.com/vprix/vncproxy/rfb"
5 | )
6 |
7 | // DesktopSizePseudoEncoding 如果客户端请求桌面大小伪编码,那么就是说它能处理帧缓存宽/高的改变。
8 | // 服务器通过发送带有桌面大小伪编码的伪矩形作为上一个矩形来完成一次更新。
9 | // 伪矩形的x 和y 被忽略,而宽和高表示帧缓存新的宽和高。没有其他的数据与伪矩形有关。
10 | type DesktopSizePseudoEncoding struct {
11 | }
12 |
13 | func (that *DesktopSizePseudoEncoding) Supported(session rfb.ISession) bool {
14 | return true
15 | }
16 |
17 | func (that *DesktopSizePseudoEncoding) Clone(data ...bool) rfb.IEncoding {
18 | obj := &DesktopSizePseudoEncoding{}
19 | return obj
20 | }
21 |
22 | func (that *DesktopSizePseudoEncoding) Type() rfb.EncodingType { return rfb.EncDesktopSizePseudo }
23 |
24 | // Read implements the Encoding interface.
25 | func (that *DesktopSizePseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
26 | return nil
27 | }
28 |
29 | func (that *DesktopSizePseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {
30 | return nil
31 | }
32 |
--------------------------------------------------------------------------------
/encodings/pseudo_extended_desktop_size.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // ExtendedDesktopSizePseudo 扩展适应客户端桌面分辨率
10 | type ExtendedDesktopSizePseudo struct {
11 | buff *bytes.Buffer
12 | }
13 |
14 | func (that *ExtendedDesktopSizePseudo) Supported(rfb.ISession) bool {
15 | return true
16 | }
17 |
18 | func (that *ExtendedDesktopSizePseudo) Clone(data ...bool) rfb.IEncoding {
19 | obj := &ExtendedDesktopSizePseudo{}
20 | if len(data) > 0 && data[0] {
21 | obj.buff = &bytes.Buffer{}
22 | _, _ = obj.buff.Write(that.buff.Bytes())
23 | }
24 | return obj
25 | }
26 |
27 | func (that *ExtendedDesktopSizePseudo) Type() rfb.EncodingType {
28 | return rfb.EncExtendedDesktopSizePseudo
29 | }
30 |
31 | func (that *ExtendedDesktopSizePseudo) Write(session rfb.ISession, rect *rfb.Rectangle) (err error) {
32 | _, err = that.buff.WriteTo(session)
33 | that.buff.Reset()
34 | return err
35 | }
36 |
37 | func (that *ExtendedDesktopSizePseudo) Read(session rfb.ISession, rect *rfb.Rectangle) error {
38 | if that.buff == nil {
39 | that.buff = &bytes.Buffer{}
40 | }
41 | //读取屏幕数量
42 | screensNumber, err := ReadUint8(session)
43 | if err != nil {
44 | return err
45 | }
46 | err = binary.Write(that.buff, binary.BigEndian, screensNumber)
47 | if err != nil {
48 | return err
49 | }
50 | // 填充
51 | pad, err := ReadBytes(3, session)
52 | if err != nil {
53 | return err
54 | }
55 | err = binary.Write(that.buff, binary.BigEndian, pad)
56 |
57 | b2, err := ReadBytes(int(screensNumber)*16, session)
58 | if err != nil {
59 | return err
60 | }
61 | _, _ = that.buff.Write(b2)
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/encodings/pseudo_fence.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "github.com/vprix/vncproxy/rfb"
5 | )
6 |
7 | type FencePseudo struct {
8 | }
9 |
10 | func (that *FencePseudo) Supported(_ rfb.ISession) bool {
11 | return true
12 | }
13 |
14 | func (that *FencePseudo) Clone(_ ...bool) rfb.IEncoding {
15 | obj := &FencePseudo{}
16 | return obj
17 | }
18 |
19 | func (that *FencePseudo) Type() rfb.EncodingType {
20 | return rfb.EncFencePseudo
21 | }
22 |
23 | func (that *FencePseudo) Read(_ rfb.ISession, _ *rfb.Rectangle) error {
24 | return nil
25 | }
26 |
27 | func (that *FencePseudo) Write(_ rfb.ISession, _ *rfb.Rectangle) error {
28 |
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/encodings/pseudo_last_rect.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "github.com/vprix/vncproxy/rfb"
5 | )
6 |
7 | type LastRectPseudo struct {
8 | }
9 |
10 | func (that *LastRectPseudo) Supported(_ rfb.ISession) bool {
11 | return true
12 | }
13 |
14 | func (that *LastRectPseudo) Clone(_ ...bool) rfb.IEncoding {
15 | obj := &LastRectPseudo{}
16 | return obj
17 | }
18 |
19 | func (that *LastRectPseudo) Type() rfb.EncodingType {
20 | return rfb.EncLastRectPseudo
21 | }
22 |
23 | func (that *LastRectPseudo) Read(_ rfb.ISession, _ *rfb.Rectangle) error {
24 | return nil
25 | }
26 |
27 | func (that *LastRectPseudo) Write(_ rfb.ISession, _ *rfb.Rectangle) error {
28 |
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/encodings/pseudo_led_state.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "encoding/binary"
5 | "github.com/vprix/vncproxy/rfb"
6 | )
7 |
8 | // LedStatePseudo 切换客户端本地小键盘锁定的led灯
9 | // 0 滚动锁
10 | // 1 数字锁定
11 | // 2 大写锁定
12 | type LedStatePseudo struct {
13 | LedState uint8
14 | }
15 |
16 | func (that *LedStatePseudo) Supported(session rfb.ISession) bool {
17 | return true
18 | }
19 |
20 | func (that *LedStatePseudo) Clone(data ...bool) rfb.IEncoding {
21 | obj := &LedStatePseudo{}
22 | return obj
23 | }
24 |
25 | func (that *LedStatePseudo) Type() rfb.EncodingType {
26 | return rfb.EncLedStatePseudo
27 | }
28 |
29 | func (that *LedStatePseudo) Read(session rfb.ISession, rect *rfb.Rectangle) error {
30 | u8, err := ReadUint8(session)
31 | if err != nil {
32 | return err
33 | }
34 | that.LedState = u8
35 | return nil
36 | }
37 |
38 | func (that *LedStatePseudo) Write(session rfb.ISession, rect *rfb.Rectangle) error {
39 | if err := binary.Write(session, binary.BigEndian, that.LedState); err != nil {
40 | return err
41 | }
42 | return nil
43 | }
44 |
--------------------------------------------------------------------------------
/encodings/pseudo_pointer_pos.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "errors"
5 | "github.com/vprix/vncproxy/canvas"
6 | "github.com/vprix/vncproxy/rfb"
7 | "image"
8 | "image/draw"
9 | )
10 |
11 | type CursorPosPseudoEncoding struct {
12 | }
13 |
14 | func (that *CursorPosPseudoEncoding) Supported(session rfb.ISession) bool {
15 | return true
16 | }
17 |
18 | func (that *CursorPosPseudoEncoding) Draw(img draw.Image, rect *rfb.Rectangle) error {
19 | cv, ok := img.(*canvas.VncCanvas)
20 | if !ok {
21 | return errors.New("canvas error")
22 | }
23 | // 本地鼠标指针的位置
24 | cv.CursorLocation = &image.Point{X: int(rect.X), Y: int(rect.Y)}
25 | return nil
26 | }
27 |
28 | func (that *CursorPosPseudoEncoding) Clone(data ...bool) rfb.IEncoding {
29 | obj := &CursorPosPseudoEncoding{}
30 | return obj
31 | }
32 |
33 | func (that *CursorPosPseudoEncoding) Type() rfb.EncodingType {
34 | return rfb.EncPointerPosPseudo
35 | }
36 |
37 | func (that *CursorPosPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
38 | return nil
39 | }
40 |
41 | func (that *CursorPosPseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {
42 | return nil
43 | }
44 |
--------------------------------------------------------------------------------
/encodings/pseudo_x_cursor.go:
--------------------------------------------------------------------------------
1 | package encodings
2 |
3 | import (
4 | "encoding/binary"
5 | "github.com/vprix/vncproxy/rfb"
6 | "math"
7 | )
8 |
9 | type XCursorPseudoEncoding struct {
10 | PrimaryR, PrimaryG, PrimaryB uint8 // 主颜色
11 | SecondaryR, SecondaryG, SecondaryB uint8 // 次颜色
12 | Bitmap []byte //颜色位图
13 | Bitmask []byte //透明度位掩码
14 | }
15 |
16 | func (that *XCursorPseudoEncoding) Supported(session rfb.ISession) bool {
17 | return true
18 | }
19 | func (that *XCursorPseudoEncoding) Clone(data ...bool) rfb.IEncoding {
20 | obj := &XCursorPseudoEncoding{}
21 | if len(data) > 0 && data[0] {
22 | obj.PrimaryR = that.PrimaryR
23 | obj.PrimaryG = that.PrimaryG
24 | obj.PrimaryB = that.PrimaryB
25 | obj.SecondaryR = that.SecondaryR
26 | obj.SecondaryG = that.SecondaryG
27 | obj.SecondaryB = that.SecondaryB
28 | Bitmap := make([]byte, len(that.Bitmap))
29 | Bitmask := make([]byte, len(that.Bitmask))
30 | copy(Bitmap, that.Bitmap)
31 | copy(Bitmask, that.Bitmask)
32 | obj.Bitmap = Bitmap
33 | obj.Bitmask = Bitmask
34 | }
35 | return obj
36 | }
37 | func (that *XCursorPseudoEncoding) Type() rfb.EncodingType {
38 | return rfb.EncXCursorPseudo
39 | }
40 |
41 | func (that *XCursorPseudoEncoding) Read(session rfb.ISession, rect *rfb.Rectangle) error {
42 | if err := binary.Read(session, binary.BigEndian, &that.PrimaryR); err != nil {
43 | return err
44 | }
45 | if err := binary.Read(session, binary.BigEndian, &that.PrimaryG); err != nil {
46 | return err
47 | }
48 | if err := binary.Read(session, binary.BigEndian, &that.PrimaryB); err != nil {
49 | return err
50 | }
51 | if err := binary.Read(session, binary.BigEndian, &that.SecondaryR); err != nil {
52 | return err
53 | }
54 | if err := binary.Read(session, binary.BigEndian, &that.SecondaryG); err != nil {
55 | return err
56 | }
57 | if err := binary.Read(session, binary.BigEndian, &that.SecondaryB); err != nil {
58 | return err
59 | }
60 |
61 | bitMapSize := int(math.Floor((float64(rect.Width)+7)/8) * float64(rect.Height))
62 | bitMaskSize := int(math.Floor((float64(rect.Width)+7)/8) * float64(rect.Height))
63 |
64 | that.Bitmap = make([]byte, bitMapSize)
65 | that.Bitmask = make([]byte, bitMaskSize)
66 | if err := binary.Read(session, binary.BigEndian, &that.Bitmap); err != nil {
67 | return err
68 | }
69 | if err := binary.Read(session, binary.BigEndian, &that.Bitmask); err != nil {
70 | return err
71 | }
72 |
73 | return nil
74 | }
75 |
76 | func (that *XCursorPseudoEncoding) Write(session rfb.ISession, rect *rfb.Rectangle) error {
77 | if err := binary.Write(session, binary.BigEndian, that.PrimaryR); err != nil {
78 | return err
79 | }
80 | if err := binary.Write(session, binary.BigEndian, that.PrimaryG); err != nil {
81 | return err
82 | }
83 | if err := binary.Write(session, binary.BigEndian, that.PrimaryB); err != nil {
84 | return err
85 | }
86 | if err := binary.Write(session, binary.BigEndian, that.SecondaryR); err != nil {
87 | return err
88 | }
89 | if err := binary.Write(session, binary.BigEndian, that.SecondaryG); err != nil {
90 | return err
91 | }
92 | if err := binary.Write(session, binary.BigEndian, that.SecondaryB); err != nil {
93 | return err
94 | }
95 |
96 | if err := binary.Write(session, binary.BigEndian, that.Bitmap); err != nil {
97 | return err
98 | }
99 | if err := binary.Write(session, binary.BigEndian, that.Bitmask); err != nil {
100 | return err
101 | }
102 |
103 | return nil
104 | }
105 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/vprix/vncproxy
2 |
3 | require (
4 | github.com/gogf/gf/v2 v2.3.2
5 | github.com/osgochina/dmicro v1.2.1
6 | golang.org/x/net v0.8.0
7 | )
8 |
9 | go 1.16
10 |
--------------------------------------------------------------------------------
/handler/ClientClientInitHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "github.com/osgochina/dmicro/logger"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | // ClientClientInitHandler vnc握手步骤第三步
11 | // 1. 根据配置信息判断该vnc会话是否独占,
12 | // 2. 发送是否独占标识给vnc服务端
13 | type ClientClientInitHandler struct{}
14 |
15 | func (that *ClientClientInitHandler) Handle(session rfb.ISession) error {
16 | if logger.IsDebug() {
17 | logger.Debug(context.TODO(), "[Proxy客户端->VNC服务端]: 执行vnc握手步骤第三步[ClientInit]")
18 | }
19 | cfg := session.Options()
20 | var shared uint8
21 | if cfg.Exclusive {
22 | shared = 0
23 | } else {
24 | shared = 1
25 | }
26 | if err := binary.Write(session, binary.BigEndian, shared); err != nil {
27 | return err
28 | }
29 | if logger.IsDebug() {
30 | logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: 执行ClientInit步骤,发送shared=%d", shared)
31 | }
32 | return session.Flush()
33 | }
34 |
--------------------------------------------------------------------------------
/handler/ClientMessageHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/osgochina/dmicro/logger"
7 | "github.com/vprix/vncproxy/rfb"
8 | "golang.org/x/net/context"
9 | )
10 |
11 | // ClientMessageHandler vnc握手已结束,进入消息交互阶段
12 | // 启动两个协程处理后续消息逻辑
13 | // 1. 协程1:通过ClientMessageCh通道获取消息,并把该消息写入到vnc服务端会话中。
14 | // 2. 协程2:从vnc服务端会话中读取消息类型及消息内容,组装该消息,发消息发送到ServerMessageCh通道中,供其他功能消费
15 | // 3. 发送编码格式消息SetEncodings到vnc服务端
16 | // 4. 发送帧数据请求消息FramebufferUpdateRequest到vnc服务端
17 | type ClientMessageHandler struct{}
18 |
19 | func (*ClientMessageHandler) Handle(session rfb.ISession) error {
20 | if logger.IsDebug() {
21 | logger.Debug(context.TODO(), "[Proxy客户端->VNC服务端]: vnc握手已结束,进入消息交互阶段[ClientMessageHandler]")
22 | }
23 | cfg := session.Options()
24 | var err error
25 |
26 | // proxy客户端支持的消息类型
27 | serverMessages := make(map[rfb.MessageType]rfb.Message)
28 | for _, m := range cfg.Messages {
29 | serverMessages[m.Type()] = m
30 | }
31 |
32 | // 通过ClientMessageCh通道获取消息,并把该消息写入到vnc服务端会话中。
33 | go func() {
34 | for {
35 | select {
36 | case <-session.Wait():
37 | return
38 | case msg := <-cfg.Input:
39 | if logger.IsDebug() {
40 | logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端] 消息类型:%s,消息内容:%s", rfb.ClientMessageType(msg.Type()), msg.String())
41 | }
42 | if err = msg.Write(session); err != nil {
43 | cfg.ErrorCh <- err
44 | _ = session.Close()
45 | return
46 | }
47 | }
48 | }
49 | }()
50 |
51 | // 从vnc服务端会话中读取消息类型及消息内容,组装该消息,发消息发送到ServerMessageCh通道中,供其他功能消费
52 | go func() {
53 | for {
54 | select {
55 | case <-session.Wait():
56 | return
57 | default:
58 | // 从会话中读取消息类型
59 | var messageType rfb.MessageType
60 | if err = binary.Read(session, binary.BigEndian, &messageType); err != nil {
61 | cfg.ErrorCh <- err
62 | return
63 | }
64 | if logger.IsDebug() {
65 | logger.Debugf(context.TODO(), "[VNC服务端->Proxy客户端] 消息类型:%s", rfb.ServerMessageType(messageType))
66 | }
67 | // 判断proxy客户端是否支持该消息
68 | msg, ok := serverMessages[messageType]
69 | if !ok {
70 | err = fmt.Errorf("未知的消息类型: %v", messageType)
71 | cfg.ErrorCh <- err
72 | _ = session.Close()
73 | return
74 | }
75 | // 读取消息内容
76 | parsedMsg, err := msg.Read(session)
77 | if err != nil {
78 | cfg.ErrorCh <- err
79 | _ = session.Close()
80 | return
81 | }
82 | if logger.IsDebug() {
83 | logger.Debugf(context.TODO(), "[VNC服务端->Proxy客户端] 消息类型:%s,消息内容:%s", rfb.ServerMessageType(parsedMsg.Type()), parsedMsg)
84 | }
85 | cfg.Output <- parsedMsg
86 | }
87 | }
88 | }()
89 | return nil
90 | }
91 |
--------------------------------------------------------------------------------
/handler/ClientSecurityHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "fmt"
7 | "github.com/osgochina/dmicro/logger"
8 | "github.com/vprix/vncproxy/rfb"
9 | )
10 |
11 | // ClientSecurityHandler vnc握手步骤第二步
12 | // 1. 读取vnc服务端支持的安全认证套件数量及类型
13 | // 2. 匹配vnc服务端与proxy客户端的安全认证套件
14 | // 3. 进入安全认证套件认证流程
15 | // 4. 获取认证结果,如果认证失败,获取失败的原因。
16 | type ClientSecurityHandler struct{}
17 |
18 | func (*ClientSecurityHandler) Handle(session rfb.ISession) error {
19 | if logger.IsDebug() {
20 | logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: 执行vnc握手第二步:[Security]")
21 | }
22 | cfg := session.Options()
23 | // 读取vnc服务端支持的安全认证套件数量
24 | var numSecurityTypes uint8
25 | if err := binary.Read(session, binary.BigEndian, &numSecurityTypes); err != nil {
26 | return err
27 | }
28 | // 读取vnc服务端支持的安全认证套件类型
29 | secTypes := make([]rfb.SecurityType, numSecurityTypes)
30 | if err := binary.Read(session, binary.BigEndian, &secTypes); err != nil {
31 | return err
32 | }
33 |
34 | // 匹配vnc服务端与proxy客户端的安全认证套件
35 | var secType rfb.ISecurityHandler
36 | for _, st := range cfg.SecurityHandlers {
37 | for _, sc := range secTypes {
38 | if st.Type() == sc {
39 | secType = st
40 | }
41 | }
42 | }
43 |
44 | // 发送proxy客户端选中的安全认证套件
45 | if err := binary.Write(session, binary.BigEndian, cfg.SecurityHandlers[0].Type()); err != nil {
46 | return err
47 | }
48 |
49 | if err := session.Flush(); err != nil {
50 | return err
51 | }
52 |
53 | // 进入安全认证套件认证流程
54 | err := secType.Auth(session)
55 | if err != nil {
56 | return fmt.Errorf("安全认证失败, error:%v", err)
57 | }
58 |
59 | // 读取安全认证结果
60 | var authCode uint32
61 | if err := binary.Read(session, binary.BigEndian, &authCode); err != nil {
62 | return err
63 | }
64 | if logger.IsDebug() {
65 | logger.Debugf(context.TODO(), "安全认证中, 安全认证套件类型: %d,认证结果(0为成功): %d", rfb.ClientMessageType(secType.Type()), authCode)
66 | }
67 | //如果认证失败,则读取失败原因
68 | if authCode == 1 {
69 | var reasonLength uint32
70 | if err = binary.Read(session, binary.BigEndian, &reasonLength); err != nil {
71 | return err
72 | }
73 | reasonText := make([]byte, reasonLength)
74 | if err = binary.Read(session, binary.BigEndian, &reasonText); err != nil {
75 | return err
76 | }
77 | return fmt.Errorf("%s", reasonText)
78 | }
79 | session.SetSecurityHandler(secType)
80 | return nil
81 | }
82 |
--------------------------------------------------------------------------------
/handler/ClientServerInitHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "github.com/osgochina/dmicro/logger"
7 | "github.com/vprix/vncproxy/messages"
8 | "github.com/vprix/vncproxy/rfb"
9 | )
10 |
11 | // ClientServerInitHandler vnc握手第四步
12 | // 1. 读取vnc服务端发送的屏幕宽高,像素格式,桌面名称
13 | type ClientServerInitHandler struct{}
14 |
15 | func (*ClientServerInitHandler) Handle(session rfb.ISession) error {
16 | if logger.IsDebug() {
17 | logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: 执行vnc握手第四步:[ServerInit]")
18 | }
19 | var err error
20 | srvInit := messages.ServerInit{}
21 |
22 | if err = binary.Read(session, binary.BigEndian, &srvInit.FBWidth); err != nil {
23 | return err
24 | }
25 | if err = binary.Read(session, binary.BigEndian, &srvInit.FBHeight); err != nil {
26 | return err
27 | }
28 | if err = binary.Read(session, binary.BigEndian, &srvInit.PixelFormat); err != nil {
29 | return err
30 | }
31 | if err = binary.Read(session, binary.BigEndian, &srvInit.NameLength); err != nil {
32 | return err
33 | }
34 |
35 | srvInit.NameText = make([]byte, srvInit.NameLength)
36 | if err = binary.Read(session, binary.BigEndian, &srvInit.NameText); err != nil {
37 | return err
38 | }
39 | if logger.IsDebug() {
40 | logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: serverInit: %s", srvInit)
41 | }
42 | session.SetDesktopName(srvInit.NameText)
43 | // 如果协议是aten1,则执行特殊的逻辑
44 | if session.ProtocolVersion() == "aten1" {
45 | session.SetWidth(800)
46 | session.SetHeight(600)
47 | // 发送像素格式消息
48 | session.SetPixelFormat(rfb.NewPixelFormatAten())
49 | } else {
50 | session.SetWidth(srvInit.FBWidth)
51 | session.SetHeight(srvInit.FBHeight)
52 |
53 | //告诉vnc服务端,proxy客户端支持的像素格式,发送`SetPixelFormat`消息
54 | pixelMsg := messages.SetPixelFormat{PF: rfb.PixelFormat32bit}
55 | err = pixelMsg.Write(session)
56 | if err != nil {
57 | return err
58 | }
59 | session.SetPixelFormat(rfb.PixelFormat32bit)
60 | }
61 | // aten1协议需要再次读取扩展信息
62 | if session.ProtocolVersion() == "aten1" {
63 | ikvm := struct {
64 | _ [8]byte
65 | IKVMVideoEnable uint8
66 | IKVMKMEnable uint8
67 | IKVMKickEnable uint8
68 | VUSBEnable uint8
69 | }{}
70 | if err = binary.Read(session, binary.BigEndian, &ikvm); err != nil {
71 | return err
72 | }
73 | }
74 | return nil
75 | }
76 |
--------------------------------------------------------------------------------
/handler/ClientVersionHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "fmt"
7 | "github.com/osgochina/dmicro/logger"
8 | "github.com/vprix/vncproxy/rfb"
9 | )
10 |
11 | // ClientVersionHandler vnc握手第一步
12 | // 1. 连接到vnc服务端后,读取其支持的rfb协议版本。
13 | // 2. 解析版本,判断该版本proxy客户端是否支持。
14 | // 3. 如果支持该版本,则发送支持的版本给vnc服务端
15 | type ClientVersionHandler struct{}
16 |
17 | func (*ClientVersionHandler) Handle(session rfb.ISession) error {
18 | if logger.IsDebug() {
19 | logger.Debugf(context.TODO(), "[Proxy客户端->VNC服务端]: 执行vnc握手第一步:[Version]")
20 | }
21 | var version [rfb.ProtoVersionLength]byte
22 |
23 | if err := binary.Read(session, binary.BigEndian, &version); err != nil {
24 | return err
25 | }
26 |
27 | major, minor, err := ParseProtoVersion(version[:])
28 | if err != nil {
29 | return err
30 | }
31 |
32 | pv := rfb.ProtoVersionUnknown
33 | if major == 3 {
34 | if minor >= 8 {
35 | pv = rfb.ProtoVersion38
36 | } else if minor >= 3 {
37 | pv = rfb.ProtoVersion38
38 | }
39 | }
40 | if pv == rfb.ProtoVersionUnknown {
41 | return fmt.Errorf("rfb协议握手失败; 不支持的版本 '%v'", string(version[:]))
42 | }
43 | session.SetProtocolVersion(string(version[:]))
44 |
45 | if err = binary.Write(session, binary.BigEndian, []byte(pv)); err != nil {
46 | return err
47 | }
48 | return session.Flush()
49 | }
50 |
51 | func ParseProtoVersion(pv []byte) (uint, uint, error) {
52 | var major, minor uint
53 |
54 | if len(pv) < rfb.ProtoVersionLength {
55 | return 0, 0, fmt.Errorf("协议版本的长度太短 (%v < %v)", len(pv), rfb.ProtoVersionLength)
56 | }
57 |
58 | l, err := fmt.Sscanf(string(pv), "RFB %d.%d\n", &major, &minor)
59 | if l != 2 {
60 | return 0, 0, fmt.Errorf("解析rfb协议失败")
61 | }
62 | if err != nil {
63 | return 0, 0, err
64 | }
65 |
66 | return major, minor, nil
67 | }
68 |
--------------------------------------------------------------------------------
/handler/ServerClientInitHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "github.com/osgochina/dmicro/logger"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | // ServerClientInitHandler vnc握手步骤第三步
11 | // 读取vnc客户端发送的是否支持共享屏幕标识
12 | type ServerClientInitHandler struct{}
13 |
14 | func (*ServerClientInitHandler) Handle(session rfb.ISession) error {
15 |
16 | if logger.IsDebug() {
17 | logger.Debugf(context.TODO(), "[VNC客户端->Proxy服务端]: 执行vnc握手第三步:[ClientInit]")
18 | }
19 | // 读取分享屏幕标识符,proxy会无视该标识,因为通过proxy链接的vnc服务端都是默认支持分享的。
20 | var shared uint8
21 | if err := binary.Read(session, binary.BigEndian, &shared); err != nil {
22 | return err
23 | }
24 | return nil
25 | }
26 |
--------------------------------------------------------------------------------
/handler/ServerMessageHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "fmt"
7 | "github.com/osgochina/dmicro/logger"
8 | "github.com/vprix/vncproxy/rfb"
9 | )
10 |
11 | // ServerMessageHandler vnc握手已结束,进入消息交互阶段
12 | // 启动两个协程,
13 | // 1. 处理proxy服务端的ServerMessage,在ServerMessageCh通道的消息都转发写入到该会话中.
14 | // 2. 从会话中读取clientMessages,并判断是否支持该消息,如果支持则转发到ClientMessageCh通道中。如果不支持则关闭该会话并报错。
15 | type ServerMessageHandler struct{}
16 |
17 | func (*ServerMessageHandler) Handle(session rfb.ISession) error {
18 | if logger.IsDebug() {
19 | logger.Debug(context.TODO(), "[VNC客户端->Proxy服务端]: vnc握手已结束,进入消息交互阶段[ServerMessageHandler]")
20 | }
21 |
22 | cfg := session.Options()
23 | var err error
24 | clientMessages := make(map[rfb.ClientMessageType]rfb.Message)
25 | for _, m := range cfg.Messages {
26 | clientMessages[rfb.ClientMessageType(m.Type())] = m
27 | }
28 |
29 | // 处理proxy服务端发送给vnc客户端的消息
30 | go func() {
31 | //defer wg.Done()
32 | for {
33 | select {
34 | case <-session.Wait(): // 如果收到退出信号,则退出协程
35 | return
36 | case msg := <-cfg.Input:
37 | // 收到proxy服务端消息,则转发写入到vnc客户端会话中。
38 | if logger.IsDebug() {
39 | logger.Debugf(context.TODO(), "[Proxy服务端->VNC客户端] 消息类型:%s,消息内容:%s", rfb.ServerMessageType(msg.Type()), msg.String())
40 | }
41 | if err = msg.Write(session); err != nil {
42 | cfg.ErrorCh <- err
43 | _ = session.Close()
44 | return
45 | }
46 | }
47 | }
48 | }()
49 |
50 | // 处理vnc客户端发送给proxy服务端的消息
51 | go func() {
52 | for {
53 | select {
54 | case <-session.Wait():
55 | return
56 | default:
57 | // 从vnc客户端的会话中读取消息类型
58 | var messageType rfb.ClientMessageType
59 | if err = binary.Read(session, binary.BigEndian, &messageType); err != nil {
60 | cfg.ErrorCh <- fmt.Errorf("读取vnc客户端数据失败,err:%v", err)
61 | _ = session.Close()
62 | return
63 | }
64 | // 判断vnc客户端发送的消息类型proxy服务端是否支持。
65 | msg, ok := clientMessages[messageType]
66 | if !ok {
67 | cfg.ErrorCh <- fmt.Errorf("不支持的消息类型: %v", messageType)
68 | _ = session.Close()
69 | return
70 | }
71 | // 从会话中读取消息内容
72 | parsedMsg, e := msg.Read(session)
73 | if e != nil {
74 | cfg.ErrorCh <- fmt.Errorf("解析消息失败,err:%v", e)
75 | _ = session.Close()
76 | return
77 | }
78 | if logger.IsDebug() {
79 | logger.Debugf(context.TODO(), "[VNC客户端->Proxy服务端] 消息类型:%s,消息内容:%s", rfb.ClientMessageType(parsedMsg.Type()), parsedMsg.String())
80 | }
81 |
82 | cfg.Output <- parsedMsg
83 | }
84 | }
85 | }()
86 | return nil
87 | }
88 |
--------------------------------------------------------------------------------
/handler/ServerSecurityHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "fmt"
7 | "github.com/osgochina/dmicro/logger"
8 | "github.com/vprix/vncproxy/rfb"
9 | )
10 |
11 | // ServerSecurityHandler vnc握手步骤第二步
12 | // 1.发送proxy服务端支持的安全认证套件数量及类型。
13 | // 2.读取vnc客户端支持的安全认证套件类型,判断是否支持,
14 | // 3.选择互相支持的安全认证套件进行认证,进入认证逻辑,如果认证成功则进入下一步,认证失败则报错。
15 | type ServerSecurityHandler struct{}
16 |
17 | func (*ServerSecurityHandler) Handle(session rfb.ISession) error {
18 | if logger.IsDebug() {
19 | logger.Debugf(context.TODO(), "[VNC客户端->Proxy服务端]: 执行vnc握手第二步:[Security]")
20 | }
21 | cfg := session.Options()
22 | var secType rfb.SecurityType
23 | if session.ProtocolVersion() == rfb.ProtoVersion37 || session.ProtocolVersion() == rfb.ProtoVersion38 {
24 | if err := binary.Write(session, binary.BigEndian, uint8(len(cfg.SecurityHandlers))); err != nil {
25 | return err
26 | }
27 |
28 | for _, sType := range cfg.SecurityHandlers {
29 | if err := binary.Write(session, binary.BigEndian, sType.Type()); err != nil {
30 | return err
31 | }
32 | }
33 | } else {
34 | st := uint32(0)
35 | for _, sType := range cfg.SecurityHandlers {
36 | if uint32(sType.Type()) > st {
37 | st = uint32(sType.Type())
38 | secType = sType.Type()
39 | }
40 | }
41 | if err := binary.Write(session, binary.BigEndian, st); err != nil {
42 | return err
43 | }
44 | }
45 | if err := session.Flush(); err != nil {
46 | return err
47 | }
48 |
49 | if session.ProtocolVersion() == rfb.ProtoVersion38 {
50 | if err := binary.Read(session, binary.BigEndian, &secType); err != nil {
51 | return err
52 | }
53 | }
54 | secTypes := make(map[rfb.SecurityType]rfb.ISecurityHandler)
55 | for _, sType := range cfg.SecurityHandlers {
56 | secTypes[sType.Type()] = sType
57 | }
58 |
59 | sType, ok := secTypes[secType]
60 | if !ok {
61 | return fmt.Errorf("security type %d not implemented", secType)
62 | }
63 |
64 | var authCode uint32
65 | authErr := sType.Auth(session)
66 | if authErr != nil {
67 | authCode = uint32(1)
68 | }
69 |
70 | if err := binary.Write(session, binary.BigEndian, authCode); err != nil {
71 | return err
72 | }
73 |
74 | if authErr == nil {
75 | if err := session.Flush(); err != nil {
76 | return err
77 | }
78 | session.SetSecurityHandler(sType)
79 | return nil
80 | }
81 |
82 | if session.ProtocolVersion() == rfb.ProtoVersion38 {
83 | if err := binary.Write(session, binary.BigEndian, uint32(len(authErr.Error()))); err != nil {
84 | return err
85 | }
86 | if err := binary.Write(session, binary.BigEndian, []byte(authErr.Error())); err != nil {
87 | return err
88 | }
89 | if err := session.Flush(); err != nil {
90 | return err
91 | }
92 | }
93 | return authErr
94 | }
95 |
--------------------------------------------------------------------------------
/handler/ServerServerInitHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "github.com/osgochina/dmicro/logger"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | // ServerServerInitHandler vnc握手步骤第四步
11 | // 1. 发送proxy服务端的参数信息,屏幕宽高,像素格式,桌面名称
12 | type ServerServerInitHandler struct{}
13 |
14 | func (*ServerServerInitHandler) Handle(session rfb.ISession) error {
15 | if logger.IsDebug() {
16 | logger.Debugf(context.TODO(), "[Proxy服务端->VNC客户端]: 执行vnc握手第四步:[ServerInit]")
17 | }
18 | if err := binary.Write(session, binary.BigEndian, session.Options().Width); err != nil {
19 | return err
20 | }
21 | if err := binary.Write(session, binary.BigEndian, session.Options().Height); err != nil {
22 | return err
23 | }
24 | if err := binary.Write(session, binary.BigEndian, session.Options().PixelFormat); err != nil {
25 | return err
26 | }
27 | desktopName := session.Options().DesktopName
28 | size := uint32(len(session.Options().DesktopName))
29 | if size == 0 {
30 | desktopName = []byte("vprix")
31 | size = uint32(len(desktopName))
32 | }
33 | if err := binary.Write(session, binary.BigEndian, size); err != nil {
34 | return err
35 | }
36 | if err := binary.Write(session, binary.BigEndian, desktopName); err != nil {
37 | return err
38 | }
39 | if logger.IsDebug() {
40 | logger.Debugf(context.TODO(), "[Proxy服务端->VNC客户端]: ServerInit[Width:%d,Height:%d,PixelFormat:%s,DesktopName:%s]",
41 | session.Options().Width, session.Options().Height, session.Options().PixelFormat, desktopName)
42 | }
43 | return session.Flush()
44 | }
45 |
--------------------------------------------------------------------------------
/handler/ServerVersionHandler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "fmt"
7 | "github.com/osgochina/dmicro/logger"
8 | "github.com/vprix/vncproxy/rfb"
9 | )
10 |
11 | // ServerVersionHandler vnc握手步骤第一步。
12 | // 1. vnc客户端链接到proxy服务端后,proxy服务端发送rfb版本信息。
13 | // 2. 发送版本信息后,接受vnc客户端返回的版本信息,进行版本匹配。
14 | // 3. 确定版本信息是相互支持的,如果不支持,则返回错误信息,如果支持则进行下一步。
15 | type ServerVersionHandler struct{}
16 |
17 | func (*ServerVersionHandler) Handle(session rfb.ISession) error {
18 | if logger.IsDebug() {
19 | logger.Debugf(context.TODO(), "[VNC客户端->Proxy服务端]: 执行vnc握手第一步:[Version]")
20 | }
21 | var version [rfb.ProtoVersionLength]byte
22 | if err := binary.Write(session, binary.BigEndian, []byte(rfb.ProtoVersion38)); err != nil {
23 | return err
24 | }
25 | if err := session.Flush(); err != nil {
26 | return err
27 | }
28 | if err := binary.Read(session, binary.BigEndian, &version); err != nil {
29 | return err
30 | }
31 | major, minor, err := ParseProtoVersion(version[:])
32 | if err != nil {
33 | return err
34 | }
35 |
36 | pv := rfb.ProtoVersionUnknown
37 | if major == 3 {
38 | if minor >= 8 {
39 | pv = rfb.ProtoVersion38
40 | } else if minor >= 3 {
41 | pv = rfb.ProtoVersion33
42 | }
43 | }
44 | if pv == rfb.ProtoVersionUnknown {
45 | return fmt.Errorf("rfb协议握手; 不支持的协议版本 '%v'", string(version[:]))
46 | }
47 |
48 | session.SetProtocolVersion(pv)
49 | return nil
50 | }
51 |
--------------------------------------------------------------------------------
/internal/dbuffer/buffer.go:
--------------------------------------------------------------------------------
1 | package dbuffer
2 |
3 | import (
4 | "io"
5 | )
6 |
7 | // ByteBuffer provides byte buffer, which can be used for minimizing
8 | // memory allocations.
9 | //
10 | // ByteBuffer may be used with functions appending data to the given []byte
11 | // slice. See example code for details.
12 | //
13 | // Use Get for obtaining an empty byte buffer.
14 | type ByteBuffer struct {
15 |
16 | // B is a byte buffer to use in append-like workloads.
17 | // See example code for details.
18 | B []byte
19 | }
20 |
21 | // Len returns the size of the byte buffer.
22 | func (b *ByteBuffer) Len() int {
23 | return len(b.B)
24 | }
25 |
26 | // ReadFrom implements io.ReaderFrom.
27 | //
28 | // The function appends all the data read from r to b.
29 | func (b *ByteBuffer) ReadFrom(r io.Reader) (int64, error) {
30 | p := b.B
31 | nStart := int64(len(p))
32 | nMax := int64(cap(p))
33 | n := nStart
34 | if nMax == 0 {
35 | nMax = 64
36 | p = make([]byte, nMax)
37 | } else {
38 | p = p[:nMax]
39 | }
40 | for {
41 | if n == nMax {
42 | nMax *= 2
43 | bNew := make([]byte, nMax)
44 | copy(bNew, p)
45 | p = bNew
46 | }
47 | nn, err := r.Read(p[n:])
48 | n += int64(nn)
49 | if err != nil {
50 | b.B = p[:n]
51 | n -= nStart
52 | if err == io.EOF {
53 | return n, nil
54 | }
55 | return n, err
56 | }
57 | }
58 | }
59 |
60 | // WriteTo implements io.WriterTo.
61 | func (b *ByteBuffer) WriteTo(w io.Writer) (int64, error) {
62 | n, err := w.Write(b.B)
63 | return int64(n), err
64 | }
65 |
66 | // Bytes returns b.B, i.e. all the bytes accumulated in the buffer.
67 | //
68 | // The purpose of this function is bytes.Buffer compatibility.
69 | func (b *ByteBuffer) Bytes() []byte {
70 | return b.B
71 | }
72 |
73 | // Write implements io.Writer - it appends p to ByteBuffer.B
74 | func (b *ByteBuffer) Write(p []byte) (int, error) {
75 | b.B = append(b.B, p...)
76 | return len(p), nil
77 | }
78 |
79 | // WriteByte appends the byte c to the buffer.
80 | //
81 | // The purpose of this function is bytes.Buffer compatibility.
82 | //
83 | // The function always returns nil.
84 | func (b *ByteBuffer) WriteByte(c byte) error {
85 | b.B = append(b.B, c)
86 | return nil
87 | }
88 |
89 | // WriteString appends s to ByteBuffer.B.
90 | func (b *ByteBuffer) WriteString(s string) (int, error) {
91 | b.B = append(b.B, s...)
92 | return len(s), nil
93 | }
94 |
95 | // Set sets ByteBuffer.B to p.
96 | func (b *ByteBuffer) Set(p []byte) {
97 | b.B = append(b.B[:0], p...)
98 | }
99 |
100 | // SetString sets ByteBuffer.B to s.
101 | func (b *ByteBuffer) SetString(s string) {
102 | b.B = append(b.B[:0], s...)
103 | }
104 |
105 | // String returns string representation of ByteBuffer.B.
106 | func (b *ByteBuffer) String() string {
107 | return string(b.B)
108 | }
109 |
110 | // Reset makes ByteBuffer.B empty.
111 | func (b *ByteBuffer) Reset() {
112 | b.B = b.B[:0]
113 | }
114 |
115 | // ChangeLen changes the buffer length.
116 | func (b *ByteBuffer) ChangeLen(newLen int) {
117 | if cap(b.B) < newLen {
118 | b.B = make([]byte, newLen)
119 | } else {
120 | b.B = b.B[:newLen]
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/internal/dbuffer/pool.go:
--------------------------------------------------------------------------------
1 | package dbuffer
2 |
3 | import (
4 | "sort"
5 | "sync"
6 | "sync/atomic"
7 | )
8 |
9 | const (
10 | minBitSize = 6 // 2**6=64 is a CPU cache line size
11 | steps = 20
12 |
13 | minSize = 1 << minBitSize
14 | maxSize = 1 << (minBitSize + steps - 1)
15 |
16 | calibrateCallsThreshold = 42000
17 | maxPercentile = 0.95
18 | )
19 |
20 | // BufferPool represents byte buffer pool.
21 | //
22 | // Distinct pools may be used for distinct types of byte buffers.
23 | // Properly determined byte buffer types with their own pools may help reducing
24 | // memory waste.
25 | type BufferPool struct {
26 | calls [steps]uint64
27 | calibrating uint64
28 |
29 | defaultSize uint64
30 | maxSize uint64
31 |
32 | pool sync.Pool
33 | }
34 |
35 | var defaultBufferPool BufferPool
36 |
37 | // GetByteBuffer returns an empty byte buffer from the pool.
38 | //
39 | // Got byte buffer may be returned to the pool via Put call.
40 | // This reduces the number of memory allocations required for byte buffer
41 | // management.
42 | func GetByteBuffer() *ByteBuffer { return defaultBufferPool.Get() }
43 |
44 | // Get returns new byte buffer with zero length.
45 | //
46 | // The byte buffer may be returned to the pool via Put after the use
47 | // in order to minimize GC overhead.
48 | func (p *BufferPool) Get() *ByteBuffer {
49 | v := p.pool.Get()
50 | if v != nil {
51 | return v.(*ByteBuffer)
52 | }
53 | return &ByteBuffer{
54 | B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)),
55 | }
56 | }
57 |
58 | // ReleaseByteBuffer returns byte buffer to the pool.
59 | //
60 | // ByteBuffer.B mustn't be touched after returning it to the pool.
61 | // Otherwise data races will occur.
62 | func ReleaseByteBuffer(b *ByteBuffer) { defaultBufferPool.Put(b) }
63 |
64 | // Put releases byte buffer obtained via Get to the pool.
65 | //
66 | // The buffer mustn't be accessed after returning to the pool.
67 | func (p *BufferPool) Put(b *ByteBuffer) {
68 | idx := index(len(b.B))
69 |
70 | if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
71 | p.calibrate()
72 | }
73 |
74 | maxSize := int(atomic.LoadUint64(&p.maxSize))
75 | if maxSize == 0 || cap(b.B) <= maxSize {
76 | b.Reset()
77 | p.pool.Put(b)
78 | }
79 | }
80 |
81 | func (p *BufferPool) calibrate() {
82 | if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
83 | return
84 | }
85 |
86 | a := make(callSizes, 0, steps)
87 | var callsSum uint64
88 | for i := uint64(0); i < steps; i++ {
89 | calls := atomic.SwapUint64(&p.calls[i], 0)
90 | callsSum += calls
91 | a = append(a, callSize{
92 | calls: calls,
93 | size: minSize << i,
94 | })
95 | }
96 | sort.Sort(a)
97 |
98 | defaultSize := a[0].size
99 | maxSize := defaultSize
100 |
101 | maxSum := uint64(float64(callsSum) * maxPercentile)
102 | callsSum = 0
103 | for i := 0; i < steps; i++ {
104 | if callsSum > maxSum {
105 | break
106 | }
107 | callsSum += a[i].calls
108 | size := a[i].size
109 | if size > maxSize {
110 | maxSize = size
111 | }
112 | }
113 |
114 | atomic.StoreUint64(&p.defaultSize, defaultSize)
115 | atomic.StoreUint64(&p.maxSize, maxSize)
116 |
117 | atomic.StoreUint64(&p.calibrating, 0)
118 | }
119 |
120 | type callSize struct {
121 | calls uint64
122 | size uint64
123 | }
124 |
125 | type callSizes []callSize
126 |
127 | func (ci callSizes) Len() int {
128 | return len(ci)
129 | }
130 |
131 | func (ci callSizes) Less(i, j int) bool {
132 | return ci[i].calls > ci[j].calls
133 | }
134 |
135 | func (ci callSizes) Swap(i, j int) {
136 | ci[i], ci[j] = ci[j], ci[i]
137 | }
138 |
139 | func index(n int) int {
140 | n--
141 | n >>= minBitSize
142 | idx := 0
143 | for n > 0 {
144 | n >>= 1
145 | idx++
146 | }
147 | if idx >= steps {
148 | idx = steps - 1
149 | }
150 | return idx
151 | }
152 |
--------------------------------------------------------------------------------
/internal/syncPool/sync_pool.go:
--------------------------------------------------------------------------------
1 | package syncPool
2 |
3 | import "sync"
4 |
5 | type SyncPool struct {
6 | pool sync.Pool
7 | newFunc func() interface{}
8 | initFunc func(interface{})
9 | }
10 |
11 | // NewSyncPool 创建对象池
12 | // newFunc:创建对象的方法
13 | // init: 对象被创建后,返回之前,调用该方法初始化对象
14 | func NewSyncPool(newFunc func() interface{}, init func(interface{})) *SyncPool {
15 | return &SyncPool{
16 | newFunc: newFunc,
17 | initFunc: init,
18 | pool: sync.Pool{
19 | New: newFunc,
20 | },
21 | }
22 | }
23 |
24 | // Get 获取对象
25 | func (that *SyncPool) Get() interface{} {
26 | var object = that.pool.Get()
27 | if that.initFunc != nil {
28 | that.initFunc(object)
29 | }
30 | return object
31 | }
32 |
33 | // Put 把对象放回对象池
34 | func (that *SyncPool) Put(value interface{}) {
35 | that.pool.Put(value)
36 | }
37 |
--------------------------------------------------------------------------------
/messages/clientClientCutText.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // ClientCutText 客户端发送剪切板内容到服务端
10 | type ClientCutText struct {
11 | _ [3]byte // 填充
12 | Length uint32 // 剪切板内容长度
13 | Text []byte // 剪切板
14 | }
15 |
16 | func (that *ClientCutText) Clone() rfb.Message {
17 | c := &ClientCutText{
18 | Length: that.Length,
19 | Text: that.Text,
20 | }
21 | return c
22 | }
23 | func (that *ClientCutText) Supported(rfb.ISession) bool {
24 | return true
25 | }
26 |
27 | // String
28 | func (that *ClientCutText) String() string {
29 | return fmt.Sprintf("length: %d", that.Length)
30 | }
31 |
32 | // Type returns MessageType
33 | func (that *ClientCutText) Type() rfb.MessageType {
34 | return rfb.MessageType(rfb.ClientCutText)
35 | }
36 |
37 | // Read 从会话中解析消息内容
38 | func (that *ClientCutText) Read(session rfb.ISession) (rfb.Message, error) {
39 | msg := &ClientCutText{}
40 | // 读取填充字节
41 | var pad [3]byte
42 | if err := binary.Read(session, binary.BigEndian, &pad); err != nil {
43 | return nil, err
44 | }
45 | // 读取消息长度
46 | if err := binary.Read(session, binary.BigEndian, &msg.Length); err != nil {
47 | return nil, err
48 | }
49 | // 读取指定长度的消息内容
50 | msg.Text = make([]byte, msg.Length)
51 | if err := binary.Read(session, binary.BigEndian, &msg.Text); err != nil {
52 | return nil, err
53 | }
54 | return msg, nil
55 | }
56 |
57 | // Write 把消息按协议格式写入会话
58 | func (that *ClientCutText) Write(session rfb.ISession) error {
59 | // 写入消息类型
60 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
61 | return err
62 | }
63 |
64 | // 写入3给字节的填充
65 | var pad [3]byte
66 | if err := binary.Write(session, binary.BigEndian, &pad); err != nil {
67 | return err
68 | }
69 |
70 | if uint32(len(that.Text)) > that.Length {
71 | that.Length = uint32(len(that.Text))
72 | }
73 |
74 | // 写入剪切板内容长度
75 | if err := binary.Write(session, binary.BigEndian, that.Length); err != nil {
76 | return err
77 | }
78 |
79 | // 写入消息内容
80 | if err := binary.Write(session, binary.BigEndian, that.Text); err != nil {
81 | return err
82 | }
83 |
84 | return session.Flush()
85 | }
86 |
--------------------------------------------------------------------------------
/messages/clientClientFence.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // ClientFence 支持 Fence扩展的客户端发送此扩展以请求数据流的同步。
10 | type ClientFence struct {
11 | flags uint32
12 | length uint8
13 | payload []byte
14 | }
15 |
16 | func (that *ClientFence) Clone() rfb.Message {
17 |
18 | c := &ClientFence{
19 | flags: that.flags,
20 | length: that.length,
21 | payload: that.payload,
22 | }
23 | return c
24 | }
25 | func (that *ClientFence) Supported(session rfb.ISession) bool {
26 | return true
27 | }
28 | func (that *ClientFence) String() string {
29 | return fmt.Sprintf("(type=%d)", that.Type())
30 | }
31 |
32 | func (that *ClientFence) Type() rfb.MessageType {
33 | return rfb.MessageType(rfb.ClientFence)
34 | }
35 |
36 | // 读取数据
37 | func (that *ClientFence) Read(session rfb.ISession) (rfb.Message, error) {
38 | msg := &ClientFence{}
39 | bytes := make([]byte, 3)
40 | //c.Read(bytes)
41 | if _, err := session.Read(bytes); err != nil {
42 | return nil, err
43 | }
44 | if err := binary.Read(session, binary.BigEndian, &msg.flags); err != nil {
45 | return nil, err
46 | }
47 | if err := binary.Read(session, binary.BigEndian, &msg.length); err != nil {
48 | return nil, err
49 | }
50 | bytes = make([]byte, msg.length)
51 | if _, err := session.Read(bytes); err != nil {
52 | return nil, err
53 | }
54 | msg.payload = bytes
55 | return msg, nil
56 | }
57 |
58 | func (that *ClientFence) Write(session rfb.ISession) error {
59 | // 写入消息类型
60 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
61 | return err
62 | }
63 | //写入填充
64 | var pad [3]byte
65 | if err := binary.Write(session, binary.BigEndian, pad); err != nil {
66 | return err
67 | }
68 |
69 | if err := binary.Write(session, binary.BigEndian, that.flags); err != nil {
70 | return err
71 | }
72 |
73 | if err := binary.Write(session, binary.BigEndian, that.length); err != nil {
74 | return err
75 | }
76 | if err := binary.Write(session, binary.BigEndian, that.payload); err != nil {
77 | return err
78 | }
79 | return session.Flush()
80 | }
81 |
--------------------------------------------------------------------------------
/messages/clientEnableContinuousUpdates.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // EnableContinuousUpdates 客户端发送连续更新消息
10 | type EnableContinuousUpdates struct {
11 | flag uint8
12 | x uint16
13 | y uint16
14 | width uint16
15 | height uint16
16 | }
17 |
18 | func (that *EnableContinuousUpdates) Clone() rfb.Message {
19 |
20 | c := &EnableContinuousUpdates{
21 | flag: that.flag,
22 | x: that.x,
23 | y: that.y,
24 | width: that.width,
25 | height: that.height,
26 | }
27 | return c
28 | }
29 | func (that *EnableContinuousUpdates) Supported(rfb.ISession) bool {
30 | return true
31 | }
32 | func (that *EnableContinuousUpdates) String() string {
33 | return fmt.Sprintf("(type=%d,flag=%d,x=%d,y=%d,width=%d,height=%d)", that.Type(), that.flag, that.x, that.y, that.width, that.height)
34 | }
35 |
36 | func (that *EnableContinuousUpdates) Type() rfb.MessageType {
37 | return rfb.MessageType(rfb.EnableContinuousUpdates)
38 | }
39 |
40 | // 读取数据
41 | func (that *EnableContinuousUpdates) Read(session rfb.ISession) (rfb.Message, error) {
42 | msg := &EnableContinuousUpdates{}
43 | if err := binary.Read(session, binary.BigEndian, &msg.flag); err != nil {
44 | return nil, err
45 | }
46 | if err := binary.Read(session, binary.BigEndian, &msg.x); err != nil {
47 | return nil, err
48 | }
49 | if err := binary.Read(session, binary.BigEndian, &msg.y); err != nil {
50 | return nil, err
51 | }
52 | if err := binary.Read(session, binary.BigEndian, &msg.width); err != nil {
53 | return nil, err
54 | }
55 | if err := binary.Read(session, binary.BigEndian, &msg.height); err != nil {
56 | return nil, err
57 | }
58 | return msg, nil
59 | }
60 |
61 | func (that *EnableContinuousUpdates) Write(session rfb.ISession) error {
62 | // 写入消息类型
63 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
64 | return err
65 | }
66 | if err := binary.Write(session, binary.BigEndian, that.flag); err != nil {
67 | return err
68 | }
69 | if err := binary.Write(session, binary.BigEndian, that.x); err != nil {
70 | return err
71 | }
72 | if err := binary.Write(session, binary.BigEndian, that.y); err != nil {
73 | return err
74 | }
75 | if err := binary.Write(session, binary.BigEndian, that.width); err != nil {
76 | return err
77 | }
78 | if err := binary.Write(session, binary.BigEndian, that.height); err != nil {
79 | return err
80 | }
81 | return session.Flush()
82 | }
83 |
--------------------------------------------------------------------------------
/messages/clientFramebufferUpdateRequest.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // FramebufferUpdateRequest 请求帧缓存更新消息
10 | // incremental 通常为非 0 值,服务器只需要发有变化的图像信息。
11 | // 当客户端丢失了缓存的帧缓冲信息,或者刚建立连接,需要完整的图像信息时,
12 | // 将 incremental 置为 0,获取全量信息。
13 | type FramebufferUpdateRequest struct {
14 | Inc uint8 // 是否是增量请求
15 | X, Y uint16 // 区域的起始坐标
16 | Width, Height uint16 // 区域的宽度和高度
17 | }
18 |
19 | func (that *FramebufferUpdateRequest) Clone() rfb.Message {
20 |
21 | c := &FramebufferUpdateRequest{
22 | Inc: that.Inc,
23 | X: that.X,
24 | Y: that.Y,
25 | Width: that.Width,
26 | Height: that.Height,
27 | }
28 | return c
29 | }
30 | func (that *FramebufferUpdateRequest) Supported(session rfb.ISession) bool {
31 | return true
32 | }
33 |
34 | // String returns string
35 | func (that *FramebufferUpdateRequest) String() string {
36 | return fmt.Sprintf("incremental: %d, x: %d, y: %d, width: %d, height: %d", that.Inc, that.X, that.Y, that.Width, that.Height)
37 | }
38 |
39 | // Type returns MessageType
40 | func (that *FramebufferUpdateRequest) Type() rfb.MessageType {
41 | return rfb.MessageType(rfb.FramebufferUpdateRequest)
42 | }
43 |
44 | // Read 从会话中解析消息内容
45 | func (that *FramebufferUpdateRequest) Read(session rfb.ISession) (rfb.Message, error) {
46 | msg := &FramebufferUpdateRequest{}
47 | if err := binary.Read(session, binary.BigEndian, msg); err != nil {
48 | return nil, err
49 | }
50 | return msg, nil
51 | }
52 |
53 | // Write 把消息按协议格式写入会话
54 | func (that *FramebufferUpdateRequest) Write(session rfb.ISession) error {
55 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
56 | return err
57 | }
58 | if err := binary.Write(session, binary.BigEndian, that); err != nil {
59 | return err
60 | }
61 | return session.Flush()
62 | }
63 |
--------------------------------------------------------------------------------
/messages/clientKeyEvent.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // KeyEvent 键盘按键事件
10 | type KeyEvent struct {
11 | Down uint8 // 1 表示键位按下,0 表示弹起
12 | _ [2]byte // 对齐字节,方便解析
13 | Key rfb.Key // 表示具体的键位,https://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.html#keysym_encoding
14 | }
15 |
16 | func (that *KeyEvent) Clone() rfb.Message {
17 |
18 | c := &KeyEvent{
19 | Down: that.Down,
20 | Key: that.Key,
21 | }
22 | return c
23 | }
24 | func (that *KeyEvent) Supported(session rfb.ISession) bool {
25 | return true
26 | }
27 |
28 | // String returns string
29 | func (that *KeyEvent) String() string {
30 | return fmt.Sprintf("down: %d, key: %v", that.Down, that.Key)
31 | }
32 |
33 | // Type returns MessageType
34 | func (that *KeyEvent) Type() rfb.MessageType {
35 | return rfb.MessageType(rfb.KeyEvent)
36 | }
37 |
38 | // Read 从会话中解析消息内容
39 | func (that *KeyEvent) Read(session rfb.ISession) (rfb.Message, error) {
40 | msg := &KeyEvent{}
41 | if err := binary.Read(session, binary.BigEndian, msg); err != nil {
42 | return nil, err
43 | }
44 | return msg, nil
45 | }
46 |
47 | // Write 把消息按协议格式写入会话
48 | func (that *KeyEvent) Write(session rfb.ISession) error {
49 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
50 | return err
51 | }
52 | if err := binary.Write(session, binary.BigEndian, that); err != nil {
53 | return err
54 | }
55 | return session.Flush()
56 | }
57 |
--------------------------------------------------------------------------------
/messages/clientPointerEvent.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // PointerEvent 鼠标事件
10 | type PointerEvent struct {
11 | Mask uint8 //8 位掩码,表示键位状态,1为按下,0为弹起
12 | X, Y uint16 // 当前 X,Y 坐标
13 | }
14 |
15 | func (that *PointerEvent) Clone() rfb.Message {
16 |
17 | c := &PointerEvent{
18 | Mask: that.Mask,
19 | X: that.X,
20 | Y: that.Y,
21 | }
22 | return c
23 | }
24 | func (that *PointerEvent) Supported(session rfb.ISession) bool {
25 | return true
26 | }
27 |
28 | // String returns string
29 | func (that *PointerEvent) String() string {
30 | return fmt.Sprintf("mask %d, x: %d, y: %d", that.Mask, that.X, that.Y)
31 | }
32 |
33 | // Type returns MessageType
34 | func (that *PointerEvent) Type() rfb.MessageType {
35 | return rfb.MessageType(rfb.PointerEvent)
36 | }
37 |
38 | // Read 从会话中解析消息内容
39 | func (that *PointerEvent) Read(session rfb.ISession) (rfb.Message, error) {
40 | msg := &PointerEvent{}
41 | if err := binary.Read(session, binary.BigEndian, msg); err != nil {
42 | return nil, err
43 | }
44 | return msg, nil
45 | }
46 |
47 | // Write 把消息按协议格式写入会话
48 | func (that *PointerEvent) Write(session rfb.ISession) error {
49 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
50 | return err
51 | }
52 | if err := binary.Write(session, binary.BigEndian, that); err != nil {
53 | return err
54 | }
55 | return session.Flush()
56 | }
57 |
--------------------------------------------------------------------------------
/messages/clientQEMUExtKeyEvent.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | type QEMUExtKeyEvent struct {
10 | SubMessageType uint8 // submessage type
11 | DownFlag uint16 // down-flag
12 | KeySym rfb.Key // key symbol
13 | KeyCode uint32 // scan code
14 | }
15 |
16 | func (that *QEMUExtKeyEvent) Clone() rfb.Message {
17 |
18 | c := &QEMUExtKeyEvent{
19 | SubMessageType: that.SubMessageType,
20 | DownFlag: that.DownFlag,
21 | KeySym: that.KeySym,
22 | KeyCode: that.KeyCode,
23 | }
24 | return c
25 | }
26 | func (that *QEMUExtKeyEvent) Supported(session rfb.ISession) bool {
27 | return true
28 | }
29 | func (that *QEMUExtKeyEvent) Type() rfb.MessageType {
30 | return rfb.MessageType(rfb.QEMUExtendedKeyEvent)
31 | }
32 |
33 | func (that *QEMUExtKeyEvent) String() string {
34 | return fmt.Sprintf("SubMessageType=%d,DownFlag=%d,KeySym=%d,KeyCode=%d", that.SubMessageType, that.DownFlag, that.KeySym, that.KeyCode)
35 | }
36 |
37 | func (that *QEMUExtKeyEvent) Read(session rfb.ISession) (rfb.Message, error) {
38 | msg := &QEMUExtKeyEvent{}
39 | if err := binary.Read(session, binary.BigEndian, msg); err != nil {
40 | return nil, err
41 | }
42 | return msg, nil
43 | }
44 |
45 | func (that *QEMUExtKeyEvent) Write(session rfb.ISession) error {
46 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
47 | return err
48 | }
49 | if err := binary.Write(session, binary.BigEndian, that); err != nil {
50 | return err
51 | }
52 | return nil
53 | }
54 |
--------------------------------------------------------------------------------
/messages/clientSetDesktopSize.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/internal/dbuffer"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | // SetDesktopSize 客户端发起设置桌面大小
11 | type SetDesktopSize struct {
12 | buff *dbuffer.ByteBuffer
13 | }
14 |
15 | func (that *SetDesktopSize) Clone() rfb.Message {
16 |
17 | c := &SetDesktopSize{
18 | buff: dbuffer.GetByteBuffer(),
19 | }
20 | _, _ = c.buff.Write(that.buff.Bytes())
21 | return c
22 | }
23 | func (that *SetDesktopSize) Supported(rfb.ISession) bool {
24 | return true
25 | }
26 | func (that *SetDesktopSize) String() string {
27 | return fmt.Sprintf("(type=%d)", that.Type())
28 | }
29 |
30 | func (that *SetDesktopSize) Type() rfb.MessageType {
31 | return rfb.MessageType(rfb.SetDesktopSize)
32 | }
33 |
34 | // 读取数据
35 | func (that *SetDesktopSize) Read(session rfb.ISession) (rfb.Message, error) {
36 | msg := &SetDesktopSize{buff: dbuffer.GetByteBuffer()}
37 | pad := make([]byte, 1)
38 | if _, err := session.Read(pad); err != nil {
39 | return nil, err
40 | }
41 | var width uint16
42 | _, _ = msg.buff.Write(pad)
43 | if err := binary.Read(session, binary.BigEndian, &width); err != nil {
44 | return nil, err
45 | }
46 | if err := binary.Write(msg.buff, binary.BigEndian, width); err != nil {
47 | return nil, err
48 | }
49 | var height uint16
50 | if err := binary.Read(session, binary.BigEndian, &height); err != nil {
51 | return nil, err
52 | }
53 | if err := binary.Write(msg.buff, binary.BigEndian, height); err != nil {
54 | return nil, err
55 | }
56 | var numberOfScreens uint8
57 | if err := binary.Read(session, binary.BigEndian, &numberOfScreens); err != nil {
58 | return nil, err
59 | }
60 | if err := binary.Write(msg.buff, binary.BigEndian, numberOfScreens); err != nil {
61 | return nil, err
62 | }
63 | pad = make([]byte, 1)
64 | if err := binary.Read(session, binary.BigEndian, &pad); err != nil {
65 | return nil, err
66 | }
67 | _, _ = msg.buff.Write(pad)
68 | for i := 0; i < int(numberOfScreens); i++ {
69 | b, err := that.readExtendedDesktopSize(session)
70 | if err != nil {
71 | return nil, err
72 | }
73 | _, _ = msg.buff.Write(b)
74 | }
75 | return msg, nil
76 | }
77 |
78 | func (that *SetDesktopSize) Write(session rfb.ISession) error {
79 | // 写入消息类型
80 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
81 | return err
82 | }
83 | _, err := session.Write(that.buff.Bytes())
84 | if err != nil {
85 | return err
86 | }
87 | dbuffer.ReleaseByteBuffer(that.buff)
88 | that.buff = nil
89 | return session.Flush()
90 | }
91 |
92 | // No. of bytes Type Description
93 | // 4 U32 id
94 | // 2 U16 x-position
95 | // 2 U16 y-position
96 | // 2 U16 width
97 | // 2 U16 height
98 | // 4 U32 flags
99 | func (that *SetDesktopSize) readExtendedDesktopSize(session rfb.ISession) ([]byte, error) {
100 | desktopSizeBuf := make([]byte, 16)
101 | if err := binary.Read(session, binary.BigEndian, &desktopSizeBuf); err != nil {
102 | return nil, err
103 | }
104 | return desktopSizeBuf, nil
105 | }
106 |
--------------------------------------------------------------------------------
/messages/clientSetEncodings.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/gogf/gf/v2/text/gstr"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | // SetEncodings 设置编码类型消息
11 | type SetEncodings struct {
12 | _ [1]byte // padding
13 | EncNum uint16 // number-of-encodings
14 | Encodings []rfb.EncodingType
15 | }
16 |
17 | func (that *SetEncodings) Clone() rfb.Message {
18 | c := &SetEncodings{
19 | EncNum: that.EncNum,
20 | Encodings: that.Encodings,
21 | }
22 | return c
23 | }
24 | func (that *SetEncodings) Supported(_ rfb.ISession) bool {
25 | return true
26 | }
27 |
28 | // String return string
29 | func (that *SetEncodings) String() string {
30 | s := fmt.Sprintf("encNum: %d, encodings[]: ", that.EncNum)
31 | var s1 []string
32 | for _, e := range that.Encodings {
33 | s1 = append(s1, fmt.Sprintf("%s", e))
34 | }
35 | return s + gstr.Implode(",", s1)
36 | }
37 |
38 | // Type returns MessageType
39 | func (that *SetEncodings) Type() rfb.MessageType {
40 | return rfb.MessageType(rfb.SetEncodings)
41 | }
42 |
43 | // Read 从会话中解析消息内容
44 | func (that *SetEncodings) Read(session rfb.ISession) (rfb.Message, error) {
45 | msg := &SetEncodings{}
46 | //读取一个字节的填充数据
47 | var pad [1]byte
48 | if err := binary.Read(session, binary.BigEndian, &pad); err != nil {
49 | return nil, err
50 | }
51 | //读取编码格式数量
52 | if err := binary.Read(session, binary.BigEndian, &msg.EncNum); err != nil {
53 | return nil, err
54 | }
55 | var enc rfb.EncodingType
56 | //读取指定数据量的编码信息
57 | for i := uint16(0); i < msg.EncNum; i++ {
58 | if err := binary.Read(session, binary.BigEndian, &enc); err != nil {
59 | return nil, err
60 | }
61 | msg.Encodings = append(msg.Encodings, enc)
62 | }
63 | if err := session.SetEncodings(msg.Encodings); err != nil {
64 | return nil, err
65 | }
66 |
67 | return msg, nil
68 | }
69 |
70 | // Write 把消息按协议格式写入会话
71 | func (that *SetEncodings) Write(session rfb.ISession) error {
72 | // 写入消息类型
73 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
74 | return err
75 | }
76 | // 写入一个字节的填充数据
77 | var pad [1]byte
78 | if err := binary.Write(session, binary.BigEndian, pad); err != nil {
79 | return err
80 | }
81 | // 写入当前支持的编码类型的数量
82 | if uint16(len(that.Encodings)) > that.EncNum {
83 | that.EncNum = uint16(len(that.Encodings))
84 | }
85 | if err := binary.Write(session, binary.BigEndian, that.EncNum); err != nil {
86 | return err
87 | }
88 | // 写入当前支持的编码类型的列表
89 | for _, enc := range that.Encodings {
90 | if err := binary.Write(session, binary.BigEndian, enc); err != nil {
91 | return err
92 | }
93 | }
94 | return session.Flush()
95 | }
96 |
--------------------------------------------------------------------------------
/messages/clientSetPixelFormat.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // SetPixelFormat 设置像素格式
10 | type SetPixelFormat struct {
11 | _ [3]byte // 填充
12 | PF rfb.PixelFormat // 像素格式
13 | }
14 |
15 | func (that *SetPixelFormat) Clone() rfb.Message {
16 | c := &SetPixelFormat{
17 | PF: that.PF,
18 | }
19 | return c
20 | }
21 |
22 | func (that *SetPixelFormat) Supported(session rfb.ISession) bool {
23 | return true
24 | }
25 |
26 | // String returns string
27 | func (that *SetPixelFormat) String() string {
28 | return fmt.Sprintf("%s", that.PF)
29 | }
30 |
31 | // Type returns MessageType
32 | func (that *SetPixelFormat) Type() rfb.MessageType {
33 | return rfb.MessageType(rfb.SetPixelFormat)
34 | }
35 |
36 | // Write 写入像素格式
37 | func (that *SetPixelFormat) Write(session rfb.ISession) error {
38 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
39 | return err
40 | }
41 |
42 | if err := binary.Write(session, binary.BigEndian, that); err != nil {
43 | return err
44 | }
45 |
46 | pf := session.Options().PixelFormat
47 | // Invalidate the color map.
48 | if pf.TrueColor != 0 {
49 | session.SetColorMap(rfb.ColorMap{})
50 | }
51 |
52 | return session.Flush()
53 | }
54 |
55 | // Read 从链接中读取像素格式到当前对象
56 | func (that *SetPixelFormat) Read(session rfb.ISession) (rfb.Message, error) {
57 | msg := &SetPixelFormat{}
58 | if err := binary.Read(session, binary.BigEndian, msg); err != nil {
59 | return nil, err
60 | }
61 | return msg, nil
62 | }
63 |
--------------------------------------------------------------------------------
/messages/default_message.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import "github.com/vprix/vncproxy/rfb"
4 |
5 | var (
6 | // DefaultClientMessage 默认client支持的消息
7 | DefaultClientMessage = []rfb.Message{
8 | &SetPixelFormat{},
9 | &SetEncodings{},
10 | &FramebufferUpdateRequest{},
11 | &KeyEvent{},
12 | &PointerEvent{},
13 | &ClientCutText{},
14 | &ClientFence{},
15 | &SetDesktopSize{},
16 | &EnableContinuousUpdates{},
17 | }
18 | // DefaultServerMessages 默认server支持的消息
19 | DefaultServerMessages = []rfb.Message{
20 | &FramebufferUpdate{},
21 | &SetColorMapEntries{},
22 | &Bell{},
23 | &ServerCutText{},
24 | &EndOfContinuousUpdates{},
25 | &ServerFence{},
26 | }
27 | )
28 |
--------------------------------------------------------------------------------
/messages/serverBell.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // Bell 响铃
10 | type Bell struct{}
11 |
12 | func (that *Bell) Clone() rfb.Message {
13 | return &Bell{}
14 | }
15 | func (that *Bell) Supported(session rfb.ISession) bool {
16 | return true
17 | }
18 |
19 | // String return string
20 | func (that *Bell) String() string {
21 | return fmt.Sprintf("bell")
22 | }
23 |
24 | // Type 消息类型
25 | func (that *Bell) Type() rfb.MessageType {
26 | return rfb.MessageType(rfb.Bell)
27 | }
28 |
29 | // Read 响铃消息只有消息类型,没有数据
30 | func (that *Bell) Read(session rfb.ISession) (rfb.Message, error) {
31 | return &Bell{}, nil
32 | }
33 |
34 | // Write 写入响应消息类型
35 | func (that *Bell) Write(session rfb.ISession) error {
36 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
37 | return err
38 | }
39 | return session.Flush()
40 | }
41 |
--------------------------------------------------------------------------------
/messages/serverEndOfContinuousUpdates.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // EndOfContinuousUpdates Bell 结束连续更新
10 | type EndOfContinuousUpdates struct{}
11 |
12 | func (that *EndOfContinuousUpdates) Clone() rfb.Message {
13 | return &EndOfContinuousUpdates{}
14 | }
15 | func (that *EndOfContinuousUpdates) Supported(session rfb.ISession) bool {
16 | return true
17 | }
18 |
19 | // String return string
20 | func (that *EndOfContinuousUpdates) String() string {
21 | return fmt.Sprintf("EndOfContinuousUpdates")
22 | }
23 |
24 | // Type 消息类型
25 | func (that *EndOfContinuousUpdates) Type() rfb.MessageType {
26 | return rfb.MessageType(rfb.EndOfContinuousUpdates)
27 | }
28 |
29 | // Read 响铃消息只有消息类型,没有数据
30 | func (that *EndOfContinuousUpdates) Read(session rfb.ISession) (rfb.Message, error) {
31 | return &EndOfContinuousUpdates{}, nil
32 | }
33 |
34 | // Write 写入响应消息类型
35 | func (that *EndOfContinuousUpdates) Write(session rfb.ISession) error {
36 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
37 | return err
38 | }
39 | return session.Flush()
40 | }
41 |
--------------------------------------------------------------------------------
/messages/serverFramebufferUpdate.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/osgochina/dmicro/logger"
7 | "github.com/vprix/vncproxy/rfb"
8 | "golang.org/x/net/context"
9 | )
10 |
11 | // FramebufferUpdate 帧缓冲更新
12 | type FramebufferUpdate struct {
13 | _ [1]byte // 填充
14 | NumRect uint16 // 多少个像素数据的矩形
15 | Rects []*rfb.Rectangle // 像素数据的矩形列表
16 | }
17 |
18 | func (that *FramebufferUpdate) String() string {
19 | return fmt.Sprintf("rects %d rectangle[]: { %v }", that.NumRect, that.Rects)
20 | }
21 | func (that *FramebufferUpdate) Supported(rfb.ISession) bool {
22 | return true
23 | }
24 |
25 | func (that *FramebufferUpdate) Type() rfb.MessageType {
26 | return rfb.MessageType(rfb.FramebufferUpdate)
27 | }
28 |
29 | // 读取帧数据
30 | func (that *FramebufferUpdate) Read(session rfb.ISession) (rfb.Message, error) {
31 | msg := &FramebufferUpdate{}
32 | var pad [1]byte
33 | if err := binary.Read(session, binary.BigEndian, &pad); err != nil {
34 | return nil, err
35 | }
36 |
37 | if err := binary.Read(session, binary.BigEndian, &msg.NumRect); err != nil {
38 | return nil, err
39 | }
40 | if logger.IsDebug() {
41 | logger.Debugf(context.TODO(), "FramebufferUpdate->读取帧数据有 %d 个矩形-------", msg.NumRect)
42 | }
43 |
44 | for i := uint16(0); i < msg.NumRect; i++ {
45 | rect := rfb.NewRectangle()
46 | if logger.IsDebug() {
47 | logger.Debugf(context.TODO(), "开始读取第 %d 个矩形", i)
48 | }
49 |
50 | if err := rect.Read(session); err != nil {
51 | return nil, err
52 | }
53 | // 如果服务器告诉客户端这是最后一个rect,则停止解析
54 | if rect.EncType == rfb.EncLastRectPseudo {
55 | if logger.IsDebug() {
56 | logger.Debugf(context.TODO(), "读取第 %d 个矩形成功,但是是最后一帧:EncLastRectPseudo", i)
57 | }
58 | msg.Rects = append(msg.Rects, rect)
59 | break
60 | }
61 | //if rect.EncType == rfb.EncDesktopSizePseudo {
62 | // session.ResetAllEncodings()
63 | //}
64 | if logger.IsDebug() {
65 | logger.Debugf(context.TODO(), "结束读取第 %d 个矩形,宽高:(%dx%d) 编码格式:%s", i, rect.Width, rect.Height, rect.EncType)
66 | }
67 | msg.Rects = append(msg.Rects, rect)
68 | }
69 | return msg, nil
70 | }
71 |
72 | // 写入帧数据
73 | func (that *FramebufferUpdate) Write(session rfb.ISession) error {
74 | // 写入消息类型
75 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
76 | return err
77 | }
78 | // 填充字节
79 | var pad [1]byte
80 | if err := binary.Write(session, binary.BigEndian, pad); err != nil {
81 | return err
82 | }
83 | // 写入矩形数量
84 | if err := binary.Write(session, binary.BigEndian, that.NumRect); err != nil {
85 | return err
86 | }
87 | // 编码后写入
88 | for _, rect := range that.Rects {
89 | if err := rect.Write(session); err != nil {
90 | return err
91 | }
92 | }
93 | return session.Flush()
94 | }
95 |
96 | func (that *FramebufferUpdate) Clone() rfb.Message {
97 |
98 | c := &FramebufferUpdate{
99 | NumRect: that.NumRect,
100 | }
101 | for _, rect := range that.Rects {
102 | c.Rects = append(c.Rects, rect.Clone())
103 | }
104 | return c
105 | }
106 |
--------------------------------------------------------------------------------
/messages/serverInit.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "fmt"
5 | "github.com/vprix/vncproxy/rfb"
6 | )
7 |
8 | // ServerInit 握手的时候服务端初始化消息
9 | type ServerInit struct {
10 | FBWidth uint16
11 | FBHeight uint16
12 | PixelFormat rfb.PixelFormat
13 | NameLength uint32
14 | NameText []byte
15 | }
16 |
17 | func (srvInit ServerInit) String() string {
18 | return fmt.Sprintf("ServerInit->Width: %d, Height: %d, PixelFormat: %s, NameLength: %d, MameText: %s", srvInit.FBWidth, srvInit.FBHeight, srvInit.PixelFormat, srvInit.NameLength, srvInit.NameText)
19 | }
20 |
--------------------------------------------------------------------------------
/messages/serverServerCutText.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // ServerCutText 服务端剪切板发送到客户端
10 | type ServerCutText struct {
11 | _ [3]byte // 填充
12 | Length uint32 // 剪切板内容长度
13 | Text []byte // 剪切板内容
14 | }
15 |
16 | func (that *ServerCutText) Clone() rfb.Message {
17 | return &ServerCutText{
18 | Length: that.Length,
19 | Text: that.Text,
20 | }
21 | }
22 | func (that *ServerCutText) Supported(session rfb.ISession) bool {
23 | return true
24 | }
25 |
26 | // String returns string
27 | func (that *ServerCutText) String() string {
28 | return fmt.Sprintf("lenght: %d", that.Length)
29 | }
30 |
31 | func (that *ServerCutText) Type() rfb.MessageType {
32 | return rfb.MessageType(rfb.ServerCutText)
33 | }
34 |
35 | // 读取消息数据
36 | func (that *ServerCutText) Read(session rfb.ISession) (rfb.Message, error) {
37 | // 每次读取以后生成的都是一个新的对象
38 | msg := &ServerCutText{}
39 | var pad [3]byte
40 | if err := binary.Read(session, binary.BigEndian, &pad); err != nil {
41 | return nil, err
42 | }
43 |
44 | if err := binary.Read(session, binary.BigEndian, &msg.Length); err != nil {
45 | return nil, err
46 | }
47 |
48 | msg.Text = make([]byte, msg.Length)
49 | if err := binary.Read(session, binary.BigEndian, &msg.Text); err != nil {
50 | return nil, err
51 | }
52 | return msg, nil
53 | }
54 |
55 | func (that *ServerCutText) Write(session rfb.ISession) error {
56 | // 写入消息类型
57 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
58 | return err
59 | }
60 | //写入填充
61 | var pad [3]byte
62 | if err := binary.Write(session, binary.BigEndian, pad); err != nil {
63 | return err
64 | }
65 |
66 | if that.Length < uint32(len(that.Text)) {
67 | that.Length = uint32(len(that.Text))
68 | }
69 | if err := binary.Write(session, binary.BigEndian, that.Length); err != nil {
70 | return err
71 | }
72 |
73 | if err := binary.Write(session, binary.BigEndian, that.Text); err != nil {
74 | return err
75 | }
76 | return session.Flush()
77 | }
78 |
--------------------------------------------------------------------------------
/messages/serverServerFence.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // ServerFence 支持 Fence扩展的服务器发送此扩展以请求数据流的同步。
10 | type ServerFence struct {
11 | flags uint32
12 | length uint8
13 | payload []byte
14 | }
15 |
16 | func (that *ServerFence) Clone() rfb.Message {
17 |
18 | c := &ServerFence{
19 | flags: that.flags,
20 | length: that.length,
21 | payload: that.payload,
22 | }
23 | return c
24 | }
25 | func (that *ServerFence) Supported(session rfb.ISession) bool {
26 | return true
27 | }
28 | func (that *ServerFence) String() string {
29 | return fmt.Sprintf("type=%d", that.Type())
30 | }
31 |
32 | func (that *ServerFence) Type() rfb.MessageType {
33 | return rfb.MessageType(rfb.ServerFence)
34 | }
35 |
36 | // 读取数据
37 | func (that *ServerFence) Read(session rfb.ISession) (rfb.Message, error) {
38 | msg := &ServerFence{}
39 | bytes := make([]byte, 3)
40 | //c.Read(bytes)
41 | if _, err := session.Read(bytes); err != nil {
42 | return nil, err
43 | }
44 | if err := binary.Read(session, binary.BigEndian, &msg.flags); err != nil {
45 | return nil, err
46 | }
47 | if err := binary.Read(session, binary.BigEndian, &msg.length); err != nil {
48 | return nil, err
49 | }
50 | bytes = make([]byte, msg.length)
51 | if _, err := session.Read(bytes); err != nil {
52 | return nil, err
53 | }
54 | msg.payload = bytes
55 | return msg, nil
56 | }
57 |
58 | func (that *ServerFence) Write(session rfb.ISession) error {
59 | // 写入消息类型
60 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
61 | return err
62 | }
63 | //写入填充
64 | var pad [3]byte
65 | if err := binary.Write(session, binary.BigEndian, pad); err != nil {
66 | return err
67 | }
68 |
69 | if err := binary.Write(session, binary.BigEndian, that.flags); err != nil {
70 | return err
71 | }
72 |
73 | if err := binary.Write(session, binary.BigEndian, that.length); err != nil {
74 | return err
75 | }
76 | if err := binary.Write(session, binary.BigEndian, that.payload); err != nil {
77 | return err
78 | }
79 | return session.Flush()
80 | }
81 |
--------------------------------------------------------------------------------
/messages/serverSetColorMapEntries.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "github.com/vprix/vncproxy/rfb"
7 | )
8 |
9 | // SetColorMapEntries 设置颜色表的内容
10 | // See RFC 6143 Section 7.6.2
11 | type SetColorMapEntries struct {
12 | _ [1]byte //填充
13 | FirstColor uint16 // 颜色的起始位置,
14 | ColorsNum uint16 // 颜色的数目
15 | Colors []rfb.Color
16 | }
17 |
18 | func (that *SetColorMapEntries) Clone() rfb.Message {
19 |
20 | c := &SetColorMapEntries{
21 | FirstColor: that.FirstColor,
22 | ColorsNum: that.ColorsNum,
23 | Colors: that.Colors,
24 | }
25 | return c
26 | }
27 | func (that *SetColorMapEntries) Supported(session rfb.ISession) bool {
28 | return true
29 | }
30 |
31 | // String returns string
32 | func (that *SetColorMapEntries) String() string {
33 | return fmt.Sprintf("first color: %d, numcolors: %d, colors[]: { %v }", that.FirstColor, that.ColorsNum, that.Colors)
34 | }
35 |
36 | // Type returns MessageType
37 | func (*SetColorMapEntries) Type() rfb.MessageType {
38 | return rfb.MessageType(rfb.SetColorMapEntries)
39 | }
40 |
41 | func (that *SetColorMapEntries) Read(session rfb.ISession) (rfb.Message, error) {
42 | msg := &SetColorMapEntries{}
43 | // 先读取一个字节的填充
44 | var pad [1]byte
45 | if err := binary.Read(session, binary.BigEndian, &pad); err != nil {
46 | return nil, err
47 | }
48 | // 单个消息不必指定整个色彩映射表,而可能能只更新几个条目。
49 | //例如,如果我想更新条目 5 和 6,我会在FirstColor中指定,后跟两组 RGB 值。first-colour:5 number-of-colours:2
50 | if err := binary.Read(session, binary.BigEndian, &msg.FirstColor); err != nil {
51 | return nil, err
52 | }
53 | // 获取此次要更新几个颜色
54 | if err := binary.Read(session, binary.BigEndian, &msg.ColorsNum); err != nil {
55 | return nil, err
56 | }
57 |
58 | msg.Colors = make([]rfb.Color, msg.ColorsNum)
59 | colorMap := session.Options().ColorMap
60 | //读取指定的颜色数据
61 | for i := uint16(0); i < msg.ColorsNum; i++ {
62 | color := &msg.Colors[i]
63 | err := color.Read(session)
64 | if err != nil {
65 | return nil, err
66 | }
67 | colorMap[msg.FirstColor+i] = *color
68 | }
69 | session.SetColorMap(colorMap)
70 | return msg, nil
71 | }
72 |
73 | func (that *SetColorMapEntries) Write(session rfb.ISession) error {
74 |
75 | // 写入消息类型
76 | if err := binary.Write(session, binary.BigEndian, that.Type()); err != nil {
77 | return err
78 | }
79 | // 填充
80 | var pad [1]byte
81 | if err := binary.Write(session, binary.BigEndian, &pad); err != nil {
82 | return err
83 | }
84 |
85 | // 首个颜色
86 | if err := binary.Write(session, binary.BigEndian, that.FirstColor); err != nil {
87 | return err
88 | }
89 | // 要更新的颜色数目
90 | if that.ColorsNum < uint16(len(that.Colors)) {
91 | that.ColorsNum = uint16(len(that.Colors))
92 | }
93 | if err := binary.Write(session, binary.BigEndian, that.ColorsNum); err != nil {
94 | return err
95 | }
96 |
97 | // 颜色数据
98 | for i := 0; i < len(that.Colors); i++ {
99 | color := that.Colors[i]
100 | if err := binary.Write(session, binary.BigEndian, color); err != nil {
101 | return err
102 | }
103 | }
104 |
105 | return session.Flush()
106 | }
107 |
--------------------------------------------------------------------------------
/rfb/color_map.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | import "encoding/binary"
4 |
5 | // ColorMap 颜色地图
6 | type ColorMap [256]Color
7 |
8 | // Color 表示颜色地图中的一个颜色。
9 | type Color struct {
10 | pf *PixelFormat
11 | cm *ColorMap
12 | cmIndex uint32 // Only valid if pf.TrueColor is false.
13 | R, G, B uint16
14 | }
15 |
16 | // 写入颜色数据
17 | func (that *Color) Write(session ISession) error {
18 | var err error
19 | pf := session.Options().PixelFormat
20 | order := pf.Order()
21 | pixel := that.cmIndex
22 | if that.pf.TrueColor != 0 {
23 | pixel = uint32(that.R) << pf.RedShift
24 | pixel |= uint32(that.G) << pf.GreenShift
25 | pixel |= uint32(that.B) << pf.BlueShift
26 | }
27 |
28 | switch pf.BPP {
29 | case 8:
30 | err = binary.Write(session, order, byte(pixel))
31 | case 16:
32 | err = binary.Write(session, order, uint16(pixel))
33 | case 32:
34 | err = binary.Write(session, order, uint32(pixel))
35 | }
36 |
37 | return err
38 | }
39 |
40 | // 从链接中读取颜色偏移量
41 | func (that *Color) Read(session ISession) error {
42 | order := that.pf.Order()
43 | var pixel uint32
44 |
45 | switch that.pf.BPP {
46 | case 8:
47 | var px uint8
48 | if err := binary.Read(session, order, &px); err != nil {
49 | return err
50 | }
51 | pixel = uint32(px)
52 | case 16:
53 | var px uint16
54 | if err := binary.Read(session, order, &px); err != nil {
55 | return err
56 | }
57 | pixel = uint32(px)
58 | case 32:
59 | var px uint32
60 | if err := binary.Read(session, order, &px); err != nil {
61 | return err
62 | }
63 | pixel = px
64 | }
65 |
66 | if that.pf.TrueColor != 0 {
67 | that.R = uint16((pixel >> that.pf.RedShift) & uint32(that.pf.RedMax))
68 | that.G = uint16((pixel >> that.pf.GreenShift) & uint32(that.pf.GreenMax))
69 | that.B = uint16((pixel >> that.pf.BlueShift) & uint32(that.pf.BlueMax))
70 | } else {
71 | *that = that.cm[pixel]
72 | that.cmIndex = pixel
73 | }
74 | return nil
75 | }
76 |
--------------------------------------------------------------------------------
/rfb/desktop.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | //
4 | //type Desktop struct {
5 | // desktopName []byte // 桌面名称
6 | // fbHeight uint16 // 缓冲帧高度
7 | // fbWidth uint16 // 缓冲帧宽度
8 | // colorMap ColorMap // 颜色地图
9 | // pixelFormat PixelFormat // 像素格式
10 | //}
11 | //
12 | //// PixelFormat 获取像素格式
13 | //func (that *Desktop) PixelFormat() PixelFormat {
14 | // return that.pixelFormat
15 | //}
16 | //
17 | //// SetPixelFormat 设置像素格式
18 | //func (that *Desktop) SetPixelFormat(pf PixelFormat) {
19 | // that.pixelFormat = pf
20 | //}
21 | //
22 | //// ColorMap 获取颜色地图
23 | //func (that *Desktop) ColorMap() ColorMap {
24 | // return that.colorMap
25 | //}
26 | //
27 | //// SetColorMap 设置颜色地图
28 | //func (that *Desktop) SetColorMap(cm ColorMap) {
29 | // that.colorMap = cm
30 | //}
31 | //
32 | //// Width 获取桌面宽度
33 | //func (that *Desktop) Width() uint16 {
34 | // return that.fbWidth
35 | //}
36 | //
37 | //// SetWidth 设置桌面宽度
38 | //func (that *Desktop) SetWidth(width uint16) {
39 | // that.fbWidth = width
40 | //}
41 | //
42 | //// Height 获取桌面高度
43 | //func (that *Desktop) Height() uint16 {
44 | // return that.fbHeight
45 | //}
46 | //
47 | //// SetHeight 设置桌面高度
48 | //func (that *Desktop) SetHeight(height uint16) {
49 | // that.fbHeight = height
50 | //}
51 | //
52 | //// DesktopName 获取该会话的桌面名称
53 | //func (that *Desktop) DesktopName() []byte {
54 | // return that.desktopName
55 | //}
56 | //
57 | //// SetDesktopName 设置桌面名称
58 | //func (that *Desktop) SetDesktopName(name []byte) {
59 | // that.desktopName = name
60 | //}
61 |
--------------------------------------------------------------------------------
/rfb/encoding.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | // IEncoding vnc像素数据编码格式的接口定义
4 | type IEncoding interface {
5 | Type() EncodingType
6 | Supported(ISession) bool
7 | Clone(...bool) IEncoding
8 | Read(ISession, *Rectangle) error
9 | Write(ISession, *Rectangle) error
10 | }
11 |
--------------------------------------------------------------------------------
/rfb/handler.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | type IHandler interface {
4 | Handle(session ISession) error
5 | }
6 |
7 | // ProtoVersionLength rfb协议长度
8 | const ProtoVersionLength = 12
9 |
10 | const (
11 | // ProtoVersionUnknown 未知协议
12 | ProtoVersionUnknown = ""
13 | // ProtoVersion33 版本 003.003
14 | ProtoVersion33 = "RFB 003.003\n"
15 | // ProtoVersion38 版本 003.008
16 | ProtoVersion38 = "RFB 003.008\n"
17 | // ProtoVersion37 版本 003.007
18 | ProtoVersion37 = "RFB 003.007\n"
19 | )
20 |
--------------------------------------------------------------------------------
/rfb/keys.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | import "fmt"
4 |
5 | type Key uint32
6 |
7 | // Keys 按键列表
8 | type Keys []Key
9 |
10 | var keymap = map[rune]Key{
11 | '-': Minus,
12 | '0': Digit0,
13 | '1': Digit1,
14 | '2': Digit2,
15 | '3': Digit3,
16 | '4': Digit4,
17 | '5': Digit5,
18 | '6': Digit6,
19 | '7': Digit7,
20 | '8': Digit8,
21 | '9': Digit9,
22 | }
23 |
24 | // IntToKeys 返回表示键入int类型所需按下的键的键。
25 | func IntToKeys(v int) Keys {
26 | k := Keys{}
27 | for _, c := range fmt.Sprintf("%d", v) {
28 | k = append(k, keymap[c])
29 | }
30 | return k
31 | }
32 |
33 | // Latin 1 (byte 3 = 0)
34 | // ISO/IEC 8859-1 = Unicode U+0020..U+00FF
35 | const (
36 | Space Key = iota + 0x0020
37 | Exclaim // exclamation mark
38 | QuoteDbl
39 | NumberSign
40 | Dollar
41 | Percent
42 | Ampersand
43 | Apostrophe
44 | ParenLeft
45 | ParenRight
46 | Asterisk
47 | Plus
48 | Comma
49 | Minus
50 | Period
51 | Slash
52 | Digit0
53 | Digit1
54 | Digit2
55 | Digit3
56 | Digit4
57 | Digit5
58 | Digit6
59 | Digit7
60 | Digit8
61 | Digit9
62 | Colon
63 | Semicolon
64 | Less
65 | Equal
66 | Greater
67 | Question
68 | At
69 | A
70 | B
71 | C
72 | D
73 | E
74 | F
75 | G
76 | H
77 | I
78 | J
79 | K
80 | L
81 | M
82 | N
83 | O
84 | P
85 | Q
86 | R
87 | S
88 | T
89 | U
90 | V
91 | W
92 | X
93 | Y
94 | Z
95 | BracketLeft
96 | Backslash
97 | BracketRight
98 | AsciiCircum
99 | Underscore
100 | Grave
101 | SmallA
102 | SmallB
103 | SmallC
104 | SmallD
105 | SmallE
106 | SmallF
107 | SmallG
108 | SmallH
109 | SmallI
110 | SmallJ
111 | SmallK
112 | SmallL
113 | SmallM
114 | SmallN
115 | SmallO
116 | SmallP
117 | SmallQ
118 | SmallR
119 | SmallS
120 | SmallT
121 | SmallU
122 | SmallV
123 | SmallW
124 | SmallX
125 | SmallY
126 | SmallZ
127 | BraceLeft
128 | Bar
129 | BraceRight
130 | AsciiTilde
131 | )
132 |
133 | const (
134 | BackSpace Key = iota + 0xff08
135 | Tab
136 | Linefeed
137 | Clear
138 | _
139 | Return
140 | )
141 |
142 | const (
143 | Pause Key = iota + 0xff13
144 | ScrollLock
145 | SysReq
146 | Escape Key = 0xff1b
147 | Delete Key = 0xffff
148 | )
149 |
150 | const ( // Cursor control & motion.
151 | Home Key = iota + 0xff50
152 | Left
153 | Up
154 | Right
155 | Down
156 | PageUp
157 | PageDown
158 | End
159 | Begin
160 | )
161 |
162 | const ( // Misc functions.
163 | Select Key = 0xff60
164 | Print
165 | Execute
166 | Insert
167 | Undo
168 | Redo
169 | Menu
170 | Find
171 | Cancel
172 | Help
173 | Break
174 | ModeSwitch Key = 0xff7e
175 | NumLock Key = 0xff7f
176 | )
177 |
178 | const ( // Keypad functions.
179 | KeypadSpace Key = 0xff80
180 | KeypadTab Key = 0xff89
181 | KeypadEnter Key = 0xff8d
182 | )
183 |
184 | const ( // Keypad functions cont.
185 | KeypadF1 Key = iota + 0xff91
186 | KeypadF2
187 | KeypadF3
188 | KeypadF4
189 | KeypadHome
190 | KeypadLeft
191 | KeypadUp
192 | KeypadRight
193 | KeypadDown
194 | KeypadPrior
195 | KeypadPageUp
196 | KeypadNext
197 | KeypadPageDown
198 | KeypadEnd
199 | KeypadBegin
200 | KeypadInsert
201 | KeypadDelete
202 | KeypadMultiply
203 | KeypadAdd
204 | KeypadSeparator
205 | KeypadSubtract
206 | KeypadDecimal
207 | KeypadDivide
208 | Keypad0
209 | Keypad1
210 | Keypad2
211 | Keypad3
212 | Keypad4
213 | Keypad5
214 | Keypad6
215 | Keypad7
216 | Keypad8
217 | Keypad9
218 | KeypadEqual Key = 0xffbd
219 | )
220 |
221 | const (
222 | F1 Key = iota + 0xffbe
223 | F2
224 | F3
225 | F4
226 | F5
227 | F6
228 | F7
229 | F8
230 | F9
231 | F10
232 | F11
233 | F12
234 | )
235 |
236 | const (
237 | ShiftLeft Key = iota + 0xffe1
238 | ShiftRight
239 | ControlLeft
240 | ControlRight
241 | CapsLock
242 | ShiftLock
243 | MetaLeft
244 | MetaRight
245 | AltLeft
246 | AltRight
247 | SuperLeft
248 | SuperRight
249 | HyperLeft
250 | HyperRight
251 | )
252 |
--------------------------------------------------------------------------------
/rfb/message.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | type Message interface {
4 | Type() MessageType
5 | String() string
6 | Supported(ISession) bool
7 | Read(ISession) (Message, error)
8 | Write(ISession) error
9 | Clone() Message
10 | }
11 |
--------------------------------------------------------------------------------
/rfb/message_type_client_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=ClientMessageType"; DO NOT EDIT.
2 |
3 | package rfb
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[SetPixelFormat-0]
12 | _ = x[SetEncodings-2]
13 | _ = x[FramebufferUpdateRequest-3]
14 | _ = x[KeyEvent-4]
15 | _ = x[PointerEvent-5]
16 | _ = x[ClientCutText-6]
17 | _ = x[ClientFence-248]
18 | _ = x[QEMUExtendedKeyEvent-255]
19 | }
20 |
21 | const (
22 | _ClientMessageType_name_0 = "SetPixelFormat"
23 | _ClientMessageType_name_1 = "SetEncodingsFramebufferUpdateRequestKeyEventPointerEventClientCutText"
24 | _ClientMessageType_name_2 = "ClientFence"
25 | _ClientMessageType_name_3 = "QEMUExtendedKeyEvent"
26 | )
27 |
28 | var (
29 | _ClientMessageType_index_1 = [...]uint8{0, 12, 36, 44, 56, 69}
30 | )
31 |
32 | func (i ClientMessageType) String() string {
33 | switch {
34 | case i == 0:
35 | return _ClientMessageType_name_0
36 | case 2 <= i && i <= 6:
37 | i -= 2
38 | return _ClientMessageType_name_1[_ClientMessageType_index_1[i]:_ClientMessageType_index_1[i+1]]
39 | case i == 248:
40 | return _ClientMessageType_name_2
41 | case i == 255:
42 | return _ClientMessageType_name_3
43 | default:
44 | return "ClientMessageType(" + strconv.FormatInt(int64(i), 10) + ")"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/rfb/message_type_server_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=ServerMessageType"; DO NOT EDIT.
2 |
3 | package rfb
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[FramebufferUpdate-0]
12 | _ = x[SetColorMapEntries-1]
13 | _ = x[Bell-2]
14 | _ = x[ServerCutText-3]
15 | }
16 |
17 | const _ServerMessageType_name = "FramebufferUpdateSetColorMapEntriesBellServerCutText"
18 |
19 | var _ServerMessageType_index = [...]uint8{0, 17, 35, 39, 52}
20 |
21 | func (i ServerMessageType) String() string {
22 | if i >= ServerMessageType(len(_ServerMessageType_index)-1) {
23 | return "ServerMessageType(" + strconv.FormatInt(int64(i), 10) + ")"
24 | }
25 | return _ServerMessageType_name[_ServerMessageType_index[i]:_ServerMessageType_index[i+1]]
26 | }
27 |
--------------------------------------------------------------------------------
/rfb/messagetype.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | type MessageType uint8
4 |
5 | // ClientMessageType vnc客户端发送给vnc服务端的消息类型
6 | type ClientMessageType MessageType
7 |
8 | //go:generate stringer -type=ClientMessageType
9 |
10 | const (
11 | SetPixelFormat ClientMessageType = 0 // 设置像素格式
12 | SetEncodings ClientMessageType = 2 // 设置消息的编码格式
13 | FramebufferUpdateRequest ClientMessageType = 3 // 请求帧缓冲内容
14 | KeyEvent ClientMessageType = 4 // 键盘事件消息
15 | PointerEvent ClientMessageType = 5 // 鼠标事件消息
16 | ClientCutText ClientMessageType = 6 // 剪切板消息
17 | EnableContinuousUpdates ClientMessageType = 150 // 打开连续更新
18 | ClientFence ClientMessageType = 248 //客户端到服务端的数据同步请求
19 | SetDesktopSize ClientMessageType = 251 //客户端设置桌面大小
20 | QEMUExtendedKeyEvent ClientMessageType = 255 // qumu虚拟机的扩展按键消息
21 | )
22 |
23 | // ServerMessageType 服务端发送给客户端的消息类型
24 | type ServerMessageType MessageType
25 |
26 | //go:generate stringer -type=ServerMessageType
27 |
28 | const (
29 | FramebufferUpdate ServerMessageType = 0 // 帧缓冲区更新消息
30 | SetColorMapEntries ServerMessageType = 1 // 设置颜色地图
31 | Bell ServerMessageType = 2 // 响铃
32 | ServerCutText ServerMessageType = 3 // 设置剪切板数据
33 | EndOfContinuousUpdates ServerMessageType = 150 //结束连续更新
34 | ServerFence ServerMessageType = 248 //支持 Fence 扩展的服务器发送此扩展以请求数据流的同步
35 | )
36 |
--------------------------------------------------------------------------------
/rfb/options.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | import "io"
4 |
5 | type Option func(*Options)
6 | type GetConn func(sess ISession) (io.ReadWriteCloser, error)
7 |
8 | // Options 配置信息
9 | type Options struct {
10 | // 公共配置
11 | Handlers []IHandler // 处理程序列表
12 | SecurityHandlers []ISecurityHandler // 安全验证
13 | Encodings []IEncoding // 支持的编码类型
14 | PixelFormat PixelFormat // 像素格式
15 | ColorMap ColorMap // 颜色地图
16 | Input chan Message // 输入消息
17 | Output chan Message // 输出消息
18 | Messages []Message // 支持的消息类型
19 | DisableServerMessageType []ServerMessageType // 禁用的消息,碰到这些消息,则跳过
20 | DisableClientMessageType []ClientMessageType // 禁用的消息,碰到这些消息,则跳过
21 | QuitCh chan struct{} // 退出
22 | ErrorCh chan error // 错误通道
23 |
24 | // 服务端配置
25 | DesktopName []byte // 桌面名称,作为服务端配置的时候,需要设置
26 | Height uint16 // 缓冲帧高度,作为服务端配置的时候,需要设置
27 | Width uint16 // 缓冲帧宽度,作为服务端配置的时候,需要设置
28 |
29 | // 客户端配置
30 | DrawCursor bool // 是否绘制鼠标指针
31 | Exclusive bool // 是否独占
32 |
33 | // 生成连接的方法
34 | GetConn GetConn
35 | }
36 |
37 | // OptHandlers 设置流程处理程序
38 | func OptHandlers(opt ...IHandler) Option {
39 | return func(options *Options) {
40 | options.Handlers = append(options.Handlers, opt...)
41 | }
42 | }
43 |
44 | // OptSecurityHandlers 设置权限认证处理程序
45 | func OptSecurityHandlers(opt ...ISecurityHandler) Option {
46 | return func(options *Options) {
47 | options.SecurityHandlers = append(options.SecurityHandlers, opt...)
48 | }
49 | }
50 |
51 | // OptEncodings 设置支持的编码格式
52 | func OptEncodings(opt ...IEncoding) Option {
53 | return func(options *Options) {
54 | options.Encodings = append(options.Encodings, opt...)
55 | }
56 | }
57 |
58 | // OptMessages 设置支持的消息类型
59 | func OptMessages(opt ...Message) Option {
60 | return func(options *Options) {
61 | options.Messages = append(options.Messages, opt...)
62 | }
63 | }
64 |
65 | // OptPixelFormat 设置像素格式
66 | func OptPixelFormat(opt PixelFormat) Option {
67 | return func(options *Options) {
68 | options.PixelFormat = opt
69 | }
70 | }
71 |
72 | // OptGetConn 设置生成连接方法
73 | func OptGetConn(opt GetConn) Option {
74 | return func(options *Options) {
75 | options.GetConn = opt
76 | }
77 | }
78 |
79 | func OptDesktopName(opt []byte) Option {
80 | return func(options *Options) {
81 | options.DesktopName = opt
82 | }
83 | }
84 |
85 | func OptHeight(opt int) Option {
86 | return func(options *Options) {
87 | options.Height = uint16(opt)
88 | }
89 | }
90 | func OptWidth(opt int) Option {
91 | return func(options *Options) {
92 | options.Width = uint16(opt)
93 | }
94 | }
95 |
96 | // OptDisableServerMessageType 要屏蔽的服务端消息
97 | func OptDisableServerMessageType(opt ...ServerMessageType) Option {
98 | return func(options *Options) {
99 | options.DisableServerMessageType = opt
100 | }
101 | }
102 |
103 | // OptDisableClientMessageType 要屏蔽的客户端消息
104 | func OptDisableClientMessageType(opt ...ClientMessageType) Option {
105 | return func(options *Options) {
106 | options.DisableClientMessageType = opt
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/rfb/pixel_format.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | )
7 |
8 | const PixelFormatLen = 16
9 |
10 | var (
11 | // PixelFormat8bit 获取8bit像素格式
12 | PixelFormat8bit = NewPixelFormat(8)
13 | // PixelFormat16bit 获取15bit像素格式
14 | PixelFormat16bit = NewPixelFormat(16)
15 | // PixelFormat32bit 获取32bit像素格式
16 | PixelFormat32bit = NewPixelFormat(32)
17 | // PixelFormatAten returns pixel format used in Aten IKVM
18 | PixelFormatAten = NewPixelFormatAten()
19 | )
20 |
21 | // PixelFormat 像素格式结构体
22 | type PixelFormat struct {
23 | BPP uint8 // 1 byte,像素的位数,位数越大,色彩越丰富。只支持[8|16|32] 该值必须大于Depth
24 | Depth uint8 // 1 byte,色深,像素中表示色彩的位数
25 | BigEndian uint8 // 1 byte,多字节像素的字节序,非零即大端序
26 | TrueColor uint8 // 1 byte,1 表示真彩色,pixel 的值表示 RGB 颜色;0 表示调色板,pexel 的值表示颜色在调色板的偏移量
27 | RedMax uint16 // 2 byte,红色的长度
28 | GreenMax uint16 // 2 byte,绿色的长度
29 | BlueMax uint16 // 2 byte,蓝色的长度
30 | RedShift uint8 // 1 byte,红色的位移量
31 | GreenShift uint8 // 1 byte,绿色的位移量
32 | BlueShift uint8 // 1 byte,蓝色的偏移量
33 | _ [3]byte // 填充字节
34 | }
35 |
36 | func (that PixelFormat) String() string {
37 | return fmt.Sprintf("{ bpp: %d depth: %d big-endian: %d true-color: %d red-max: %d green-max: %d blue-max: %d red-shift: %d green-shift: %d blue-shift: %d }",
38 | that.BPP, that.Depth, that.BigEndian, that.TrueColor, that.RedMax, that.GreenMax, that.BlueMax, that.RedShift, that.GreenShift, that.BlueShift)
39 | }
40 |
41 | // Order 确定像素格式是使用了大端字节序还是小端字节序
42 | func (that PixelFormat) Order() binary.ByteOrder {
43 | if that.BigEndian == 1 {
44 | return binary.BigEndian
45 | }
46 | return binary.LittleEndian
47 | }
48 |
49 | func NewPixelFormat(bpp uint8) PixelFormat {
50 | bigEndian := uint8(0)
51 | // rgbMax := uint16(math.Exp2(float64(bpp))) - 1
52 | rMax := uint16(255)
53 | gMax := uint16(255)
54 | bMax := uint16(255)
55 | var (
56 | tc = uint8(1)
57 | rs, gs, bs uint8
58 | depth uint8
59 | )
60 | switch bpp {
61 | case 8:
62 | tc = 0
63 | depth = 8
64 | rs, gs, bs = 0, 0, 0
65 | case 16:
66 | depth = 16
67 | rs, gs, bs = 0, 4, 8
68 | case 32:
69 | depth = 24
70 | // rs, gs, bs = 0, 8, 16
71 | rs, gs, bs = 16, 8, 0
72 | }
73 | return PixelFormat{
74 | BPP: bpp,
75 | Depth: depth,
76 | BigEndian: bigEndian,
77 | TrueColor: tc,
78 | RedMax: rMax,
79 | GreenMax: gMax,
80 | BlueMax: bMax,
81 | RedShift: rs,
82 | GreenShift: gs,
83 | BlueShift: bs,
84 | //_: [3]byte{},
85 | }
86 | }
87 |
88 | func NewPixelFormatAten() PixelFormat {
89 | return PixelFormat{
90 | BPP: 16,
91 | Depth: 15,
92 | BigEndian: 0,
93 | TrueColor: 1,
94 | RedMax: (1 << 5) - 1,
95 | GreenMax: (1 << 5) - 1,
96 | BlueMax: (1 << 5) - 1,
97 | RedShift: 10,
98 | GreenShift: 5,
99 | BlueShift: 0,
100 | //_: [3]byte{},
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/rfb/rectangle.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | )
7 |
8 | // Rectangle 表示像素数据的矩形
9 | type Rectangle struct {
10 | X uint16
11 | Y uint16
12 | Width uint16
13 | Height uint16
14 | EncType EncodingType
15 | Enc IEncoding
16 | }
17 |
18 | func NewRectangle() *Rectangle {
19 | return &Rectangle{}
20 | }
21 | func (that *Rectangle) String() string {
22 | return fmt.Sprintf("X:%d,Y:%d,Width:%d,Height:%d,EncType:%s", that.X, that.Y, that.Width, that.Height, that.EncType)
23 | }
24 |
25 | // 读取矩形数据
26 | func (that *Rectangle) Read(sess ISession) error {
27 | var err error
28 |
29 | //读取x坐标
30 | if err = binary.Read(sess, binary.BigEndian, &that.X); err != nil {
31 | return err
32 | }
33 | // 读取y坐标
34 | if err = binary.Read(sess, binary.BigEndian, &that.Y); err != nil {
35 | return err
36 | }
37 | // 读取x坐标上的宽度
38 | if err = binary.Read(sess, binary.BigEndian, &that.Width); err != nil {
39 | return err
40 | }
41 | // 读取y坐标上的高度
42 | if err = binary.Read(sess, binary.BigEndian, &that.Height); err != nil {
43 | return err
44 | }
45 | // 读取编码类型
46 | if err = binary.Read(sess, binary.BigEndian, &that.EncType); err != nil {
47 | return err
48 | }
49 | that.Enc = sess.NewEncoding(that.EncType)
50 | if that.Enc == nil {
51 | return fmt.Errorf("不支持的编码类型: %s", that.EncType)
52 | }
53 | return that.Enc.Read(sess, that)
54 | }
55 |
56 | // 写入矩形数据
57 | func (that *Rectangle) Write(sess ISession) error {
58 | var err error
59 |
60 | if err = binary.Write(sess, binary.BigEndian, that.X); err != nil {
61 | return err
62 | }
63 | if err = binary.Write(sess, binary.BigEndian, that.Y); err != nil {
64 | return err
65 | }
66 | if err = binary.Write(sess, binary.BigEndian, that.Width); err != nil {
67 | return err
68 | }
69 | if err = binary.Write(sess, binary.BigEndian, that.Height); err != nil {
70 | return err
71 | }
72 | if err = binary.Write(sess, binary.BigEndian, that.EncType); err != nil {
73 | return err
74 | }
75 |
76 | // 通过预定义的编码格式写入
77 | return that.Enc.Write(sess, that)
78 | }
79 |
80 | func (that *Rectangle) Clone() *Rectangle {
81 | r := &Rectangle{
82 | X: that.X,
83 | Y: that.Y,
84 | Width: that.Width,
85 | Height: that.Height,
86 | EncType: that.EncType,
87 | Enc: that.Enc.Clone(true),
88 | }
89 | return r
90 | }
91 |
--------------------------------------------------------------------------------
/rfb/security.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | // SecurityType 安全认证类型
4 | type SecurityType uint8
5 |
6 | //go:generate stringer -type=SecurityType
7 |
8 | const (
9 | SecTypeUnknown SecurityType = SecurityType(0) // 未知认证类型
10 | SecTypeNone SecurityType = SecurityType(1) // 不需要认证
11 | SecTypeVNC SecurityType = SecurityType(2) // vnc密码认证
12 | SecTypeTight SecurityType = SecurityType(16) // tight vnc主导的认证模式
13 | SecTypeVeNCrypt SecurityType = SecurityType(19) // VeNCrypt 通用认证类型
14 | )
15 |
16 | // SecuritySubType 认证子类型
17 | type SecuritySubType uint32
18 |
19 | //go:generate stringer -type=SecuritySubType
20 |
21 | // SecSubTypeUnknown 未知的子类型认证
22 | const (
23 | SecSubTypeUnknown SecuritySubType = SecuritySubType(0)
24 | )
25 |
26 | // VeNCrypt 安全认证会有两种认证版本0.1和0.2
27 | // 以下表示0.1
28 | const (
29 | SecSubTypeVeNCrypt01Unknown SecuritySubType = SecuritySubType(0)
30 | SecSubTypeVeNCrypt01Plain SecuritySubType = SecuritySubType(19)
31 | SecSubTypeVeNCrypt01TLSNone SecuritySubType = SecuritySubType(20)
32 | SecSubTypeVeNCrypt01TLSVNC SecuritySubType = SecuritySubType(21)
33 | SecSubTypeVeNCrypt01TLSPlain SecuritySubType = SecuritySubType(22)
34 | SecSubTypeVeNCrypt01X509None SecuritySubType = SecuritySubType(23)
35 | SecSubTypeVeNCrypt01X509VNC SecuritySubType = SecuritySubType(24)
36 | SecSubTypeVeNCrypt01X509Plain SecuritySubType = SecuritySubType(25)
37 | )
38 |
39 | // 以下表示0.2版本的类型
40 | const (
41 | SecSubTypeVeNCrypt02Unknown SecuritySubType = SecuritySubType(0)
42 | SecSubTypeVeNCrypt02Plain SecuritySubType = SecuritySubType(256)
43 | SecSubTypeVeNCrypt02TLSNone SecuritySubType = SecuritySubType(257)
44 | SecSubTypeVeNCrypt02TLSVNC SecuritySubType = SecuritySubType(258)
45 | SecSubTypeVeNCrypt02TLSPlain SecuritySubType = SecuritySubType(259)
46 | SecSubTypeVeNCrypt02X509None SecuritySubType = SecuritySubType(260)
47 | SecSubTypeVeNCrypt02X509VNC SecuritySubType = SecuritySubType(261)
48 | SecSubTypeVeNCrypt02X509Plain SecuritySubType = SecuritySubType(262)
49 | )
50 |
51 | // ISecurityHandler 认证方式的接口
52 | type ISecurityHandler interface {
53 | Type() SecurityType
54 | SubType() SecuritySubType
55 | Auth(ISession) error
56 | }
57 |
--------------------------------------------------------------------------------
/rfb/securitysubtype_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=SecuritySubType"; DO NOT EDIT.
2 |
3 | package rfb
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[SecSubTypeUnknown-0]
12 | _ = x[SecSubTypeVeNCrypt01Unknown-0]
13 | _ = x[SecSubTypeVeNCrypt01Plain-19]
14 | _ = x[SecSubTypeVeNCrypt01TLSNone-20]
15 | _ = x[SecSubTypeVeNCrypt01TLSVNC-21]
16 | _ = x[SecSubTypeVeNCrypt01TLSPlain-22]
17 | _ = x[SecSubTypeVeNCrypt01X509None-23]
18 | _ = x[SecSubTypeVeNCrypt01X509VNC-24]
19 | _ = x[SecSubTypeVeNCrypt01X509Plain-25]
20 | _ = x[SecSubTypeVeNCrypt02Unknown-0]
21 | _ = x[SecSubTypeVeNCrypt02Plain-256]
22 | _ = x[SecSubTypeVeNCrypt02TLSNone-257]
23 | _ = x[SecSubTypeVeNCrypt02TLSVNC-258]
24 | _ = x[SecSubTypeVeNCrypt02TLSPlain-259]
25 | _ = x[SecSubTypeVeNCrypt02X509None-260]
26 | _ = x[SecSubTypeVeNCrypt02X509VNC-261]
27 | _ = x[SecSubTypeVeNCrypt02X509Plain-262]
28 | }
29 |
30 | const (
31 | _SecuritySubType_name_0 = "SecSubTypeUnknown"
32 | _SecuritySubType_name_1 = "SecSubTypeVeNCrypt01PlainSecSubTypeVeNCrypt01TLSNoneSecSubTypeVeNCrypt01TLSVNCSecSubTypeVeNCrypt01TLSPlainSecSubTypeVeNCrypt01X509NoneSecSubTypeVeNCrypt01X509VNCSecSubTypeVeNCrypt01X509Plain"
33 | _SecuritySubType_name_2 = "SecSubTypeVeNCrypt02PlainSecSubTypeVeNCrypt02TLSNoneSecSubTypeVeNCrypt02TLSVNCSecSubTypeVeNCrypt02TLSPlainSecSubTypeVeNCrypt02X509NoneSecSubTypeVeNCrypt02X509VNCSecSubTypeVeNCrypt02X509Plain"
34 | )
35 |
36 | var (
37 | _SecuritySubType_index_1 = [...]uint8{0, 25, 52, 78, 106, 134, 161, 190}
38 | _SecuritySubType_index_2 = [...]uint8{0, 25, 52, 78, 106, 134, 161, 190}
39 | )
40 |
41 | func (i SecuritySubType) String() string {
42 | switch {
43 | case i == 0:
44 | return _SecuritySubType_name_0
45 | case 19 <= i && i <= 25:
46 | i -= 19
47 | return _SecuritySubType_name_1[_SecuritySubType_index_1[i]:_SecuritySubType_index_1[i+1]]
48 | case 256 <= i && i <= 262:
49 | i -= 256
50 | return _SecuritySubType_name_2[_SecuritySubType_index_2[i]:_SecuritySubType_index_2[i+1]]
51 | default:
52 | return "SecuritySubType(" + strconv.FormatInt(int64(i), 10) + ")"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/rfb/securitytype_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=SecurityType"; DO NOT EDIT.
2 |
3 | package rfb
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[SecTypeUnknown-0]
12 | _ = x[SecTypeNone-1]
13 | _ = x[SecTypeVNC-2]
14 | _ = x[SecTypeTight-16]
15 | _ = x[SecTypeVeNCrypt-19]
16 | }
17 |
18 | const (
19 | _SecurityType_name_0 = "SecTypeUnknownSecTypeNoneSecTypeVNC"
20 | _SecurityType_name_1 = "SecTypeTight"
21 | _SecurityType_name_2 = "SecTypeVeNCrypt"
22 | )
23 |
24 | var (
25 | _SecurityType_index_0 = [...]uint8{0, 14, 25, 35}
26 | )
27 |
28 | func (i SecurityType) String() string {
29 | switch {
30 | case i <= 2:
31 | return _SecurityType_name_0[_SecurityType_index_0[i]:_SecurityType_index_0[i+1]]
32 | case i == 16:
33 | return _SecurityType_name_1
34 | case i == 19:
35 | return _SecurityType_name_2
36 | default:
37 | return "SecurityType(" + strconv.FormatInt(int64(i), 10) + ")"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/rfb/session.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | import (
4 | "github.com/gogf/gf/v2/container/gmap"
5 | "io"
6 | )
7 |
8 | // ISession vnc连接的接口
9 | type ISession interface {
10 | io.ReadWriteCloser
11 | Conn() io.ReadWriteCloser
12 | Start()
13 | Flush() error // 清空缓冲区
14 | Wait() <-chan struct{} // 等待会话处理结束
15 | Init(...Option) error
16 | Options() Options
17 | SetPixelFormat(PixelFormat)
18 | SetColorMap(ColorMap)
19 | SetWidth(uint16)
20 | SetHeight(uint16)
21 | SetDesktopName([]byte)
22 | ProtocolVersion() string // 获取当前的rfb协议
23 | SetProtocolVersion(string) // 设置rfb协议
24 | SetSecurityHandler(ISecurityHandler) // 设置安全认证处理方法
25 | SecurityHandler() ISecurityHandler // 获取当前安全认证的处理方法
26 | Encodings() []IEncoding // 获取该会话支持的图像编码类型
27 | SetEncodings([]EncodingType) error // 设置该链接支持的图像编码类型
28 | NewEncoding(EncodingType) IEncoding
29 | Swap() *gmap.Map // 获取会话的自定义存储数据
30 | Type() SessionType
31 | }
32 |
33 | type SessionType uint8
34 |
35 | //go:generate stringer -type=SessionType
36 |
37 | const (
38 | ClientSessionType SessionType = 0
39 | ServerSessionType SessionType = 1
40 | RecorderSessionType SessionType = 2
41 | PlayerSessionType SessionType = 3
42 | CanvasSessionType SessionType = 4
43 | )
44 |
--------------------------------------------------------------------------------
/rfb/session_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=SessionType"; DO NOT EDIT.
2 |
3 | package rfb
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[ClientSessionType-0]
12 | _ = x[ServerSessionType-1]
13 | _ = x[RecorderSessionType-2]
14 | _ = x[PlayerSessionType-3]
15 | _ = x[CanvasSessionType-4]
16 | }
17 |
18 | const _SessionType_name = "ClientSessionTypeServerSessionTypeRecorderSessionTypePlayerSessionTypeCanvasSessionType"
19 |
20 | var _SessionType_index = [...]uint8{0, 17, 34, 53, 70, 87}
21 |
22 | func (i SessionType) String() string {
23 | if i >= SessionType(len(_SessionType_index)-1) {
24 | return "SessionType(" + strconv.FormatInt(int64(i), 10) + ")"
25 | }
26 | return _SessionType_name[_SessionType_index[i]:_SessionType_index[i+1]]
27 | }
28 |
--------------------------------------------------------------------------------
/rfb/target_config.go:
--------------------------------------------------------------------------------
1 | package rfb
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | type TargetConfig struct {
9 | Network string // 网络协议
10 | Timeout time.Duration // 超时时间
11 | Host string // vnc服务端地址
12 | Port int // vnc服务端端口
13 | Password []byte // vnc服务端密码
14 | }
15 |
16 | func (that TargetConfig) Addr() string {
17 | return fmt.Sprintf("%s:%d", that.Host, that.Port)
18 | }
19 |
20 | func (that TargetConfig) GetNetwork() string {
21 | if len(that.Network) == 0 {
22 | return "tcp"
23 | }
24 | return that.Network
25 | }
26 |
27 | func (that TargetConfig) GetTimeout() time.Duration {
28 | if that.Timeout == 0 {
29 | return 10 * time.Second
30 | }
31 | return that.Timeout
32 | }
33 |
--------------------------------------------------------------------------------
/security/security_none.go:
--------------------------------------------------------------------------------
1 | package security
2 |
3 | import "github.com/vprix/vncproxy/rfb"
4 |
5 | // ClientAuthNone vnc客户端认证
6 | type ClientAuthNone struct{}
7 |
8 | // ServerAuthNone 服务端认证
9 | type ServerAuthNone struct{}
10 |
11 | var _ rfb.ISecurityHandler = new(ClientAuthNone)
12 | var _ rfb.ISecurityHandler = new(ServerAuthNone)
13 |
14 | func (*ClientAuthNone) Type() rfb.SecurityType {
15 | return rfb.SecTypeNone
16 | }
17 | func (*ClientAuthNone) SubType() rfb.SecuritySubType {
18 | return rfb.SecSubTypeUnknown
19 | }
20 |
21 | func (*ClientAuthNone) Auth(rfb.ISession) error {
22 | return nil
23 | }
24 |
25 | func (*ServerAuthNone) Type() rfb.SecurityType {
26 | return rfb.SecTypeNone
27 | }
28 |
29 | func (*ServerAuthNone) SubType() rfb.SecuritySubType {
30 | return rfb.SecSubTypeUnknown
31 | }
32 |
33 | func (*ServerAuthNone) Auth(rfb.ISession) error {
34 | return nil
35 | }
36 |
--------------------------------------------------------------------------------
/security/security_tight.go:
--------------------------------------------------------------------------------
1 | package security
2 |
--------------------------------------------------------------------------------
/security/security_vencryptplain.go:
--------------------------------------------------------------------------------
1 | package security
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "fmt"
7 | "github.com/vprix/vncproxy/rfb"
8 | )
9 |
10 | type ClientAuthVeNCrypt02Plain struct {
11 | Username []byte
12 | Password []byte
13 | }
14 |
15 | func (*ClientAuthVeNCrypt02Plain) Type() rfb.SecurityType {
16 | return rfb.SecTypeVeNCrypt
17 | }
18 |
19 | func (*ClientAuthVeNCrypt02Plain) SubType() rfb.SecuritySubType {
20 | return rfb.SecSubTypeVeNCrypt02Plain
21 | }
22 |
23 | func (auth *ClientAuthVeNCrypt02Plain) Auth(session rfb.ISession) error {
24 | // 发送认证版本号
25 | if err := binary.Write(session, binary.BigEndian, []uint8{0, 2}); err != nil {
26 | return err
27 | }
28 | if err := session.Flush(); err != nil {
29 | return err
30 | }
31 | var (
32 | major, minor uint8
33 | )
34 | // 对比版本号
35 | if err := binary.Read(session, binary.BigEndian, &major); err != nil {
36 | return err
37 | }
38 | if err := binary.Read(session, binary.BigEndian, &minor); err != nil {
39 | return err
40 | }
41 | res := uint8(1)
42 | if major == 0 && minor == 2 {
43 | res = uint8(0)
44 | }
45 | if err := binary.Write(session, binary.BigEndian, res); err != nil {
46 | return err
47 | }
48 | if err := session.Flush(); err != nil {
49 | return err
50 | }
51 | // 选择认证子类型,只支持 SecSubTypeVeNCrypt02Plain 用户名密码认证
52 | if err := binary.Write(session, binary.BigEndian, uint8(1)); err != nil {
53 | return err
54 | }
55 | if err := binary.Write(session, binary.BigEndian, auth.SubType()); err != nil {
56 | return err
57 | }
58 | if err := session.Flush(); err != nil {
59 | return err
60 | }
61 | var secType rfb.SecuritySubType
62 | if err := binary.Read(session, binary.BigEndian, &secType); err != nil {
63 | return err
64 | }
65 | // 客户端选择的认证类型服务端不支持
66 | if secType != auth.SubType() {
67 | if err := binary.Write(session, binary.BigEndian, uint8(1)); err != nil {
68 | return err
69 | }
70 | if err := session.Flush(); err != nil {
71 | return err
72 | }
73 | return fmt.Errorf("invalid sectype")
74 | }
75 | // 服务端未设置用户名密码认证数据
76 | if len(auth.Password) == 0 || len(auth.Username) == 0 {
77 | return fmt.Errorf("Security Handshake failed; no username and/or password provided for VeNCryptAuth. ")
78 | }
79 | var (
80 | uLength, pLength uint32
81 | )
82 | // 获取用户名和密码长度
83 | if err := binary.Read(session, binary.BigEndian, &uLength); err != nil {
84 | return err
85 | }
86 | if err := binary.Read(session, binary.BigEndian, &pLength); err != nil {
87 | return err
88 | }
89 |
90 | // 获取用户名和密码内容
91 | username := make([]byte, uLength)
92 | password := make([]byte, pLength)
93 | if err := binary.Read(session, binary.BigEndian, &username); err != nil {
94 | return err
95 | }
96 |
97 | if err := binary.Read(session, binary.BigEndian, &password); err != nil {
98 | return err
99 | }
100 | // 对比用户名密码是否正确,如果不正确则报错
101 | if !bytes.Equal(auth.Username, username) || !bytes.Equal(auth.Password, password) {
102 | return fmt.Errorf("invalid username/password")
103 | }
104 | return nil
105 | }
106 |
--------------------------------------------------------------------------------
/security/security_vnc.go:
--------------------------------------------------------------------------------
1 | package security
2 |
3 | import (
4 | "bytes"
5 | "crypto/des"
6 | "encoding/binary"
7 | "fmt"
8 | "github.com/gogf/gf/v2/util/grand"
9 | "github.com/vprix/vncproxy/rfb"
10 | )
11 |
12 | // ChallengeLen 随机认证串的长度
13 | const ChallengeLen = 16
14 |
15 | // ServerAuthVNC vnc服务端使用vnc auth认证方式
16 | type ServerAuthVNC struct {
17 | Challenge []byte
18 | Password []byte
19 | Crypted []byte
20 | }
21 |
22 | var _ rfb.ISecurityHandler = new(ServerAuthVNC)
23 | var _ rfb.ISecurityHandler = new(ClientAuthVNC)
24 |
25 | func (*ServerAuthVNC) Type() rfb.SecurityType {
26 | return rfb.SecTypeVNC
27 | }
28 | func (*ServerAuthVNC) SubType() rfb.SecuritySubType {
29 | return rfb.SecSubTypeUnknown
30 | }
31 |
32 | // 写入随机字符串
33 | func (that *ServerAuthVNC) writeChallenge(session rfb.ISession) error {
34 | if err := binary.Write(session, binary.BigEndian, that.Challenge); err != nil {
35 | return err
36 | }
37 | return session.Flush()
38 | }
39 |
40 | func (that *ServerAuthVNC) ReadChallenge(session rfb.ISession) error {
41 | var crypted [ChallengeLen]byte
42 | if err := binary.Read(session, binary.BigEndian, &crypted); err != nil {
43 | return err
44 | }
45 | that.Crypted = crypted[:]
46 | return nil
47 | }
48 |
49 | func (that *ServerAuthVNC) Auth(session rfb.ISession) error {
50 |
51 | if len(that.Challenge) != ChallengeLen {
52 | that.Challenge = grand.B(ChallengeLen)
53 | }
54 |
55 | if err := that.writeChallenge(session); err != nil {
56 | return err
57 | }
58 | if err := that.ReadChallenge(session); err != nil {
59 | return err
60 | }
61 | // 加密随机认证串,并把加密后的串与客户端穿过来的串进行对比,如果对比一致,则说明密码一致
62 | encrypted, err := AuthVNCEncode(that.Password, that.Challenge)
63 | if err != nil {
64 | return err
65 | }
66 | if !bytes.Equal(encrypted, that.Crypted) {
67 | return fmt.Errorf("密码错误")
68 | }
69 | return nil
70 | }
71 |
72 | // ClientAuthVNC vnc 客户端使用vnc auth认证方式
73 | type ClientAuthVNC struct {
74 | Challenge []byte
75 | Password []byte
76 | }
77 |
78 | func (*ClientAuthVNC) Type() rfb.SecurityType {
79 | return rfb.SecTypeVNC
80 | }
81 | func (*ClientAuthVNC) SubType() rfb.SecuritySubType {
82 | return rfb.SecSubTypeUnknown
83 | }
84 |
85 | func (that *ClientAuthVNC) Auth(session rfb.ISession) error {
86 | if len(that.Password) == 0 {
87 | return fmt.Errorf("安全认证失败,因为没有传入VNCAuth认证方式所用的密码")
88 | }
89 | var challenge [ChallengeLen]byte
90 | if err := binary.Read(session, binary.BigEndian, &challenge); err != nil {
91 | return err
92 | }
93 | // 使用密码对认证串加密
94 | encrypted, err := AuthVNCEncode(that.Password, challenge[:])
95 | if err != nil {
96 | return err
97 | }
98 | // 发送加密后的认证串
99 | if err = binary.Write(session, binary.BigEndian, encrypted); err != nil {
100 | return err
101 | }
102 | return session.Flush()
103 | }
104 |
105 | // AuthVNCEncode 加密随机认证串
106 | func AuthVNCEncode(password []byte, challenge []byte) ([]byte, error) {
107 | if len(challenge) != ChallengeLen {
108 | return nil, fmt.Errorf("随机认证串的长度不正确,正确的应该是16字节")
109 | }
110 | // 截取密码的前八位,因为只有前八位才有用
111 | key := make([]byte, 8)
112 | copy(key, password)
113 |
114 | // 对密码的每个字节进行翻转
115 | for i := range key {
116 | key[i] = (key[i]&0x55)<<1 | (key[i]&0xAA)>>1 // Swap adjacent bits
117 | key[i] = (key[i]&0x33)<<2 | (key[i]&0xCC)>>2 // Swap adjacent pairs
118 | key[i] = (key[i]&0x0F)<<4 | (key[i]&0xF0)>>4 // Swap the 2 halves
119 | }
120 |
121 | // 使用密码对随即认证串进行加密
122 | cipher, err := des.NewCipher(key)
123 | if err != nil {
124 | return nil, err
125 | }
126 | for i := 0; i < len(challenge); i += cipher.BlockSize() {
127 | cipher.Encrypt(challenge[i:i+cipher.BlockSize()], challenge[i:i+cipher.BlockSize()])
128 | }
129 |
130 | return challenge, nil
131 | }
132 |
--------------------------------------------------------------------------------
/vnc/player.go:
--------------------------------------------------------------------------------
1 | package vnc
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "fmt"
7 | "github.com/gogf/gf/v2/container/gtype"
8 | "github.com/gogf/gf/v2/os/gfile"
9 | "github.com/osgochina/dmicro/logger"
10 | "github.com/vprix/vncproxy/handler"
11 | "github.com/vprix/vncproxy/messages"
12 | "github.com/vprix/vncproxy/rfb"
13 | "github.com/vprix/vncproxy/session"
14 | "io"
15 | "os"
16 | "sync"
17 | "time"
18 | )
19 |
20 | type Player struct {
21 | svrSession *session.ServerSession // vnc客户端连接到proxy的会话
22 | playerSession *session.PlayerSession
23 | errorCh chan error
24 | closed *gtype.Bool
25 | syncOnce sync.Once
26 | }
27 |
28 | func NewPlayer(filePath string, svrSession *session.ServerSession) *Player {
29 | playerSession := session.NewPlayerSession(
30 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {
31 | if !gfile.Exists(filePath) {
32 | return nil, fmt.Errorf("要读取的文件[%s]不存在", filePath)
33 | }
34 | return gfile.OpenFile(filePath, os.O_RDONLY, 0644)
35 | }),
36 | )
37 |
38 | return &Player{
39 | errorCh: make(chan error, 32),
40 | svrSession: svrSession,
41 | playerSession: playerSession,
42 | closed: gtype.NewBool(false),
43 | }
44 | }
45 |
46 | // Start 启动
47 | func (that *Player) Start() error {
48 |
49 | err := that.svrSession.Init(rfb.OptHandlers([]rfb.IHandler{
50 | &handler.ServerVersionHandler{},
51 | &handler.ServerSecurityHandler{},
52 | that, // 把链接到vnc服务端的逻辑加入
53 | &handler.ServerClientInitHandler{},
54 | &handler.ServerServerInitHandler{},
55 | &handler.ServerMessageHandler{},
56 | }...))
57 | if err != nil {
58 | return err
59 | }
60 |
61 | that.svrSession.Start()
62 | err = <-that.errorCh
63 | return err
64 | }
65 |
66 | // Handle 建立远程链接
67 | func (that *Player) Handle(sess rfb.ISession) error {
68 | that.playerSession.Start()
69 | that.svrSession = sess.(*session.ServerSession)
70 | that.svrSession.SetWidth(that.playerSession.Options().Width)
71 | that.svrSession.SetHeight(that.playerSession.Options().Height)
72 | that.svrSession.SetDesktopName(that.playerSession.Options().DesktopName)
73 | that.svrSession.SetPixelFormat(that.playerSession.Options().PixelFormat)
74 |
75 | go that.handleIO()
76 | return nil
77 | }
78 |
79 | func (that *Player) handleIO() {
80 | for that.closed.Val() == false {
81 | select {
82 | case <-that.svrSession.Wait():
83 | return
84 | case <-that.playerSession.Wait():
85 | return
86 | case err := <-that.svrSession.Options().ErrorCh:
87 | that.errorCh <- err
88 | that.Close()
89 | case err := <-that.playerSession.Options().ErrorCh:
90 | that.errorCh <- err
91 | that.Close()
92 | case msg := <-that.svrSession.Options().Output:
93 | if logger.IsDebug() {
94 | logger.Debugf(context.TODO(), "收到vnc客户端发送过来的消息,%s", msg)
95 | }
96 | if msg.Type() == rfb.MessageType(rfb.FramebufferUpdateRequest) {
97 | that.syncOnce.Do(func() {
98 | go that.readRbs()
99 | })
100 | }
101 | }
102 | }
103 | }
104 |
105 | func (that *Player) readRbs() {
106 | for that.closed.Val() == false {
107 | // 从会话中读取消息类型
108 | var messageType rfb.ServerMessageType
109 | if err := binary.Read(that.playerSession, binary.BigEndian, &messageType); err != nil {
110 | that.playerSession.Options().ErrorCh <- err
111 | return
112 | }
113 | msg := &messages.FramebufferUpdate{}
114 | // 读取消息内容
115 | parsedMsg, err := msg.Read(that.playerSession)
116 | if err != nil {
117 | that.playerSession.Options().ErrorCh <- err
118 | return
119 | }
120 | that.svrSession.Options().Input <- parsedMsg
121 | var sleep int64
122 | _ = binary.Read(that.playerSession, binary.BigEndian, &sleep)
123 | if sleep > 0 {
124 | time.Sleep(time.Duration(sleep))
125 | }
126 | }
127 | }
128 |
129 | func (that *Player) Close() {
130 | that.closed.Set(true)
131 | _ = that.svrSession.Close()
132 | _ = that.playerSession.Close()
133 | }
134 |
--------------------------------------------------------------------------------
/vnc/recorder.go:
--------------------------------------------------------------------------------
1 | package vnc
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "github.com/gogf/gf/v2/container/gtype"
7 | "github.com/gogf/gf/v2/os/gtime"
8 | "github.com/osgochina/dmicro/logger"
9 | "github.com/vprix/vncproxy/messages"
10 | "github.com/vprix/vncproxy/rfb"
11 | "github.com/vprix/vncproxy/session"
12 | )
13 |
14 | type Recorder struct {
15 | errorCh chan error
16 | closed *gtype.Bool
17 | cliSession *session.ClientSession // 链接到vnc服务端的会话
18 | recorderSession *session.RecorderSession
19 | }
20 |
21 | func NewRecorder(recorderSess *session.RecorderSession, cliSession *session.ClientSession) *Recorder {
22 | recorder := &Recorder{
23 | recorderSession: recorderSess,
24 | cliSession: cliSession,
25 | errorCh: make(chan error, 32),
26 | closed: gtype.NewBool(false),
27 | }
28 | return recorder
29 | }
30 |
31 | func (that *Recorder) Start() error {
32 | var err error
33 | that.cliSession.Start()
34 | encS := []rfb.EncodingType{
35 | rfb.EncCursorPseudo,
36 | rfb.EncPointerPosPseudo,
37 | rfb.EncCopyRect,
38 | rfb.EncZRLE,
39 | rfb.EncHexTile,
40 | rfb.EncZlib,
41 | rfb.EncRRE,
42 | }
43 | err = that.cliSession.SetEncodings(encS)
44 | if err != nil {
45 | return err
46 | }
47 | // 设置参数信息
48 | that.recorderSession.SetProtocolVersion(that.cliSession.ProtocolVersion())
49 | that.recorderSession.SetWidth(that.cliSession.Options().Width)
50 | that.recorderSession.SetHeight(that.cliSession.Options().Height)
51 | that.recorderSession.SetPixelFormat(that.cliSession.Options().PixelFormat)
52 | that.recorderSession.SetDesktopName(that.cliSession.Options().DesktopName)
53 | that.recorderSession.Start()
54 | reqMsg := messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}
55 | err = reqMsg.Write(that.cliSession)
56 | if err != nil {
57 | return err
58 | }
59 | var lastUpdate *gtime.Time
60 | for {
61 | select {
62 | case msg := <-that.recorderSession.Options().Output:
63 | logger.Debugf(context.TODO(), "client message received.messageType:%d,message:%s", msg.Type(), msg)
64 | case msg := <-that.cliSession.Options().Output:
65 | if rfb.ServerMessageType(msg.Type()) == rfb.FramebufferUpdate {
66 | err = msg.Write(that.recorderSession)
67 | if err != nil {
68 | return err
69 | }
70 | if lastUpdate == nil {
71 | _ = binary.Write(that.recorderSession, binary.BigEndian, int64(0))
72 | } else {
73 | secsPassed := gtime.Now().UnixNano() - lastUpdate.UnixNano()
74 | _ = binary.Write(that.recorderSession, binary.BigEndian, secsPassed)
75 | }
76 | err = that.recorderSession.Flush()
77 | if err != nil {
78 | return err
79 | }
80 | lastUpdate = gtime.Now()
81 | reqMsg = messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}
82 | err = reqMsg.Write(that.cliSession)
83 | if err != nil {
84 | return err
85 | }
86 | }
87 | case <-that.cliSession.Wait():
88 | return nil
89 | case <-that.recorderSession.Wait():
90 | return nil
91 | case err = <-that.cliSession.Options().ErrorCh:
92 | that.errorCh <- err
93 | that.Close()
94 | case err = <-that.recorderSession.Options().ErrorCh:
95 | that.errorCh <- err
96 | that.Close()
97 | case err = <-that.errorCh:
98 | return err
99 | }
100 | }
101 | }
102 |
103 | func (that *Recorder) Close() {
104 | that.closed.Set(true)
105 | _ = that.cliSession.Close()
106 | _ = that.recorderSession.Close()
107 | }
108 |
--------------------------------------------------------------------------------
/vnc/screenshot.go:
--------------------------------------------------------------------------------
1 | package vnc
2 |
3 | import (
4 | "fmt"
5 | "github.com/osgochina/dmicro/logger"
6 | "github.com/vprix/vncproxy/messages"
7 | "github.com/vprix/vncproxy/rfb"
8 | "github.com/vprix/vncproxy/security"
9 | "github.com/vprix/vncproxy/session"
10 | "golang.org/x/net/context"
11 | "io"
12 | "net"
13 | "time"
14 | )
15 |
16 | type Screenshot struct {
17 | cliSession *session.ClientSession // 链接到vnc服务端的会话
18 | canvasSession *session.CanvasSession
19 | timeout time.Duration
20 | }
21 |
22 | func NewScreenshot(targetCfg rfb.TargetConfig) *Screenshot {
23 | securityHandlers := []rfb.ISecurityHandler{
24 | &security.ClientAuthNone{},
25 | }
26 | if len(targetCfg.Password) > 0 {
27 | securityHandlers = []rfb.ISecurityHandler{
28 | &security.ClientAuthVNC{Password: targetCfg.Password},
29 | }
30 | }
31 | canvasSession := session.NewCanvasSession()
32 | cliSession := session.NewClient(
33 | rfb.OptSecurityHandlers(securityHandlers...),
34 | rfb.OptGetConn(func(sess rfb.ISession) (io.ReadWriteCloser, error) {
35 | return net.DialTimeout(targetCfg.GetNetwork(), targetCfg.Addr(), targetCfg.GetTimeout())
36 | }),
37 | )
38 | recorder := &Screenshot{
39 | canvasSession: canvasSession,
40 | cliSession: cliSession,
41 | timeout: targetCfg.GetTimeout(),
42 | }
43 | return recorder
44 | }
45 |
46 | func (that *Screenshot) GetImage() (io.ReadWriteCloser, error) {
47 | var err error
48 | that.cliSession.Start()
49 | encS := []rfb.EncodingType{
50 | rfb.EncCursorPseudo,
51 | rfb.EncPointerPosPseudo,
52 | rfb.EncHexTile,
53 | rfb.EncTight,
54 | rfb.EncZRLE,
55 | }
56 | defer func() {
57 | _ = that.cliSession.Close()
58 | }()
59 | err = that.cliSession.SetEncodings(encS)
60 | if err != nil {
61 | return nil, err
62 | }
63 | // 设置参数信息
64 | that.canvasSession.SetProtocolVersion(that.cliSession.ProtocolVersion())
65 | that.canvasSession.SetWidth(that.cliSession.Options().Width)
66 | that.canvasSession.SetHeight(that.cliSession.Options().Height)
67 | that.canvasSession.SetPixelFormat(that.cliSession.Options().PixelFormat)
68 | that.canvasSession.SetDesktopName(that.cliSession.Options().DesktopName)
69 | that.canvasSession.Start()
70 | defer func() {
71 | _ = that.canvasSession.Close()
72 | }()
73 | reqMsg := messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}
74 | err = reqMsg.Write(that.cliSession)
75 | if err != nil {
76 | return nil, err
77 | }
78 | ctx, cancel := context.WithTimeout(context.Background(), that.timeout)
79 | defer cancel()
80 | for {
81 | select {
82 | case <-ctx.Done():
83 | return nil, fmt.Errorf("获取截图超时")
84 | case msg := <-that.cliSession.Options().Output:
85 | if rfb.ServerMessageType(msg.Type()) == rfb.FramebufferUpdate {
86 | err = msg.Write(that.canvasSession)
87 | if err != nil {
88 | return nil, err
89 | }
90 | err = that.canvasSession.Flush()
91 | return that.canvasSession.Conn(), err
92 | }
93 | if logger.IsDebug() {
94 | logger.Debugf(context.TODO(), "获取到来自vnc服务端的消息%v", msg)
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/vnc/video.go:
--------------------------------------------------------------------------------
1 | package vnc
2 |
3 | import (
4 | "context"
5 | "github.com/osgochina/dmicro/logger"
6 | "github.com/vprix/vncproxy/encodings"
7 | "github.com/vprix/vncproxy/messages"
8 | "github.com/vprix/vncproxy/rfb"
9 | "github.com/vprix/vncproxy/security"
10 | "github.com/vprix/vncproxy/session"
11 | "io"
12 | "net"
13 | "time"
14 | )
15 |
16 | type Video struct {
17 | cliCfg *rfb.Options
18 | targetCfg rfb.TargetConfig
19 | cliSession *session.ClientSession // 链接到vnc服务端的会话
20 | canvasSession *session.CanvasSession
21 | }
22 |
23 | func NewVideo(cliCfg *rfb.Options, targetCfg rfb.TargetConfig) *Video {
24 | if cliCfg == nil {
25 | cliCfg = &rfb.Options{
26 | PixelFormat: rfb.PixelFormat32bit,
27 | Messages: messages.DefaultServerMessages,
28 | Encodings: encodings.DefaultEncodings,
29 | Output: make(chan rfb.Message),
30 | Input: make(chan rfb.Message),
31 | ErrorCh: make(chan error),
32 | }
33 | }
34 | if cliCfg.Output == nil {
35 | cliCfg.Output = make(chan rfb.Message)
36 | }
37 | if cliCfg.Input == nil {
38 | cliCfg.Input = make(chan rfb.Message)
39 | }
40 | if cliCfg.ErrorCh == nil {
41 | cliCfg.ErrorCh = make(chan error)
42 | }
43 | if len(targetCfg.Password) > 0 {
44 | cliCfg.SecurityHandlers = []rfb.ISecurityHandler{
45 | &security.ClientAuthVNC{Password: targetCfg.Password},
46 | }
47 | } else {
48 | cliCfg.SecurityHandlers = []rfb.ISecurityHandler{
49 | &security.ClientAuthNone{},
50 | }
51 | }
52 | recorder := &Video{
53 | //canvasSession: session.NewCanvasSession(*cliCfg),
54 | targetCfg: targetCfg,
55 | cliCfg: cliCfg,
56 | }
57 | return recorder
58 | }
59 |
60 | func (that *Video) Start() error {
61 | var err error
62 | timeout := 10 * time.Second
63 | if that.targetCfg.Timeout > 0 {
64 | timeout = that.targetCfg.Timeout
65 | }
66 | network := "tcp"
67 | if len(that.targetCfg.Network) > 0 {
68 | network = that.targetCfg.Network
69 | }
70 | that.cliCfg.GetConn = func(sess rfb.ISession) (io.ReadWriteCloser, error) {
71 | return net.DialTimeout(network, that.targetCfg.Addr(), timeout)
72 | }
73 | that.cliSession = session.NewClient()
74 |
75 | that.cliSession.Start()
76 | encS := []rfb.EncodingType{
77 | rfb.EncCursorPseudo,
78 | rfb.EncPointerPosPseudo,
79 | rfb.EncCopyRect,
80 | rfb.EncTight,
81 | rfb.EncZRLE,
82 | rfb.EncHexTile,
83 | rfb.EncZlib,
84 | rfb.EncRRE,
85 | }
86 | err = that.cliSession.SetEncodings(encS)
87 | if err != nil {
88 | return err
89 | }
90 | // 设置参数信息
91 | that.canvasSession.SetProtocolVersion(that.cliSession.ProtocolVersion())
92 | that.canvasSession.SetWidth(that.cliSession.Options().Width)
93 | that.canvasSession.SetHeight(that.cliSession.Options().Height)
94 | that.canvasSession.SetPixelFormat(that.cliSession.Options().PixelFormat)
95 | that.canvasSession.SetDesktopName(that.cliSession.Options().DesktopName)
96 | that.canvasSession.Start()
97 | reqMsg := messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}
98 | err = reqMsg.Write(that.cliSession)
99 | if err != nil {
100 | return err
101 | }
102 | for {
103 | select {
104 | case msg := <-that.cliCfg.Output:
105 | logger.Debugf(context.TODO(), "client message received.messageType:%d,message:%s", msg.Type(), msg)
106 | case msg := <-that.cliCfg.Input:
107 | if rfb.ServerMessageType(msg.Type()) == rfb.FramebufferUpdate {
108 | err = msg.Write(that.canvasSession)
109 | if err != nil {
110 | return err
111 | }
112 | err = that.canvasSession.Flush()
113 | if err != nil {
114 | return err
115 | }
116 | reqMsg = messages.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: that.cliSession.Options().Width, Height: that.cliSession.Options().Height}
117 | err = reqMsg.Write(that.cliSession)
118 | if err != nil {
119 | return err
120 | }
121 | }
122 | case err = <-that.cliCfg.ErrorCh:
123 | return err
124 | }
125 | }
126 | }
127 |
128 | func (that *Video) Close() {
129 | _ = that.cliSession.Close()
130 | _ = that.canvasSession.Close()
131 |
132 | }
133 |
--------------------------------------------------------------------------------