├── .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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 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 | ![帧缓冲器](../images/rfc6143-glassory-1.png) 6 | 7 | # 调色板 8 | 9 | 传统的帧缓冲器支持的色彩模式很广泛。受限于昂贵的内存,大多数早期的帧缓冲器使用的是1位、2位、4位或 8位的色深。小的色深导致不能产生完整的色彩范围。其解决方法是为帧缓冲器增加一个查找表(lookup table,LUT),把帧缓冲器中存储的每个“颜色”作为一个索引。这就是所谓的索引色(indexed color)模式。 10 | 11 | ![调色板](../images/rfc6143-glassory-2.png) 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 | ![游程编码](../images/rfc6143-glassory-3.png) 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 | ![RFB 协议通信](../images/rfc6143-readme-1.png) 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 | ![像素组成帧缓冲](../../images/rfc6143-transfer-pixel-format-1.png) 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 | --------------------------------------------------------------------------------