├── .circleci └── config.yml ├── .gitignore ├── .idea └── workspace.xml ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── button_string.go ├── buttons.go ├── client.go ├── clientmessagetype_string.go ├── conn.go ├── encoders ├── dv8-enc.go ├── dv9-enc.go ├── huffyuv-enc.go ├── image-enc.go ├── mjpeg-enc.go ├── qtrle-enc.go └── x264-enc.go ├── encoding.go ├── encoding_atenhermon.go ├── encoding_copyrect.go ├── encoding_corre.go ├── encoding_cursor.go ├── encoding_desktopname.go ├── encoding_desktopsize.go ├── encoding_hextile.go ├── encoding_pointer_pos.go ├── encoding_raw.go ├── encoding_rre.go ├── encoding_tight.go ├── encoding_tightpng.go ├── encoding_util.go ├── encoding_util_test.go ├── encoding_xcursor.go ├── encoding_zlib.go ├── encoding_zrle.go ├── encodingtype_string.go ├── example ├── client │ ├── debug │ ├── ffmpeg │ └── main.go ├── file-reader │ └── main.go ├── proxy │ └── main.go └── server │ └── main.go ├── fbs-connection.go ├── fbs-reader.go ├── go.mod ├── go.sum ├── handlers.go ├── image.go ├── key_string.go ├── keys.go ├── logger └── logger.go ├── messages.go ├── messages_aten.go ├── pixel_format.go ├── rgb-image.go ├── security.go ├── security_aten.go ├── security_none.go ├── security_tight.go ├── security_vencryptplain.go ├── security_vnc.go ├── securitysubtype_string.go ├── securitytype_string.go ├── server.go ├── tightcompression_string.go └── tightfilter_string.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 # use CircleCI 2.0 2 | jobs: # basic units of work in a run 3 | build: # runs not using Workflows must have a `build` job as entry point 4 | docker: # run the steps with Docker 5 | # CircleCI Go images available at: https://hub.docker.com/r/circleci/golang/ 6 | - image: circleci/golang:1.12 7 | # CircleCI PostgreSQL images available at: https://hub.docker.com/r/circleci/postgres/ 8 | - image: circleci/postgres:9.6-alpine 9 | environment: # environment variables for primary container 10 | POSTGRES_USER: circleci-demo-go 11 | POSTGRES_DB: circle_test 12 | 13 | parallelism: 2 14 | 15 | environment: # environment variables for the build itself 16 | TEST_RESULTS: /tmp/test-results # path to where test results will be saved 17 | 18 | steps: # steps that comprise the `build` job 19 | - checkout # check out source code to working directory 20 | - run: mkdir -p $TEST_RESULTS # create the test results directory 21 | 22 | - restore_cache: # restores saved cache if no changes are detected since last run 23 | keys: 24 | - go-mod-v4-{{ checksum "go.sum" }} 25 | 26 | # Wait for Postgres to be ready before proceeding 27 | - run: 28 | name: Waiting for Postgres to be ready 29 | command: dockerize -wait tcp://localhost:5432 -timeout 1m 30 | 31 | - run: 32 | name: Run unit tests 33 | environment: # environment variables for the database url and path to migration files 34 | CONTACTS_DB_URL: "postgres://circleci-demo-go@localhost:5432/circle_test?sslmode=disable" 35 | CONTACTS_DB_MIGRATIONS: /home/circleci/project/db/migrations 36 | 37 | # store the results of our tests in the $TEST_RESULTS directory 38 | command: | 39 | PACKAGE_NAMES=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname) 40 | gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES 41 | 42 | # - run: make # pull and build dependencies for the project 43 | 44 | # - save_cache: 45 | # key: go-mod-v4-{{ checksum "go.sum" }} 46 | # paths: 47 | # - "/go/pkg/mod" 48 | 49 | # - run: 50 | # name: Start service 51 | # environment: 52 | # CONTACTS_DB_URL: "postgres://circleci-demo-go@localhost:5432/circle_test?sslmode=disable" 53 | # CONTACTS_DB_MIGRATIONS: /home/circleci/project/db/migrations 54 | # command: ./workdir/contacts 55 | # background: true # keep service running and proceed to next step 56 | 57 | # - run: 58 | # name: Validate service is working 59 | # command: | 60 | # sleep 5 61 | # curl --retry 10 --retry-delay 1 -X POST --header "Content-Type: application/json" -d '{"email":"test@example.com","name":"Test User"}' http://localhost:8080/contacts 62 | 63 | # - store_artifacts: # upload test summary for display in Artifacts 64 | # path: /tmp/test-results 65 | # destination: raw-test-output 66 | 67 | # - store_test_results: # upload test results for display in Test Summary 68 | # path: /tmp/test-results 69 | workflows: 70 | version: 2 71 | build-workflow: 72 | jobs: 73 | - build 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | debug 13 | ffmpeg 14 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 15 | .glide/ 16 | .idea/ 17 | workspace.xml 18 | *.avi 19 | *.mp4 20 | *.mov 21 | *.idx_ 22 | example/client/client 23 | example/server/server 24 | example/proxy/proxy 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Client", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "debug", 12 | "args": [ 13 | "localhost:5903" 14 | ], 15 | "program": "${workspaceRoot}/example/client" 16 | }, 17 | { 18 | "name": "Launch FbsReader", 19 | "type": "go", 20 | "request": "launch", 21 | "mode": "debug", 22 | "args": [ 23 | "/Users/amitbet/Downloads/vncproxy-darwin-amd64-v1.0/myfbs.fbs" 24 | ], 25 | "program": "${workspaceRoot}/example/file-reader" 26 | } 27 | 28 | ] 29 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "go", 4 | "isShellCommand": true, 5 | "echoCommand": true, 6 | "showOutput": "always", 7 | // "showOutput": "silent", 8 | "options": { 9 | // "env": { 10 | // "GOPATH": "/Users/lukeh/dd/go" 11 | // } 12 | }, 13 | "tasks": [ 14 | { 15 | "taskName": "install", 16 | "args": [ 17 | "-v", 18 | "./..." 19 | ], 20 | "osx": { 21 | "options": { 22 | "env": { 23 | //"GOPATH": "${env.HOME}/Dropbox/go" 24 | } 25 | } 26 | }, 27 | "windows": { 28 | "options": { 29 | "env": { 30 | //"GOPATH": "${env.USERPROFILE}\\Dropbox\\go" 31 | } 32 | } 33 | }, 34 | "isBuildCommand": true, 35 | "problemMatcher": "$go" 36 | }, 37 | { 38 | "taskName": "test", 39 | "args": [ 40 | "-v", 41 | "./..." 42 | ], 43 | "isTestCommand": true 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 Mitchell Hashimoto 4 | Copyright (c) 2016-2017 Kate Ward 5 | Copyright (c) 2017 Vasiliy Tolstov 6 | Copyright (c) 2018 Amit Bezalel 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vnc2Video [![CircleCI](https://circleci.com/gh/amitbet/vnc2video.svg?style=shield)](https://circleci.com/gh/amitbet/vnc2video) [![MIT Licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/CircleCI-Public/circleci-demo-go/master/LICENSE.md) 2 | ## A **real wold** implementation of vnc client for go 3 | After searching the web for an vnc client in golang which is not a toy & support more than handshake + RAW encoding, I came up blank, so, I set out to write one myself. 4 | 5 | The video encoding part means that something can be viewed, and since I don't really feel like writing GTK UIs in 2018 (plus VNC viewers are a dime a dozen), a video file will do. 6 | In actuality the images produced are go images and can easily be saved as JPEG, or displayed in any UI you want to create. 7 | 8 | ## Encoding support: 9 | * Tight VNC 10 | * Hextile 11 | * ZLIB 12 | * CopyRect 13 | * Raw 14 | * RRE 15 | * ZRLE 16 | * Rich-cursor pseudo 17 | * Desktop Size Pseudo 18 | * Cursor pos Pseudo 19 | 20 | ## Video codec support: 21 | * x264 (ffmpeg) - the market standard 22 | * dv8 (ffmpeg) - google encoding current standard for webm 23 | * dv9 (ffmpeg) - a stronger codec supported by webm format on most browsers 24 | * qtrle (ffmpeg) - the best losless encoding I could find. (10 - 20 MB/min) 25 | * huffyuv (ffmpeg) - a lossless encoding which is low-Cpu but less compressed (50-100 MB/min) 26 | * MJpeg (native golang implementation) - lossy intra frame only (every frame encoded separately) 27 | 28 | ## Frame Buffer Stream file support (fbs) 29 | * Supports reading & rendering fbs files that can be created by [vncProxy](https://github.com/amitbet/vncproxy) 30 | * This allows recording vnc without the cost of video encoding while retaining the ability to transcode it into video later if the vnc session is found to be important. 31 | 32 | ## About 33 | It may seem strange that I didn't use my previous vncproxy code in order to create this client, but since that code is highly optimized to be a proxy (never hold a full message in buffer & introduce no lags), it is not best suited to be a client, so instead of spending the time reverting all the proxy-specific code, I just started from the most advanced go vnc-client code I found. 34 | 35 | Most of what I added is the rfb-encoder & video encoding implementations, there are naturally some additional changes in order to get a global canvas (draw.Image) to render on by all encodings. 36 | 37 | The code for the encodings was gathered by peeking at several RFB source codes in cpp & some in java, reading the excellent documentation in [rfbproto](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst), and **a lot** of gritty bit-plucking, pixel jogging & code cajoling until everything fell into place on screen. 38 | 39 | I did not include tightPng in the supported encoding list since I didn't find a server to test it with, so I can't vouch for the previous implementation, If you have such a server handy, please check and tell me if it works. 40 | -------------------------------------------------------------------------------- /button_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Button"; DO NOT EDIT. 2 | 3 | package vnc2video 4 | 5 | import "fmt" 6 | 7 | const ( 8 | _Button_name_0 = "BtnNoneBtnLeftBtnMiddle" 9 | _Button_name_1 = "BtnRight" 10 | _Button_name_2 = "BtnFour" 11 | _Button_name_3 = "BtnFive" 12 | _Button_name_4 = "BtnSix" 13 | _Button_name_5 = "BtnSeven" 14 | _Button_name_6 = "BtnEight" 15 | ) 16 | 17 | var ( 18 | _Button_index_0 = [...]uint8{0, 7, 14, 23} 19 | _Button_index_1 = [...]uint8{0, 8} 20 | _Button_index_2 = [...]uint8{0, 7} 21 | _Button_index_3 = [...]uint8{0, 7} 22 | _Button_index_4 = [...]uint8{0, 6} 23 | _Button_index_5 = [...]uint8{0, 8} 24 | _Button_index_6 = [...]uint8{0, 8} 25 | ) 26 | 27 | func (i Button) String() string { 28 | switch { 29 | case 0 <= i && i <= 2: 30 | return _Button_name_0[_Button_index_0[i]:_Button_index_0[i+1]] 31 | case i == 4: 32 | return _Button_name_1 33 | case i == 8: 34 | return _Button_name_2 35 | case i == 16: 36 | return _Button_name_3 37 | case i == 32: 38 | return _Button_name_4 39 | case i == 64: 40 | return _Button_name_5 41 | case i == 128: 42 | return _Button_name_6 43 | default: 44 | return fmt.Sprintf("Button(%d)", i) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /buttons.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | // Button represents a mask of pointer presses/releases. 4 | type Button uint8 5 | 6 | //go:generate stringer -type=Button 7 | 8 | // All available button mask components. 9 | const ( 10 | BtnLeft Button = 1 << iota 11 | BtnMiddle 12 | BtnRight 13 | BtnFour 14 | BtnFive 15 | BtnSix 16 | BtnSeven 17 | BtnEight 18 | BtnNone Button = 0 19 | ) 20 | 21 | // Mask returns button mask 22 | func Mask(button Button) uint8 { 23 | return uint8(button) 24 | } 25 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "encoding/binary" 7 | "fmt" 8 | "net" 9 | "sync" 10 | "github.com/amitbet/vnc2video/logger" 11 | ) 12 | 13 | var ( 14 | // DefaultClientHandlers represents default client handlers 15 | DefaultClientHandlers = []Handler{ 16 | &DefaultClientVersionHandler{}, 17 | &DefaultClientSecurityHandler{}, 18 | &DefaultClientClientInitHandler{}, 19 | &DefaultClientServerInitHandler{}, 20 | &DefaultClientMessageHandler{}, 21 | } 22 | ) 23 | 24 | // Connect handshake with remote server using underlining net.Conn 25 | func Connect(ctx context.Context, c net.Conn, cfg *ClientConfig) (*ClientConn, error) { 26 | conn, err := NewClientConn(c, cfg) 27 | if err != nil { 28 | conn.Close() 29 | cfg.ErrorCh <- err 30 | return nil, err 31 | } 32 | 33 | if len(cfg.Handlers) == 0 { 34 | cfg.Handlers = DefaultClientHandlers 35 | } 36 | 37 | for _, h := range cfg.Handlers { 38 | if err := h.Handle(conn); err != nil { 39 | logger.Error("Handshake failed, check that server is running: ", err) 40 | conn.Close() 41 | cfg.ErrorCh <- err 42 | return nil, err 43 | } 44 | } 45 | 46 | canvas := NewVncCanvas(int(conn.Width()), int(conn.Height())) 47 | canvas.DrawCursor = cfg.DrawCursor 48 | conn.Canvas = canvas 49 | return conn, nil 50 | } 51 | 52 | var _ Conn = (*ClientConn)(nil) 53 | 54 | // Config returns connection config 55 | func (c *ClientConn) Config() interface{} { 56 | return c.cfg 57 | } 58 | 59 | func (c *ClientConn) GetEncInstance(typ EncodingType) Encoding { 60 | for _, enc := range c.encodings { 61 | if enc.Type() == typ { 62 | return enc 63 | } 64 | } 65 | return nil 66 | } 67 | 68 | // Wait waiting for connection close 69 | func (c *ClientConn) Wait() { 70 | <-c.quit 71 | } 72 | 73 | // Conn return underlining net.Conn 74 | func (c *ClientConn) Conn() net.Conn { 75 | return c.c 76 | } 77 | 78 | // SetProtoVersion sets proto version 79 | func (c *ClientConn) SetProtoVersion(pv string) { 80 | c.protocol = pv 81 | } 82 | 83 | // SetEncodings write SetEncodings message 84 | func (c *ClientConn) SetEncodings(encs []EncodingType) error { 85 | 86 | msg := &SetEncodings{ 87 | EncNum: uint16(len(encs)), 88 | Encodings: encs, 89 | } 90 | 91 | return msg.Write(c) 92 | } 93 | 94 | // Flush flushes data to conn 95 | func (c *ClientConn) Flush() error { 96 | return c.bw.Flush() 97 | } 98 | 99 | // Close closing conn 100 | func (c *ClientConn) Close() error { 101 | if c.quit != nil { 102 | close(c.quit) 103 | c.quit = nil 104 | } 105 | if c.quitCh != nil { 106 | close(c.quitCh) 107 | } 108 | return c.c.Close() 109 | } 110 | 111 | // Read reads data from conn 112 | func (c *ClientConn) Read(buf []byte) (int, error) { 113 | return c.br.Read(buf) 114 | } 115 | 116 | // Write data to conn must be Flushed 117 | func (c *ClientConn) Write(buf []byte) (int, error) { 118 | return c.bw.Write(buf) 119 | } 120 | 121 | // ColorMap returns color map 122 | func (c *ClientConn) ColorMap() ColorMap { 123 | return c.colorMap 124 | } 125 | 126 | // SetColorMap sets color map 127 | func (c *ClientConn) SetColorMap(cm ColorMap) { 128 | c.colorMap = cm 129 | } 130 | 131 | // DesktopName returns connection desktop name 132 | func (c *ClientConn) DesktopName() []byte { 133 | return c.desktopName 134 | } 135 | 136 | // PixelFormat returns connection pixel format 137 | func (c *ClientConn) PixelFormat() PixelFormat { 138 | return c.pixelFormat 139 | } 140 | 141 | // SetDesktopName sets desktop name 142 | func (c *ClientConn) SetDesktopName(name []byte) { 143 | c.desktopName = name 144 | } 145 | 146 | // SetPixelFormat sets pixel format 147 | func (c *ClientConn) SetPixelFormat(pf PixelFormat) error { 148 | c.pixelFormat = pf 149 | return nil 150 | } 151 | 152 | // Encodings returns client encodings 153 | func (c *ClientConn) Encodings() []Encoding { 154 | return c.encodings 155 | } 156 | 157 | // Width returns width 158 | func (c *ClientConn) Width() uint16 { 159 | return c.fbWidth 160 | } 161 | 162 | // Height returns height 163 | func (c *ClientConn) Height() uint16 { 164 | return c.fbHeight 165 | } 166 | 167 | // Protocol returns protocol 168 | func (c *ClientConn) Protocol() string { 169 | return c.protocol 170 | } 171 | 172 | // SetWidth sets width of client conn 173 | func (c *ClientConn) SetWidth(width uint16) { 174 | c.fbWidth = width 175 | } 176 | 177 | // SetHeight sets height of client conn 178 | func (c *ClientConn) SetHeight(height uint16) { 179 | c.fbHeight = height 180 | } 181 | 182 | // SecurityHandler returns security handler 183 | func (c *ClientConn) SecurityHandler() SecurityHandler { 184 | return c.securityHandler 185 | } 186 | 187 | // SetSecurityHandler sets security handler 188 | func (c *ClientConn) SetSecurityHandler(sechandler SecurityHandler) error { 189 | c.securityHandler = sechandler 190 | return nil 191 | } 192 | 193 | // The ClientConn type holds client connection information 194 | type ClientConn struct { 195 | c net.Conn 196 | br *bufio.Reader 197 | bw *bufio.Writer 198 | cfg *ClientConfig 199 | protocol string 200 | // If the pixel format uses a color map, then this is the color 201 | // map that is used. This should not be modified directly, since 202 | // the data comes from the server. 203 | // Definition in §5 - Representation of Pixel Data. 204 | colorMap ColorMap 205 | Canvas *VncCanvas 206 | // Name associated with the desktop, sent from the server. 207 | desktopName []byte 208 | 209 | // Encodings supported by the client. This should not be modified 210 | // directly. Instead, SetEncodings() should be used. 211 | encodings []Encoding 212 | 213 | securityHandler SecurityHandler 214 | 215 | // Height of the frame buffer in pixels, sent from the server. 216 | fbHeight uint16 217 | 218 | // Width of the frame buffer in pixels, sent from the server. 219 | fbWidth uint16 220 | 221 | // The pixel format associated with the connection. This shouldn't 222 | // be modified. If you wish to set a new pixel format, use the 223 | // SetPixelFormat method. 224 | pixelFormat PixelFormat 225 | 226 | quitCh chan struct{} 227 | quit chan struct{} 228 | errorCh chan error 229 | } 230 | 231 | func (cc *ClientConn) ResetAllEncodings() { 232 | for _, enc := range cc.encodings { 233 | enc.Reset() 234 | } 235 | } 236 | 237 | // NewClientConn creates new client conn using config 238 | func NewClientConn(c net.Conn, cfg *ClientConfig) (*ClientConn, error) { 239 | if len(cfg.Encodings) == 0 { 240 | return nil, fmt.Errorf("client can't handle encodings") 241 | } 242 | return &ClientConn{ 243 | c: c, 244 | cfg: cfg, 245 | br: bufio.NewReader(c), 246 | bw: bufio.NewWriter(c), 247 | encodings: cfg.Encodings, 248 | quitCh: cfg.QuitCh, 249 | errorCh: cfg.ErrorCh, 250 | pixelFormat: cfg.PixelFormat, 251 | quit: make(chan struct{}), 252 | }, nil 253 | } 254 | 255 | // DefaultClientMessageHandler represents default client message handler 256 | type DefaultClientMessageHandler struct{} 257 | 258 | // Handle handles server messages. 259 | func (*DefaultClientMessageHandler) Handle(c Conn) error { 260 | logger.Trace("starting DefaultClientMessageHandler") 261 | cfg := c.Config().(*ClientConfig) 262 | var err error 263 | var wg sync.WaitGroup 264 | wg.Add(2) 265 | //defer c.Close() 266 | 267 | serverMessages := make(map[ServerMessageType]ServerMessage) 268 | for _, m := range cfg.Messages { 269 | serverMessages[m.Type()] = m 270 | } 271 | 272 | go func() { 273 | defer wg.Done() 274 | for { 275 | select { 276 | case msg := <-cfg.ClientMessageCh: 277 | if err = msg.Write(c); err != nil { 278 | cfg.ErrorCh <- err 279 | return 280 | } 281 | } 282 | } 283 | }() 284 | 285 | go func() { 286 | defer wg.Done() 287 | for { 288 | select { 289 | default: 290 | var messageType ServerMessageType 291 | if err = binary.Read(c, binary.BigEndian, &messageType); err != nil { 292 | cfg.ErrorCh <- err 293 | return 294 | } 295 | logger.Infof("========got server message, msgType=%d", messageType) 296 | msg, ok := serverMessages[messageType] 297 | if !ok { 298 | err = fmt.Errorf("unknown message-type: %v", messageType) 299 | cfg.ErrorCh <- err 300 | return 301 | } 302 | canvas := c.(*ClientConn).Canvas 303 | canvas.RemoveCursor() 304 | parsedMsg, err := msg.Read(c) 305 | canvas.PaintCursor() 306 | //canvas.SwapBuffers() 307 | logger.Debugf("============== End Message: type=%d ==============", messageType) 308 | 309 | if err != nil { 310 | cfg.ErrorCh <- err 311 | return 312 | } 313 | cfg.ServerMessageCh <- parsedMsg 314 | } 315 | } 316 | }() 317 | //encodings := c.Encodings() 318 | encTypes := make(map[EncodingType]EncodingType) 319 | for _, myEnc := range c.Encodings() { 320 | encTypes[myEnc.Type()] = myEnc.Type() 321 | //encTypes = append(encTypes, myEnc.Type()) 322 | } 323 | v := make([]EncodingType, 0, len(encTypes)) 324 | 325 | for _, value := range encTypes { 326 | v = append(v, value) 327 | } 328 | logger.Tracef("setting encodings: %v", v) 329 | c.SetEncodings(v) 330 | 331 | firstMsg := FramebufferUpdateRequest{Inc: 0, X: 0, Y: 0, Width: c.Width(), Height: c.Height()} 332 | logger.Tracef("sending initial req message: %v", firstMsg) 333 | firstMsg.Write(c) 334 | 335 | //wg.Wait() 336 | return nil 337 | } 338 | 339 | // A ClientConfig structure is used to configure a ClientConn. After 340 | // one has been passed to initialize a connection, it must not be modified. 341 | type ClientConfig struct { 342 | Handlers []Handler 343 | SecurityHandlers []SecurityHandler 344 | Encodings []Encoding 345 | PixelFormat PixelFormat 346 | ColorMap ColorMap 347 | ClientMessageCh chan ClientMessage 348 | ServerMessageCh chan ServerMessage 349 | Exclusive bool 350 | DrawCursor bool 351 | Messages []ServerMessage 352 | QuitCh chan struct{} 353 | ErrorCh chan error 354 | quit chan struct{} 355 | } 356 | -------------------------------------------------------------------------------- /clientmessagetype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=ClientMessageType"; DO NOT EDIT. 2 | 3 | package vnc2video 4 | 5 | import "fmt" 6 | 7 | const ( 8 | _ClientMessageType_name_0 = "SetPixelFormatMsgType" 9 | _ClientMessageType_name_1 = "SetEncodingsMsgTypeFramebufferUpdateRequestMsgTypeKeyEventMsgTypePointerEventMsgTypeClientCutTextMsgType" 10 | ) 11 | 12 | var ( 13 | _ClientMessageType_index_0 = [...]uint8{0, 21} 14 | _ClientMessageType_index_1 = [...]uint8{0, 19, 50, 65, 84, 104} 15 | ) 16 | 17 | func (i ClientMessageType) String() string { 18 | switch { 19 | case i == 0: 20 | return _ClientMessageType_name_0 21 | case 2 <= i && i <= 6: 22 | i -= 2 23 | return _ClientMessageType_name_1[_ClientMessageType_index_1[i]:_ClientMessageType_index_1[i+1]] 24 | default: 25 | return fmt.Sprintf("ClientMessageType(%d)", i) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "io" 5 | "net" 6 | ) 7 | 8 | // Conn represents vnc conection 9 | type Conn interface { 10 | io.ReadWriteCloser 11 | Conn() net.Conn 12 | Config() interface{} 13 | Protocol() string 14 | PixelFormat() PixelFormat 15 | SetPixelFormat(PixelFormat) error 16 | ColorMap() ColorMap 17 | SetColorMap(ColorMap) 18 | Encodings() []Encoding 19 | SetEncodings([]EncodingType) error 20 | Width() uint16 21 | Height() uint16 22 | SetWidth(uint16) 23 | SetHeight(uint16) 24 | DesktopName() []byte 25 | SetDesktopName([]byte) 26 | Flush() error 27 | Wait() 28 | SetProtoVersion(string) 29 | SetSecurityHandler(SecurityHandler) error 30 | SecurityHandler() SecurityHandler 31 | GetEncInstance(EncodingType) Encoding 32 | } 33 | -------------------------------------------------------------------------------- /encoders/dv8-enc.go: -------------------------------------------------------------------------------- 1 | package encoders 2 | 3 | import ( 4 | "image" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "github.com/amitbet/vnc2video/logger" 10 | ) 11 | 12 | type VP8ImageEncoder struct { 13 | cmd *exec.Cmd 14 | FFMpegBinPath string 15 | input io.WriteCloser 16 | closed bool 17 | Framerate int 18 | } 19 | 20 | func (enc *VP8ImageEncoder) Init(videoFileName string) { 21 | fileExt := ".webm" 22 | if enc.Framerate == 0 { 23 | enc.Framerate = 12 24 | } 25 | if !strings.HasSuffix(videoFileName, fileExt) { 26 | videoFileName = videoFileName + fileExt 27 | } 28 | binary := "./ffmpeg" 29 | cmd := exec.Command(binary, 30 | "-f", "image2pipe", 31 | "-vcodec", "ppm", 32 | //"-r", strconv.Itoa(framerate), 33 | "-vsync", "2", 34 | "-r", "5", 35 | "-probesize", "10000000", 36 | "-an", //no audio 37 | //"-vsync", "2", 38 | ///"-probesize", "10000000", 39 | "-y", 40 | //"-i", "pipe:0", 41 | "-i", "-", 42 | 43 | //"-crf", "4", 44 | "-vcodec", "libvpx", //"libvpx",//"libvpx-vp9"//"libx264" 45 | "-b:v", "0.5M", 46 | //"-maxrate", "1.5M", 47 | 48 | "-threads", "8", 49 | //"-speed", "0", 50 | //"-lossless", "1", //for vpx 51 | // "-tile-columns", "6", 52 | //"-frame-parallel", "1", 53 | // "-an", "-f", "webm", 54 | 55 | //"-preset", "ultrafast", 56 | //"-deadline", "realtime", 57 | "-quality", "good", 58 | "-cpu-used", "-16", 59 | "-minrate", "0.2M", 60 | "-maxrate", "0.7M", 61 | "-bufsize", "50M", 62 | "-g", "180", 63 | "-keyint_min", "180", 64 | "-rc_lookahead", "20", 65 | //"-crf", "34", 66 | //"-profile", "0", 67 | "-qmax", "51", 68 | "-qmin", "3", 69 | //"-slices", "4", 70 | //"-vb", "2M", 71 | 72 | videoFileName, 73 | ) 74 | //cmd := exec.Command("/bin/echo") 75 | 76 | //io.Copy(cmd.Stdout, os.Stdout) 77 | cmd.Stdout = os.Stdout 78 | cmd.Stderr = os.Stderr 79 | 80 | encInput, err := cmd.StdinPipe() 81 | enc.input = encInput 82 | if err != nil { 83 | logger.Error("can't get ffmpeg input pipe") 84 | } 85 | enc.cmd = cmd 86 | } 87 | func (enc *VP8ImageEncoder) Run(videoFileName string) { 88 | if _, err := os.Stat(enc.FFMpegBinPath); os.IsNotExist(err) { 89 | logger.Error("encoder file doesn't exist in path:", enc.FFMpegBinPath) 90 | return 91 | } 92 | 93 | enc.Init(videoFileName) 94 | logger.Debugf("launching binary: %v", enc.cmd) 95 | err := enc.cmd.Run() 96 | if err != nil { 97 | logger.Errorf("error while launching ffmpeg: %v\n err: %v", enc.cmd.Args, err) 98 | } 99 | } 100 | func (enc *VP8ImageEncoder) Encode(img image.Image) { 101 | if enc.input == nil || enc.closed { 102 | return 103 | } 104 | 105 | err := encodePPM(enc.input, img) 106 | if err != nil { 107 | logger.Error("error while encoding image:", err) 108 | } 109 | } 110 | 111 | func (enc *VP8ImageEncoder) Close() { 112 | enc.closed = true 113 | } 114 | -------------------------------------------------------------------------------- /encoders/dv9-enc.go: -------------------------------------------------------------------------------- 1 | package encoders 2 | 3 | import ( 4 | "image" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "github.com/amitbet/vnc2video/logger" 10 | ) 11 | 12 | type DV9ImageEncoder struct { 13 | cmd *exec.Cmd 14 | FFMpegBinPath string 15 | input io.WriteCloser 16 | Framerate int 17 | } 18 | 19 | func (enc *DV9ImageEncoder) Init(videoFileName string) { 20 | fileExt := ".mp4" 21 | if enc.Framerate == 0 { 22 | enc.Framerate = 12 23 | } 24 | if !strings.HasSuffix(videoFileName, fileExt) { 25 | videoFileName = videoFileName + fileExt 26 | } 27 | binary := "./ffmpeg" 28 | cmd := exec.Command(binary, 29 | "-f", "image2pipe", 30 | "-vcodec", "ppm", 31 | //"-r", strconv.Itoa(framerate), 32 | "-r", "5", 33 | //"-i", "pipe:0", 34 | "-i", "-", 35 | "-vcodec", "libvpx-vp9", //"libvpx",//"libvpx-vp9"//"libx264" 36 | "-b:v", "1M", 37 | "-threads", "8", 38 | //"-speed", "0", 39 | //"-lossless", "1", //for vpx 40 | // "-tile-columns", "6", 41 | //"-frame-parallel", "1", 42 | // "-an", "-f", "webm", 43 | "-cpu-used", "-8", 44 | 45 | //"-preset", "ultrafast", 46 | "-deadline", "realtime", 47 | //"-cpu-used", "-5", 48 | "-maxrate", "2.5M", 49 | "-bufsize", "10M", 50 | "-g", "120", 51 | 52 | //"-rc_lookahead", "16", 53 | //"-profile", "0", 54 | "-qmax", "51", 55 | "-qmin", "11", 56 | //"-slices", "4", 57 | //"-vb", "2M", 58 | 59 | videoFileName, 60 | ) 61 | //cmd := exec.Command("/bin/echo") 62 | 63 | //io.Copy(cmd.Stdout, os.Stdout) 64 | cmd.Stdout = os.Stdout 65 | cmd.Stderr = os.Stderr 66 | 67 | encInput, err := cmd.StdinPipe() 68 | enc.input = encInput 69 | if err != nil { 70 | logger.Error("can't get ffmpeg input pipe") 71 | } 72 | enc.cmd = cmd 73 | } 74 | func (enc *DV9ImageEncoder) Run(videoFileName string) { 75 | if _, err := os.Stat(enc.FFMpegBinPath); os.IsNotExist(err) { 76 | logger.Error("encoder file doesn't exist in path:", enc.FFMpegBinPath) 77 | return 78 | } 79 | 80 | enc.Init(videoFileName) 81 | logger.Debugf("launching binary: %v", enc.cmd) 82 | err := enc.cmd.Run() 83 | if err != nil { 84 | logger.Errorf("error while launching ffmpeg: %v\n err: %v", enc.cmd.Args, err) 85 | } 86 | } 87 | func (enc *DV9ImageEncoder) Encode(img image.Image) { 88 | err := encodePPM(enc.input, img) 89 | if err != nil { 90 | logger.Error("error while encoding image:", err) 91 | } 92 | } 93 | func (enc *DV9ImageEncoder) Close() { 94 | 95 | } 96 | -------------------------------------------------------------------------------- /encoders/huffyuv-enc.go: -------------------------------------------------------------------------------- 1 | package encoders 2 | 3 | import ( 4 | "errors" 5 | "image" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "github.com/amitbet/vnc2video/logger" 11 | ) 12 | 13 | // this is a very common loseless encoder (but produces huge files) 14 | type HuffYuvImageEncoder struct { 15 | FFMpegBinPath string 16 | cmd *exec.Cmd 17 | input io.WriteCloser 18 | closed bool 19 | Framerate int 20 | } 21 | 22 | func (enc *HuffYuvImageEncoder) Init(videoFileName string) { 23 | if enc.Framerate == 0 { 24 | enc.Framerate = 12 25 | } 26 | 27 | fileExt := ".avi" 28 | if !strings.HasSuffix(videoFileName, fileExt) { 29 | videoFileName = videoFileName + fileExt 30 | } 31 | //binary := "./ffmpeg" 32 | cmd := exec.Command(enc.FFMpegBinPath, 33 | "-f", "image2pipe", 34 | "-vcodec", "ppm", 35 | //"-r", strconv.Itoa(framerate), 36 | "-r", "12", 37 | 38 | //"-re", 39 | //"-i", "pipe:0", 40 | "-an", //no audio 41 | //"-vsync", "2", 42 | ///"-probesize", "10000000", 43 | "-y", 44 | 45 | "-i", "-", 46 | //"–s", "640×360", 47 | "-vcodec", "huffyuv", //"libvpx",//"libvpx-vp9"//"libx264" 48 | //"-b:v", "0.33M", 49 | "-threads", "7", 50 | ///"-coder", "1", 51 | ///"-bf", "0", 52 | ///"-me_method", "hex", 53 | //"-speed", "0", 54 | //"-lossless", "1", //for vpx 55 | // "-an", "-f", "webm", 56 | "-preset", "veryfast", 57 | //"-tune", "animation", 58 | "-maxrate", "0.5M", 59 | "-bufsize", "50M", 60 | "-g", "250", 61 | 62 | //"-crf", "0", //for lossless encoding!!!! 63 | 64 | //"-rc_lookahead", "16", 65 | //"-profile", "0", 66 | "-crf", "34", 67 | //"-qmax", "51", 68 | //"-qmin", "7", 69 | //"-slices", "4", 70 | //"-vb", "2M", 71 | 72 | videoFileName, 73 | ) 74 | //cmd := exec.Command("/bin/echo") 75 | 76 | //io.Copy(cmd.Stdout, os.Stdout) 77 | cmd.Stdout = os.Stdout 78 | cmd.Stderr = os.Stderr 79 | 80 | encInput, err := cmd.StdinPipe() 81 | enc.input = encInput 82 | if err != nil { 83 | logger.Error("can't get ffmpeg input pipe") 84 | } 85 | enc.cmd = cmd 86 | } 87 | func (enc *HuffYuvImageEncoder) Run(videoFileName string) error { 88 | if _, err := os.Stat(enc.FFMpegBinPath); os.IsNotExist(err) { 89 | logger.Error("encoder file doesn't exist in path:", enc.FFMpegBinPath) 90 | return errors.New("encoder file doesn't exist in path" + videoFileName) 91 | } 92 | 93 | enc.Init(videoFileName) 94 | logger.Debugf("launching binary: %v", enc.cmd) 95 | err := enc.cmd.Run() 96 | if err != nil { 97 | logger.Errorf("error while launching ffmpeg: %v\n err: %v", enc.cmd.Args, err) 98 | return err 99 | } 100 | return nil 101 | } 102 | func (enc *HuffYuvImageEncoder) Encode(img image.Image) { 103 | if enc.input == nil || enc.closed { 104 | return 105 | } 106 | 107 | err := encodePPM(enc.input, img) 108 | if err != nil { 109 | logger.Error("error while encoding image:", err) 110 | } 111 | } 112 | 113 | func (enc *HuffYuvImageEncoder) Close() { 114 | enc.closed = true 115 | //enc.cmd.Process.Kill() 116 | } 117 | -------------------------------------------------------------------------------- /encoders/image-enc.go: -------------------------------------------------------------------------------- 1 | package encoders 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "image" 7 | "image/color" 8 | "io" 9 | "github.com/amitbet/vnc2video" 10 | ) 11 | 12 | func encodePPMGeneric(w io.Writer, img image.Image) error { 13 | maxvalue := 255 14 | size := img.Bounds() 15 | // write ppm header 16 | _, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | // write the bitmap 22 | colModel := color.RGBAModel 23 | row := make([]uint8, size.Dx()*3) 24 | for y := size.Min.Y; y < size.Max.Y; y++ { 25 | i := 0 26 | for x := size.Min.X; x < size.Max.X; x++ { 27 | color := colModel.Convert(img.At(x, y)).(color.RGBA) 28 | row[i] = color.R 29 | row[i+1] = color.G 30 | row[i+2] = color.B 31 | i += 3 32 | } 33 | if _, err := w.Write(row); err != nil { 34 | return err 35 | } 36 | } 37 | return nil 38 | } 39 | 40 | var convImage []uint8 41 | 42 | func encodePPMforRGBA(w io.Writer, img *image.RGBA) error { 43 | maxvalue := 255 44 | size := img.Bounds() 45 | // write ppm header 46 | _, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | if convImage == nil { 52 | convImage = make([]uint8, size.Dy()*size.Dx()*3) 53 | } 54 | 55 | rowCount := 0 56 | for i := 0; i < len(img.Pix); i++ { 57 | if (i % 4) != 3 { 58 | convImage[rowCount] = img.Pix[i] 59 | rowCount++ 60 | } 61 | } 62 | 63 | if _, err := w.Write(convImage); err != nil { 64 | return err 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func encodePPM(w io.Writer, img image.Image) error { 71 | if img == nil { 72 | return errors.New("nil image") 73 | } 74 | img1, isRGBImage := img.(*vnc2video.RGBImage) 75 | img2, isRGBA := img.(*image.RGBA) 76 | if isRGBImage { 77 | return encodePPMforRGBImage(w, img1) 78 | } else if isRGBA { 79 | return encodePPMforRGBA(w, img2) 80 | } 81 | return encodePPMGeneric(w, img) 82 | } 83 | 84 | func encodePPMforRGBImage(w io.Writer, img *vnc2video.RGBImage) error { 85 | maxvalue := 255 86 | size := img.Bounds() 87 | // write ppm header 88 | _, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | if _, err := w.Write(img.Pix); err != nil { 94 | return err 95 | } 96 | return nil 97 | } 98 | 99 | type ImageEncoder interface { 100 | Init(string) 101 | Run() 102 | Encode(image.Image) 103 | Close() 104 | } 105 | -------------------------------------------------------------------------------- /encoders/mjpeg-enc.go: -------------------------------------------------------------------------------- 1 | package encoders 2 | 3 | import ( 4 | "bytes" 5 | "image" 6 | "image/jpeg" 7 | "strings" 8 | "github.com/amitbet/vnc2video/logger" 9 | 10 | "github.com/icza/mjpeg" 11 | ) 12 | 13 | type MJPegImageEncoder struct { 14 | avWriter mjpeg.AviWriter 15 | Quality int 16 | Framerate int32 17 | closed bool 18 | } 19 | 20 | func (enc *MJPegImageEncoder) Init(videoFileName string) { 21 | fileExt := ".avi" 22 | if enc.Framerate == 0 { 23 | enc.Framerate = 12 24 | } 25 | if !strings.HasSuffix(videoFileName, fileExt) { 26 | videoFileName = videoFileName + fileExt 27 | } 28 | if enc.Framerate <= 0 { 29 | enc.Framerate = 5 30 | } 31 | avWriter, err := mjpeg.New(videoFileName, 1024, 768, enc.Framerate) 32 | if err != nil { 33 | logger.Error("Error during mjpeg init: ", err) 34 | } 35 | enc.avWriter = avWriter 36 | } 37 | func (enc *MJPegImageEncoder) Run(videoFileName string) { 38 | enc.Init(videoFileName) 39 | } 40 | 41 | func (enc *MJPegImageEncoder) Encode(img image.Image) { 42 | if enc.closed { 43 | return 44 | } 45 | 46 | buf := &bytes.Buffer{} 47 | jOpts := &jpeg.Options{Quality: enc.Quality} 48 | if enc.Quality <= 0 { 49 | jOpts = nil 50 | } 51 | err := jpeg.Encode(buf, img, jOpts) 52 | if err != nil { 53 | logger.Error("Error while creating jpeg: ", err) 54 | } 55 | 56 | //logger.Tracef("buff: %v\n", buf.Bytes()) 57 | 58 | err = enc.avWriter.AddFrame(buf.Bytes()) 59 | if err != nil { 60 | logger.Error("Error while adding frame to mjpeg: ", err) 61 | } 62 | } 63 | 64 | func (enc *MJPegImageEncoder) Close() { 65 | err := enc.avWriter.Close() 66 | 67 | enc.closed = true 68 | if err != nil { 69 | logger.Error("Error while closing mjpeg: ", err) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /encoders/qtrle-enc.go: -------------------------------------------------------------------------------- 1 | package encoders 2 | 3 | import ( 4 | "errors" 5 | "image" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "github.com/amitbet/vnc2video/logger" 11 | ) 12 | 13 | // QTRLEImageEncoder quick time rle is an efficient loseless codec, uses .mov extension 14 | type QTRLEImageEncoder struct { 15 | FFMpegBinPath string 16 | cmd *exec.Cmd 17 | input io.WriteCloser 18 | closed bool 19 | Framerate int 20 | } 21 | 22 | func (enc *QTRLEImageEncoder) Init(videoFileName string) { 23 | fileExt := ".mov" 24 | if enc.Framerate == 0 { 25 | enc.Framerate = 12 26 | } 27 | if !strings.HasSuffix(videoFileName, fileExt) { 28 | videoFileName = videoFileName + fileExt 29 | } 30 | //binary := "./ffmpeg" 31 | cmd := exec.Command(enc.FFMpegBinPath, 32 | "-f", "image2pipe", 33 | "-vcodec", "ppm", 34 | //"-r", strconv.Itoa(framerate), 35 | "-r", "12", 36 | 37 | //"-re", 38 | //"-i", "pipe:0", 39 | "-an", //no audio 40 | //"-vsync", "2", 41 | ///"-probesize", "10000000", 42 | "-y", 43 | 44 | "-i", "-", 45 | //"–s", "640×360", 46 | "-vcodec", "qtrle", //"libvpx",//"libvpx-vp9"//"libx264" 47 | //"-b:v", "0.33M", 48 | "-threads", "7", 49 | ///"-coder", "1", 50 | ///"-bf", "0", 51 | ///"-me_method", "hex", 52 | //"-speed", "0", 53 | //"-lossless", "1", //for vpx 54 | // "-an", "-f", "webm", 55 | "-preset", "veryfast", 56 | //"-tune", "animation", 57 | "-maxrate", "0.5M", 58 | "-bufsize", "50M", 59 | "-g", "250", 60 | 61 | //"-crf", "0", //for lossless encoding!!!! 62 | 63 | //"-rc_lookahead", "16", 64 | //"-profile", "0", 65 | "-crf", "34", 66 | //"-qmax", "51", 67 | //"-qmin", "7", 68 | //"-slices", "4", 69 | //"-vb", "2M", 70 | 71 | videoFileName, 72 | ) 73 | //cmd := exec.Command("/bin/echo") 74 | 75 | //io.Copy(cmd.Stdout, os.Stdout) 76 | cmd.Stdout = os.Stdout 77 | cmd.Stderr = os.Stderr 78 | 79 | encInput, err := cmd.StdinPipe() 80 | enc.input = encInput 81 | if err != nil { 82 | logger.Error("can't get ffmpeg input pipe") 83 | } 84 | enc.cmd = cmd 85 | } 86 | func (enc *QTRLEImageEncoder) Run(videoFileName string) error { 87 | if _, err := os.Stat(enc.FFMpegBinPath); os.IsNotExist(err) { 88 | if _, err := os.Stat(enc.FFMpegBinPath + ".exe"); os.IsNotExist(err) { 89 | logger.Error("encoder file doesn't exist in path:", enc.FFMpegBinPath) 90 | return errors.New("encoder file doesn't exist in path" + videoFileName) 91 | } else { 92 | enc.FFMpegBinPath = enc.FFMpegBinPath + ".exe" 93 | } 94 | } 95 | 96 | enc.Init(videoFileName) 97 | logger.Debugf("launching binary: %v", enc.cmd) 98 | err := enc.cmd.Run() 99 | if err != nil { 100 | logger.Errorf("error while launching ffmpeg: %v\n err: %v", enc.cmd.Args, err) 101 | return err 102 | } 103 | return nil 104 | } 105 | func (enc *QTRLEImageEncoder) Encode(img image.Image) { 106 | if enc.input == nil || enc.closed { 107 | return 108 | } 109 | 110 | err := encodePPM(enc.input, img) 111 | if err != nil { 112 | logger.Error("error while encoding image:", err) 113 | } 114 | } 115 | 116 | func (enc *QTRLEImageEncoder) Close() { 117 | enc.closed = true 118 | //enc.cmd.Process.Kill() 119 | } 120 | -------------------------------------------------------------------------------- /encoders/x264-enc.go: -------------------------------------------------------------------------------- 1 | package encoders 2 | 3 | import ( 4 | "errors" 5 | "image" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "strconv" 10 | "strings" 11 | "github.com/amitbet/vnc2video/logger" 12 | ) 13 | 14 | type X264ImageEncoder struct { 15 | FFMpegBinPath string 16 | cmd *exec.Cmd 17 | input io.WriteCloser 18 | closed bool 19 | Framerate int 20 | } 21 | 22 | func (enc *X264ImageEncoder) Init(videoFileName string) { 23 | fileExt := ".mp4" 24 | if enc.Framerate == 0 { 25 | enc.Framerate = 12 26 | } 27 | if !strings.HasSuffix(videoFileName, fileExt) { 28 | videoFileName = videoFileName + fileExt 29 | } 30 | //binary := "./ffmpeg" 31 | cmd := exec.Command(enc.FFMpegBinPath, 32 | "-f", "image2pipe", 33 | "-vcodec", "ppm", 34 | //"-r", strconv.Itoa(framerate), 35 | "-r", strconv.Itoa(enc.Framerate), 36 | 37 | //"-re", 38 | //"-i", "pipe:0", 39 | "-an", //no audio 40 | //"-vsync", "2", 41 | ///"-probesize", "10000000", 42 | "-y", 43 | 44 | "-i", "-", 45 | //"–s", "640×360", 46 | "-vcodec", "libx264", //"libvpx",//"libvpx-vp9"//"libx264" 47 | //"-b:v", "0.33M", 48 | "-threads", "8", 49 | ///"-coder", "1", 50 | ///"-bf", "0", 51 | ///"-me_method", "hex", 52 | //"-speed", "0", 53 | //"-lossless", "1", //for vpx 54 | // "-an", "-f", "webm", 55 | "-preset", "veryfast", 56 | //"-tune", "animation", 57 | // "-maxrate", "0.5M", 58 | // "-bufsize", "50M", 59 | "-g", "250", 60 | 61 | //"-crf", "0", //for lossless encoding!!!! 62 | 63 | //"-rc_lookahead", "16", 64 | //"-profile", "0", 65 | "-crf", "37", 66 | //"-qmax", "51", 67 | //"-qmin", "7", 68 | //"-slices", "4", 69 | //"-vb", "2M", 70 | 71 | videoFileName, 72 | ) 73 | //cmd := exec.Command("/bin/echo") 74 | 75 | //io.Copy(cmd.Stdout, os.Stdout) 76 | cmd.Stdout = os.Stdout 77 | cmd.Stderr = os.Stderr 78 | 79 | encInput, err := cmd.StdinPipe() 80 | enc.input = encInput 81 | if err != nil { 82 | logger.Error("can't get ffmpeg input pipe") 83 | } 84 | enc.cmd = cmd 85 | } 86 | func (enc *X264ImageEncoder) Run(videoFileName string) error { 87 | if _, err := os.Stat(enc.FFMpegBinPath); os.IsNotExist(err) { 88 | if _, err := os.Stat(enc.FFMpegBinPath + ".exe"); os.IsNotExist(err) { 89 | logger.Error("encoder file doesn't exist in path:", enc.FFMpegBinPath) 90 | return errors.New("encoder file doesn't exist in path" + videoFileName) 91 | } else { 92 | enc.FFMpegBinPath = enc.FFMpegBinPath + ".exe" 93 | } 94 | } 95 | 96 | enc.Init(videoFileName) 97 | logger.Debugf("launching binary: %v", enc.cmd) 98 | err := enc.cmd.Run() 99 | if err != nil { 100 | logger.Errorf("error while launching ffmpeg: %v\n err: %v", enc.cmd.Args, err) 101 | return err 102 | } 103 | return nil 104 | } 105 | func (enc *X264ImageEncoder) Encode(img image.Image) { 106 | if enc.input == nil || enc.closed { 107 | return 108 | } 109 | 110 | err := encodePPM(enc.input, img) 111 | if err != nil { 112 | logger.Error("error while encoding image:", err) 113 | } 114 | } 115 | 116 | func (enc *X264ImageEncoder) Close() { 117 | enc.closed = true 118 | //enc.cmd.Process.Kill() 119 | } 120 | -------------------------------------------------------------------------------- /encoding.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bytes" 5 | "image" 6 | "image/draw" 7 | "sync" 8 | ) 9 | 10 | // EncodingType represents a known VNC encoding type. 11 | type EncodingType int32 12 | 13 | //go:generate stringer -type=EncodingType 14 | 15 | const ( 16 | // EncRaw raw encoding 17 | EncRaw EncodingType = 0 18 | // EncCopyRect copyrect encoding 19 | EncCopyRect EncodingType = 1 20 | 21 | EncRRE EncodingType = 2 22 | EncCoRRE EncodingType = 4 23 | EncHextile EncodingType = 5 24 | EncZlib EncodingType = 6 25 | EncTight EncodingType = 7 26 | EncZlibHex EncodingType = 8 27 | EncUltra1 EncodingType = 9 28 | EncUltra2 EncodingType = 10 29 | EncJPEG EncodingType = 21 30 | EncJRLE EncodingType = 22 31 | EncTRLE EncodingType = 15 32 | EncZRLE EncodingType = 16 33 | EncAtenAST2100 EncodingType = 0x57 34 | EncAtenASTJPEG EncodingType = 0x58 35 | EncAtenHermon EncodingType = 0x59 36 | EncAtenYarkon EncodingType = 0x60 37 | EncAtenPilot3 EncodingType = 0x61 38 | EncJPEGQualityLevelPseudo10 EncodingType = -23 39 | EncJPEGQualityLevelPseudo9 EncodingType = -24 40 | EncJPEGQualityLevelPseudo8 EncodingType = -25 41 | EncJPEGQualityLevelPseudo7 EncodingType = -26 42 | EncJPEGQualityLevelPseudo6 EncodingType = -27 43 | EncJPEGQualityLevelPseudo5 EncodingType = -28 44 | EncJPEGQualityLevelPseudo4 EncodingType = -29 45 | EncJPEGQualityLevelPseudo3 EncodingType = -30 46 | EncJPEGQualityLevelPseudo2 EncodingType = -31 47 | EncJPEGQualityLevelPseudo1 EncodingType = -32 48 | EncPointerPosPseudo EncodingType = -232 49 | EncCursorPseudo EncodingType = -239 50 | EncXCursorPseudo EncodingType = -240 51 | EncDesktopSizePseudo EncodingType = -223 52 | EncLastRectPseudo EncodingType = -224 53 | EncCompressionLevel10 EncodingType = -247 54 | EncCompressionLevel9 EncodingType = -248 55 | EncCompressionLevel8 EncodingType = -249 56 | EncCompressionLevel7 EncodingType = -250 57 | EncCompressionLevel6 EncodingType = -251 58 | EncCompressionLevel5 EncodingType = -252 59 | EncCompressionLevel4 EncodingType = -253 60 | EncCompressionLevel3 EncodingType = -254 61 | EncCompressionLevel2 EncodingType = -255 62 | EncCompressionLevel1 EncodingType = -256 63 | EncQEMUPointerMotionChangePseudo EncodingType = -257 64 | EncQEMUExtendedKeyEventPseudo EncodingType = -258 65 | EncTightPng EncodingType = -260 66 | EncDesktopNamePseudo EncodingType = -307 67 | EncExtendedDesktopSizePseudo EncodingType = -308 68 | EncXvpPseudo EncodingType = -309 69 | EncClientRedirect EncodingType = -311 70 | EncFencePseudo EncodingType = -312 71 | EncContinuousUpdatesPseudo EncodingType = -313 72 | EncExtendedClipboardPseudo EncodingType = -1063131698 //C0A1E5CE 73 | ) 74 | 75 | var bPool = sync.Pool{ 76 | New: func() interface{} { 77 | // The Pool's New function should generally only return pointer 78 | // types, since a pointer can be put into the return interface 79 | // value without an allocation: 80 | return new(bytes.Buffer) 81 | }, 82 | } 83 | 84 | type Renderer interface { 85 | SetTargetImage(draw.Image) 86 | } 87 | 88 | // Encoding represents interface for vnc encoding 89 | type Encoding interface { 90 | Type() EncodingType 91 | Read(Conn, *Rectangle) error 92 | Write(Conn, *Rectangle) error 93 | Supported(Conn) bool 94 | Reset() error 95 | } 96 | 97 | func setBit(n uint8, pos uint8) uint8 { 98 | n |= (1 << pos) 99 | return n 100 | } 101 | 102 | func clrBit(n uint8, pos uint8) uint8 { 103 | n = n &^ (1 << pos) 104 | return n 105 | } 106 | 107 | func hasBit(n uint8, pos uint8) bool { 108 | v := n & (1 << pos) 109 | return (v > 0) 110 | } 111 | 112 | func getBit(n uint8, pos uint8) uint8 { 113 | n = n & (1 << pos) 114 | return n 115 | } 116 | 117 | func newRGBAImage(rgba []byte, rect *Rectangle) image.Image { 118 | img := &image.RGBA{Stride: 4 * int(rect.Width)} 119 | img.Pix = rgba 120 | img.Rect.Max.X = int(rect.Width) 121 | img.Rect.Max.Y = int(rect.Height) 122 | return img 123 | } 124 | -------------------------------------------------------------------------------- /encoding_atenhermon.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | ) 7 | 8 | const ( 9 | EncAtenHermonSubrect EncodingType = 0 10 | EncAtenHermonRaw EncodingType = 1 11 | ) 12 | 13 | type AtenHermon struct { 14 | _ [4]byte 15 | AtenLength uint32 16 | AtenType uint8 17 | _ [1]byte 18 | AtenSubrects uint32 19 | AtenRawLength uint32 20 | Encodings []Encoding 21 | } 22 | 23 | type AtenHermonSubrect struct { 24 | A uint16 25 | B uint16 26 | Y uint8 27 | X uint8 28 | Data []byte 29 | } 30 | 31 | func (*AtenHermon) Supported(Conn) bool { 32 | return false 33 | } 34 | 35 | func (*AtenHermon) Type() EncodingType { return EncAtenHermon } 36 | func (*AtenHermon) Reset() error { 37 | return nil 38 | } 39 | 40 | func (enc *AtenHermon) Read(c Conn, rect *Rectangle) error { 41 | var pad4 [4]byte 42 | 43 | if err := binary.Read(c, binary.BigEndian, &pad4); err != nil { 44 | return err 45 | } 46 | 47 | var aten_length uint32 48 | if err := binary.Read(c, binary.BigEndian, &aten_length); err != nil { 49 | return err 50 | } 51 | enc.AtenLength = aten_length 52 | 53 | if rect.Width == 64896 && rect.Height == 65056 { 54 | if aten_length != 10 && aten_length != 0 { 55 | return fmt.Errorf("screen is off and length is invalid") 56 | } 57 | aten_length = 0 58 | } 59 | 60 | if c.Width() != rect.Width && c.Height() != rect.Height { 61 | c.SetWidth(rect.Width) 62 | c.SetHeight(rect.Height) 63 | } 64 | 65 | var aten_type uint8 66 | if err := binary.Read(c, binary.BigEndian, &aten_type); err != nil { 67 | return err 68 | } 69 | enc.AtenType = aten_type 70 | 71 | var pad1 [1]byte 72 | if err := binary.Read(c, binary.BigEndian, &pad1); err != nil { 73 | return err 74 | } 75 | 76 | var subrects uint32 77 | if err := binary.Read(c, binary.BigEndian, &subrects); err != nil { 78 | return err 79 | } 80 | enc.AtenSubrects = subrects 81 | 82 | var raw_length uint32 83 | if err := binary.Read(c, binary.BigEndian, &raw_length); err != nil { 84 | return err 85 | } 86 | enc.AtenRawLength = raw_length 87 | 88 | if aten_length != raw_length { 89 | return fmt.Errorf("aten_length != raw_length, %d != %d", aten_length, raw_length) 90 | } 91 | 92 | aten_length -= 10 // skip 93 | 94 | for aten_length > 0 { 95 | switch EncodingType(aten_type) { 96 | case EncAtenHermonSubrect: 97 | encSR := &AtenHermonSubrect{} 98 | if err := encSR.Read(c, rect); err != nil { 99 | return err 100 | } 101 | enc.Encodings = append(enc.Encodings, encSR) 102 | aten_length -= 6 + (16 * 16 * uint32(c.PixelFormat().BPP/8)) 103 | case EncAtenHermonRaw: 104 | encRaw := &RawEncoding{} 105 | if err := encRaw.Read(c, rect); err != nil { 106 | return err 107 | } 108 | enc.Encodings = append(enc.Encodings, encRaw) 109 | aten_length -= uint32(rect.Area()) * uint32(c.PixelFormat().BPP/8) 110 | default: 111 | return fmt.Errorf("unknown aten hermon type %d", aten_type) 112 | 113 | } 114 | } 115 | 116 | if aten_length < 0 { 117 | return fmt.Errorf("aten_len dropped below zero") 118 | } 119 | return nil 120 | } 121 | 122 | func (enc *AtenHermon) Write(c Conn, rect *Rectangle) error { 123 | if !enc.Supported(c) { 124 | for _, ew := range enc.Encodings { 125 | if err := ew.Write(c, rect); err != nil { 126 | return err 127 | } 128 | } 129 | return nil 130 | } 131 | var pad4 [4]byte 132 | 133 | if err := binary.Write(c, binary.BigEndian, pad4); err != nil { 134 | return err 135 | } 136 | 137 | if err := binary.Write(c, binary.BigEndian, enc.AtenLength); err != nil { 138 | return err 139 | } 140 | 141 | if err := binary.Write(c, binary.BigEndian, enc.AtenType); err != nil { 142 | return err 143 | } 144 | 145 | var pad1 [1]byte 146 | if err := binary.Write(c, binary.BigEndian, pad1); err != nil { 147 | return err 148 | } 149 | 150 | if err := binary.Write(c, binary.BigEndian, enc.AtenSubrects); err != nil { 151 | return err 152 | } 153 | 154 | if err := binary.Write(c, binary.BigEndian, enc.AtenRawLength); err != nil { 155 | return err 156 | } 157 | 158 | for _, ew := range enc.Encodings { 159 | if err := ew.Write(c, rect); err != nil { 160 | return err 161 | } 162 | } 163 | return nil 164 | } 165 | 166 | func (*AtenHermonSubrect) Supported(Conn) bool { 167 | return false 168 | } 169 | 170 | func (enc *AtenHermonSubrect) Type() EncodingType { 171 | return EncAtenHermonSubrect 172 | } 173 | func (*AtenHermonSubrect) Reset() error { 174 | return nil 175 | } 176 | func (enc *AtenHermonSubrect) Read(c Conn, rect *Rectangle) error { 177 | if err := binary.Read(c, binary.BigEndian, &enc.A); err != nil { 178 | return err 179 | } 180 | if err := binary.Read(c, binary.BigEndian, &enc.B); err != nil { 181 | return err 182 | } 183 | if err := binary.Read(c, binary.BigEndian, &enc.Y); err != nil { 184 | return err 185 | } 186 | if err := binary.Read(c, binary.BigEndian, &enc.X); err != nil { 187 | return err 188 | } 189 | enc.Data = make([]byte, 16*16*uint32(c.PixelFormat().BPP/8)) 190 | if err := binary.Read(c, binary.BigEndian, &enc.Data); err != nil { 191 | return err 192 | } 193 | return nil 194 | } 195 | 196 | func (enc *AtenHermonSubrect) Write(c Conn, rect *Rectangle) error { 197 | if !enc.Supported(c) { 198 | return nil 199 | } 200 | if err := binary.Write(c, binary.BigEndian, enc.A); err != nil { 201 | return err 202 | } 203 | if err := binary.Write(c, binary.BigEndian, enc.B); err != nil { 204 | return err 205 | } 206 | if err := binary.Write(c, binary.BigEndian, enc.Y); err != nil { 207 | return err 208 | } 209 | if err := binary.Write(c, binary.BigEndian, enc.X); err != nil { 210 | return err 211 | } 212 | if err := binary.Write(c, binary.BigEndian, enc.Data); err != nil { 213 | return err 214 | } 215 | return nil 216 | } 217 | -------------------------------------------------------------------------------- /encoding_copyrect.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "image" 6 | "image/draw" 7 | "github.com/amitbet/vnc2video/logger" 8 | ) 9 | 10 | type CopyRectEncoding struct { 11 | SX, SY uint16 12 | Image draw.Image 13 | } 14 | 15 | func (*CopyRectEncoding) Supported(Conn) bool { 16 | return true 17 | } 18 | func (*CopyRectEncoding) Reset() error { 19 | return nil 20 | } 21 | func (*CopyRectEncoding) Type() EncodingType { return EncCopyRect } 22 | 23 | func (enc *CopyRectEncoding) SetTargetImage(img draw.Image) { 24 | //logger.Tracef("!!!!!!!!!!!!!setting image: %v", img.Bounds()) 25 | enc.Image = img 26 | } 27 | 28 | func (enc *CopyRectEncoding) Read(c Conn, rect *Rectangle) error { 29 | logger.Tracef("Reading: CopyRect %v", rect) 30 | if err := binary.Read(c, binary.BigEndian, &enc.SX); err != nil { 31 | return err 32 | } 33 | if err := binary.Read(c, binary.BigEndian, &enc.SY); err != nil { 34 | return err 35 | } 36 | cpyIm := image.NewRGBA(image.Rectangle{Min: image.Point{0, 0}, Max: image.Point{int(rect.Width), int(rect.Height)}}) 37 | for x := 0; x < int(rect.Width); x++ { 38 | for y := 0; y < int(rect.Height); y++ { 39 | col := enc.Image.At(x+int(enc.SX), y+int(enc.SY)) 40 | cpyIm.Set(x, y, col) 41 | } 42 | } 43 | 44 | for x := 0; x < int(rect.Width); x++ { 45 | for y := 0; y < int(rect.Height); y++ { 46 | col := cpyIm.At(x, y) 47 | enc.Image.Set(int(rect.X)+x, int(rect.Y)+y, col) 48 | } 49 | } 50 | 51 | return nil 52 | } 53 | 54 | func (enc *CopyRectEncoding) Write(c Conn, rect *Rectangle) error { 55 | if err := binary.Write(c, binary.BigEndian, enc.SX); err != nil { 56 | return err 57 | } 58 | if err := binary.Write(c, binary.BigEndian, enc.SY); err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /encoding_corre.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | type CoRREEncoding struct { 9 | numSubRects uint32 10 | backgroundColor []byte 11 | subRectData []byte 12 | } 13 | 14 | func (z *CoRREEncoding) Type() int32 { 15 | return 4 16 | } 17 | 18 | func (z *CoRREEncoding) WriteTo(w io.Writer) (n int, err error) { 19 | binary.Write(w, binary.BigEndian, z.numSubRects) 20 | if err != nil { 21 | return 0, err 22 | } 23 | 24 | w.Write(z.backgroundColor) 25 | if err != nil { 26 | return 0, err 27 | } 28 | 29 | w.Write(z.subRectData) 30 | 31 | if err != nil { 32 | return 0, err 33 | } 34 | b := len(z.backgroundColor) + len(z.subRectData) + 4 35 | return b, nil 36 | } 37 | func (z *CoRREEncoding) Read(r Conn, rect *Rectangle) error { 38 | //func (z *CoRREEncoding) Read(pixelFmt *PixelFormat, rect *Rectangle, r io.Reader) (Encoding, error) { 39 | bytesPerPixel := int(r.PixelFormat().BPP / 8) 40 | var numOfSubrectangles uint32 41 | if err := binary.Read(r, binary.BigEndian, &numOfSubrectangles); err != nil { 42 | return err 43 | } 44 | 45 | z.numSubRects = numOfSubrectangles 46 | var err error 47 | //read whole-rect background color 48 | z.backgroundColor, err = ReadBytes(bytesPerPixel, r) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | //read all individual rects (color=BPP + x=16b + y=16b + w=16b + h=16b) 54 | z.subRectData, err = ReadBytes(int(numOfSubrectangles)*(bytesPerPixel+4), r) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /encoding_cursor.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "image" 6 | "image/color" 7 | "image/draw" 8 | "github.com/amitbet/vnc2video/logger" 9 | ) 10 | 11 | type CursorPseudoEncoding struct { 12 | Colors []Color 13 | BitMask []byte 14 | Image draw.Image 15 | } 16 | 17 | func (*CursorPseudoEncoding) Supported(Conn) bool { 18 | return true 19 | } 20 | 21 | func (enc *CursorPseudoEncoding) SetTargetImage(img draw.Image) { 22 | enc.Image = img 23 | } 24 | 25 | func (enc *CursorPseudoEncoding) Reset() error { 26 | return nil 27 | } 28 | 29 | func (*CursorPseudoEncoding) Type() EncodingType { return EncCursorPseudo } 30 | 31 | func (enc *CursorPseudoEncoding) Read(c Conn, rect *Rectangle) error { 32 | logger.Tracef("CursorPseudoEncoding.Read: got rect: %v", rect) 33 | //rgba := make([]byte, int(rect.Height)*int(rect.Width)*int(c.PixelFormat().BPP/8)) 34 | numColors := int(rect.Height) * int(rect.Width) 35 | colors := make([]color.Color, numColors) 36 | var err error 37 | pf := c.PixelFormat() 38 | for i := 0; i < numColors; i++ { 39 | colors[i], err = ReadColor(c, &pf) 40 | if err != nil { 41 | return err 42 | } 43 | } 44 | // if err := binary.Read(c, binary.BigEndian, &rgba); err != nil { 45 | // return err 46 | // } 47 | 48 | bitmask := make([]byte, int((rect.Width+7)/8*rect.Height)) 49 | if err := binary.Read(c, binary.BigEndian, &bitmask); err != nil { 50 | return err 51 | } 52 | scanLine := (rect.Width + 7) / 8 53 | canvas := enc.Image.(*VncCanvas) 54 | //canvas.Cursor = 55 | cursorImg := image.NewRGBA(MakeRect(0, 0, int(rect.Width), int(rect.Height))) 56 | //cursorMask := image.NewRGBA(cursorImg.Bounds()) 57 | cursorMask := [][]bool{} 58 | for i := 0; i < int(rect.Width); i++ { 59 | cursorMask = append(cursorMask, make([]bool, rect.Height)) 60 | } 61 | 62 | //int[] cursorPixels = new int[rect.width * rect.height]; 63 | for y := 0; y < int(rect.Height); y++ { 64 | for x := 0; x < int(rect.Width); x++ { 65 | offset := y*int(rect.Width) + x 66 | if bitmask[y*int(scanLine)+x/8]&(1< 0 { 67 | cursorImg.Set(x, y, colors[offset]) 68 | //cursorMask.Set(x, y, color.RGBA{1, 1, 1, 1}) 69 | cursorMask[x][y] = true 70 | //logger.Tracef("CursorPseudoEncoding.Read: setting pixel: (%d,%d) %v", x+int(rect.X), y+int(rect.Y), colors[offset]) 71 | } 72 | } 73 | } 74 | canvas.CursorOffset = &image.Point{int(rect.X), int(rect.Y)} 75 | canvas.Cursor = cursorImg 76 | canvas.CursorBackup = image.NewRGBA(cursorImg.Bounds()) 77 | canvas.CursorMask = cursorMask 78 | /* 79 | rectStride := 4 * rect.Width 80 | for i := uint16(0); i < rect.Height; i++ { 81 | for j := uint16(0); j < rect.Width; j += 8 { 82 | for idx, k := j/8, 7; k >= 0; k-- { 83 | if (bitmask[idx] & (1 << uint(k))) == 0 { 84 | pIdx := j*4 + i*rectStride 85 | rgba[pIdx] = 0 86 | rgba[pIdx+1] = 0 87 | rgba[pIdx+2] = 0 88 | rgba[pIdx+3] = 0 89 | } 90 | } 91 | } 92 | } 93 | */ 94 | /* 95 | int bytesPerPixel = renderer.getBytesPerPixel(); 96 | int length = rect.width * rect.height * bytesPerPixel; 97 | if (0 == length) 98 | return; 99 | byte[] buffer = ByteBuffer.getInstance().getBuffer(length); 100 | transport.readBytes(buffer, 0, length); 101 | 102 | StringBuilder sb = new StringBuilder(" "); 103 | for (int i=0; i> 4 & 0x0f 144 | subtileY := dimensions & 0x0f 145 | dimensions, err = ReadUint8(r) // bits 7-4 for w, bits 3-0 for h 146 | if err != nil { 147 | logger.Error("HextileEncoding.Read: problem reading 2nd dimensions from connection: ", err) 148 | return err 149 | } 150 | subtileWidth := 1 + (dimensions >> 4 & 0x0f) 151 | subtileHeight := 1 + (dimensions & 0x0f) 152 | subrectBounds := image.Rectangle{Min: image.Point{int(tx) + int(subtileX), int(ty) + int(subtileY)}, Max: image.Point{int(tx) + int(subtileX) + int(subtileWidth), int(ty) + int(subtileY) + int(subtileHeight)}} 153 | FillRect(z.Image, &subrectBounds, color) 154 | //logger.Tracef("%v", subrectBounds) 155 | } 156 | } 157 | } 158 | 159 | return nil 160 | } 161 | -------------------------------------------------------------------------------- /encoding_pointer_pos.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | "github.com/amitbet/vnc2video/logger" 7 | ) 8 | 9 | type CursorPosPseudoEncoding struct { 10 | prevPosBackup draw.Image 11 | prevPositionRect image.Rectangle 12 | cursorImage draw.Image 13 | Image draw.Image 14 | } 15 | 16 | func (*CursorPosPseudoEncoding) Supported(Conn) bool { 17 | return true 18 | } 19 | 20 | func (enc *CursorPosPseudoEncoding) SetTargetImage(img draw.Image) { 21 | enc.Image = img 22 | } 23 | 24 | func (enc *CursorPosPseudoEncoding) Reset() error { 25 | return nil 26 | } 27 | 28 | func (*CursorPosPseudoEncoding) Type() EncodingType { return EncPointerPosPseudo } 29 | 30 | func (enc *CursorPosPseudoEncoding) Read(c Conn, rect *Rectangle) error { 31 | logger.Tracef("CursorPosPseudoEncoding: got cursot pos update: %v", rect) 32 | canvas := enc.Image.(*VncCanvas) 33 | canvas.CursorLocation = &image.Point{X: int(rect.X), Y: int(rect.Y)} 34 | return nil 35 | } 36 | 37 | func (enc *CursorPosPseudoEncoding) Write(c Conn, rect *Rectangle) error { 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /encoding_raw.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "image/draw" 5 | ) 6 | 7 | type RawEncoding struct { 8 | Image draw.Image 9 | //Colors []Color 10 | } 11 | 12 | func (*RawEncoding) Supported(Conn) bool { 13 | return true 14 | } 15 | 16 | func (*RawEncoding) Reset() error { 17 | return nil 18 | } 19 | 20 | func (enc *RawEncoding) Write(c Conn, rect *Rectangle) error { 21 | var err error 22 | 23 | return err 24 | } 25 | func (enc *RawEncoding) SetTargetImage(img draw.Image) { 26 | enc.Image = img 27 | } 28 | 29 | // Read implements the Encoding interface. 30 | func (enc *RawEncoding) Read(c Conn, rect *Rectangle) error { 31 | pf := c.PixelFormat() 32 | 33 | DecodeRaw(c, &pf, rect, enc.Image) 34 | 35 | return nil 36 | } 37 | 38 | func (*RawEncoding) Type() EncodingType { return EncRaw } 39 | -------------------------------------------------------------------------------- /encoding_rre.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "image/draw" 6 | "io" 7 | //"image/draw" 8 | ) 9 | 10 | type RREEncoding struct { 11 | //Colors []Color 12 | numSubRects uint32 13 | backgroundColor []byte 14 | subRectData []byte 15 | Image draw.Image 16 | } 17 | 18 | func (*RREEncoding) Supported(Conn) bool { 19 | return true 20 | } 21 | 22 | func (enc *RREEncoding) SetTargetImage(img draw.Image) { 23 | enc.Image = img 24 | } 25 | 26 | func (enc *RREEncoding) Reset() error { 27 | return nil 28 | } 29 | 30 | func (*RREEncoding) Type() EncodingType { return EncRRE } 31 | 32 | func (enc *RREEncoding) Write(c Conn, rect *Rectangle) error { 33 | 34 | return nil 35 | } 36 | 37 | func (z *RREEncoding) WriteTo(w io.Writer) (n int, err error) { 38 | binary.Write(w, binary.BigEndian, z.numSubRects) 39 | if err != nil { 40 | return 0, err 41 | } 42 | 43 | w.Write(z.backgroundColor) 44 | if err != nil { 45 | return 0, err 46 | } 47 | 48 | w.Write(z.subRectData) 49 | 50 | if err != nil { 51 | return 0, err 52 | } 53 | b := len(z.backgroundColor) + len(z.subRectData) + 4 54 | return b, nil 55 | } 56 | 57 | func (enc *RREEncoding) Read(r Conn, rect *Rectangle) error { 58 | //func (z *RREEncoding) Read(pixelFmt *PixelFormat, rect *Rectangle, r io.Reader) (Encoding, error) { 59 | pf := r.PixelFormat() 60 | //bytesPerPixel := int(pf.BPP / 8) 61 | 62 | var numOfSubrectangles uint32 63 | if err := binary.Read(r, binary.BigEndian, &numOfSubrectangles); err != nil { 64 | return err 65 | } 66 | 67 | var err error 68 | enc.numSubRects = numOfSubrectangles 69 | 70 | //read whole-rect background color 71 | bgColor, err := ReadColor(r, &pf) 72 | if err != nil { 73 | return err 74 | } 75 | imgRect := MakeRectFromVncRect(rect) 76 | FillRect(enc.Image, &imgRect, bgColor) 77 | 78 | //read all individual rects (color=bytesPerPixel + x=16b + y=16b + w=16b + h=16b) 79 | 80 | for i := 0; i < int(numOfSubrectangles); i++ { 81 | color, err := ReadColor(r, &pf) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | x, err := ReadUint16(r) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | y, err := ReadUint16(r) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | width, err := ReadUint16(r) 97 | if err != nil { 98 | return err 99 | } 100 | height, err := ReadUint16(r) 101 | if err != nil { 102 | return err 103 | } 104 | subRect := MakeRect(int(rect.X+x), int(rect.Y+y), int(width), int(height)) 105 | FillRect(enc.Image, &subRect, color) 106 | } 107 | 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /encoding_tightpng.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "image" 8 | "image/color" 9 | "image/draw" 10 | "image/png" 11 | "io" 12 | "github.com/amitbet/vnc2video/logger" 13 | ) 14 | 15 | func (*TightPngEncoding) Supported(Conn) bool { 16 | return true 17 | } 18 | func (*TightPngEncoding) Reset() error { 19 | return nil 20 | } 21 | 22 | func (enc *TightPngEncoding) Write(c Conn, rect *Rectangle) error { 23 | if err := writeTightCC(c, enc.TightCC); err != nil { 24 | return err 25 | } 26 | cmp := enc.TightCC.Compression 27 | switch cmp { 28 | case TightCompressionPNG: 29 | buf := bPool.Get().(*bytes.Buffer) 30 | buf.Reset() 31 | defer bPool.Put(buf) 32 | pngEnc := &png.Encoder{CompressionLevel: png.BestSpeed} 33 | //pngEnc := &png.Encoder{CompressionLevel: png.NoCompression} 34 | if err := pngEnc.Encode(buf, enc.Image); err != nil { 35 | return err 36 | } 37 | if err := writeTightLength(c, buf.Len()); err != nil { 38 | return err 39 | } 40 | 41 | if _, err := buf.WriteTo(c); err != nil { 42 | return err 43 | } 44 | case TightCompressionFill: 45 | var tpx TightPixel 46 | r, g, b, _ := enc.Image.At(0, 0).RGBA() 47 | tpx.R = uint8(r) 48 | tpx.G = uint8(g) 49 | tpx.B = uint8(b) 50 | if err := binary.Write(c, binary.BigEndian, tpx); err != nil { 51 | return err 52 | } 53 | default: 54 | return fmt.Errorf("unknown tight compression %d", cmp) 55 | } 56 | return nil 57 | } 58 | 59 | type TightPngEncoding struct { 60 | TightCC *TightCC 61 | Image draw.Image 62 | } 63 | 64 | func (*TightPngEncoding) Type() EncodingType { return EncTightPng } 65 | 66 | func (enc *TightPngEncoding) Read(c Conn, rect *Rectangle) error { 67 | tcc, err := readTightCC(c) 68 | logger.Trace("starting to read a tight rect: %v", rect) 69 | if err != nil { 70 | return err 71 | } 72 | enc.TightCC = tcc 73 | cmp := enc.TightCC.Compression 74 | switch cmp { 75 | case TightCompressionPNG: 76 | l, err := readTightLength(c) 77 | if err != nil { 78 | return err 79 | } 80 | img, err := png.Decode(io.LimitReader(c, int64(l))) 81 | if err != nil { 82 | return err 83 | } 84 | //draw.Draw(enc.Image, enc.Image.Bounds(), img, image.Point{X: int(rect.X), Y: int(rect.Y)}, draw.Src) 85 | DrawImage(enc.Image, img, image.Point{X: int(rect.X), Y: int(rect.Y)}) 86 | case TightCompressionFill: 87 | var tpx TightPixel 88 | if err := binary.Read(c, binary.BigEndian, &tpx); err != nil { 89 | return err 90 | } 91 | //enc.Image = image.NewRGBA(image.Rect(0, 0, 1, 1)) 92 | col := color.RGBA{R: tpx.R, G: tpx.G, B: tpx.B, A: 1} 93 | myRect := MakeRectFromVncRect(rect) 94 | FillRect(enc.Image, &myRect, col) 95 | //enc.Image.(draw.Image).Set(0, 0, color.RGBA{R: tpx.R, G: tpx.G, B: tpx.B, A: 1}) 96 | default: 97 | return fmt.Errorf("unknown compression %d", cmp) 98 | } 99 | return nil 100 | } 101 | 102 | func (enc *TightPngEncoding) SetTargetImage(img draw.Image) { 103 | enc.Image = img 104 | } 105 | -------------------------------------------------------------------------------- /encoding_util.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "image" 8 | "image/color" 9 | "image/draw" 10 | "io" 11 | ) 12 | 13 | const ( 14 | BlockWidth = 16 15 | BlockHeight = 16 16 | ) 17 | 18 | type VncCanvas struct { 19 | draw.Image 20 | //DisplayBuff draw.Image 21 | //WriteBuff draw.Image 22 | imageBuffs [2]draw.Image 23 | Cursor draw.Image 24 | CursorMask [][]bool 25 | CursorBackup draw.Image 26 | CursorOffset *image.Point 27 | CursorLocation *image.Point 28 | DrawCursor bool 29 | Changed map[string]bool 30 | } 31 | 32 | func NewVncCanvas(width, height int) *VncCanvas { 33 | //dispImg := NewRGBImage(image.Rect(0, 0, width, height)) 34 | writeImg := NewRGBImage(image.Rect(0, 0, width, height)) 35 | canvas := VncCanvas{ 36 | Image: writeImg, 37 | //DisplayBuff: dispImg, 38 | //WriteBuff: writeImg, 39 | } 40 | return &canvas 41 | } 42 | 43 | func (c *VncCanvas) SetChanged(rect *Rectangle) { 44 | if c.Changed == nil { 45 | c.Changed = make(map[string]bool) 46 | } 47 | for x := int(rect.X) / BlockWidth; x*BlockWidth < int(rect.X+rect.Width); x++ { 48 | for y := int(rect.Y) / BlockHeight; y*BlockHeight < int(rect.Y+rect.Height); y++ { 49 | key := fmt.Sprintf("%d,%d", x, y) 50 | //fmt.Println("setting block: ", key) 51 | c.Changed[key] = true 52 | } 53 | } 54 | } 55 | 56 | func (c *VncCanvas) Reset(rect *Rectangle) { 57 | c.Changed = nil 58 | } 59 | 60 | func (c *VncCanvas) RemoveCursor() image.Image { 61 | if c.Cursor == nil || c.CursorLocation == nil { 62 | return c.Image 63 | } 64 | if !c.DrawCursor { 65 | return c.Image 66 | } 67 | rect := c.Cursor.Bounds() 68 | loc := c.CursorLocation 69 | img := c.Image 70 | for y := rect.Min.Y; y < int(rect.Max.Y); y++ { 71 | for x := rect.Min.X; x < int(rect.Max.X); x++ { 72 | // offset := y*int(rect.Width) + x 73 | // if bitmask[y*int(scanLine)+x/8]&(1< 0 { 74 | col := c.CursorBackup.At(x, y) 75 | //mask := c.CursorMask.At(x, y).(color.RGBA) 76 | mask := c.CursorMask[x][y] 77 | //logger.Info("Drawing Cursor: ", x, y, col, mask) 78 | if mask { 79 | //logger.Info("Drawing Cursor for real: ", x, y, col) 80 | img.Set(x+loc.X-c.CursorOffset.X, y+loc.Y-c.CursorOffset.Y, col) 81 | } 82 | // //logger.Tracef("CursorPseudoEncoding.Read: setting pixel: (%d,%d) %v", x+int(rect.X), y+int(rect.Y), colors[offset]) 83 | // } 84 | } 85 | } 86 | return img 87 | } 88 | 89 | // func (c *VncCanvas) SwapBuffers() { 90 | // swapSpace := c.DisplayBuff 91 | // c.DisplayBuff = c.WriteBuff 92 | // c.WriteBuff = swapSpace 93 | // c.Image = c.WriteBuff 94 | // } 95 | 96 | func (c *VncCanvas) PaintCursor() image.Image { 97 | if c.Cursor == nil || c.CursorLocation == nil { 98 | return c.Image 99 | } 100 | if !c.DrawCursor { 101 | return c.Image 102 | } 103 | rect := c.Cursor.Bounds() 104 | if c.CursorBackup == nil { 105 | c.CursorBackup = image.NewRGBA(c.Cursor.Bounds()) 106 | } 107 | 108 | loc := c.CursorLocation 109 | img := c.Image 110 | for y := rect.Min.Y; y < int(rect.Max.Y); y++ { 111 | for x := rect.Min.X; x < int(rect.Max.X); x++ { 112 | // offset := y*int(rect.Width) + x 113 | // if bitmask[y*int(scanLine)+x/8]&(1< 0 { 114 | col := c.Cursor.At(x, y) 115 | //mask := c.CursorMask.At(x, y).(RGBColor) 116 | mask := c.CursorMask[x][y] 117 | backup := c.Image.At(x+loc.X-c.CursorOffset.X, y+loc.Y-c.CursorOffset.Y) 118 | //c.CursorBackup.Set(x, y, backup) 119 | //backup the previous data at this point 120 | 121 | //logger.Info("Drawing Cursor: ", x, y, col, mask) 122 | if mask { 123 | 124 | c.CursorBackup.Set(x, y, backup) 125 | //logger.Info("Drawing Cursor for real: ", x, y, col) 126 | img.Set(x+loc.X-c.CursorOffset.X, y+loc.Y-c.CursorOffset.Y, col) 127 | } 128 | // //logger.Tracef("CursorPseudoEncoding.Read: setting pixel: (%d,%d) %v", x+int(rect.X), y+int(rect.Y), colors[offset]) 129 | // } 130 | } 131 | } 132 | return img 133 | } 134 | 135 | func Min(a, b int) int { 136 | if a < b { 137 | return a 138 | } 139 | return b 140 | } 141 | 142 | func DrawImage(target draw.Image, imageToApply image.Image, pos image.Point) { 143 | rect := imageToApply.Bounds() 144 | for x := rect.Min.X; x < rect.Max.X; x++ { 145 | for y := rect.Min.Y; y < rect.Max.Y; y++ { 146 | target.Set(x+pos.X, y+pos.Y, imageToApply.At(x, y)) 147 | } 148 | } 149 | } 150 | 151 | func FillRect(img draw.Image, rect *image.Rectangle, c color.Color) { 152 | for x := rect.Min.X; x < rect.Max.X; x++ { 153 | for y := rect.Min.Y; y < rect.Max.Y; y++ { 154 | img.Set(x, y, c) 155 | } 156 | } 157 | } 158 | 159 | // Read unmarshal color from conn 160 | func ReadColor(c io.Reader, pf *PixelFormat) (*color.RGBA, error) { 161 | if pf.TrueColor == 0 { 162 | return nil, errors.New("support for non true color formats was not implemented") 163 | } 164 | order := pf.order() 165 | var pixel uint32 166 | 167 | switch pf.BPP { 168 | case 8: 169 | var px uint8 170 | if err := binary.Read(c, order, &px); err != nil { 171 | return nil, err 172 | } 173 | pixel = uint32(px) 174 | case 16: 175 | var px uint16 176 | if err := binary.Read(c, order, &px); err != nil { 177 | return nil, err 178 | } 179 | pixel = uint32(px) 180 | case 32: 181 | var px uint32 182 | if err := binary.Read(c, order, &px); err != nil { 183 | return nil, err 184 | } 185 | pixel = uint32(px) 186 | } 187 | 188 | rgb := color.RGBA{ 189 | R: uint8((pixel >> pf.RedShift) & uint32(pf.RedMax)), 190 | G: uint8((pixel >> pf.GreenShift) & uint32(pf.GreenMax)), 191 | B: uint8((pixel >> pf.BlueShift) & uint32(pf.BlueMax)), 192 | A: 1, 193 | } 194 | 195 | return &rgb, nil 196 | } 197 | 198 | func DecodeRaw(reader io.Reader, pf *PixelFormat, rect *Rectangle, targetImage draw.Image) error { 199 | for y := 0; y < int(rect.Height); y++ { 200 | for x := 0; x < int(rect.Width); x++ { 201 | col, err := ReadColor(reader, pf) 202 | if err != nil { 203 | return err 204 | } 205 | 206 | targetImage.(draw.Image).Set(int(rect.X)+x, int(rect.Y)+y, col) 207 | } 208 | } 209 | 210 | return nil 211 | } 212 | 213 | func ReadUint8(r io.Reader) (uint8, error) { 214 | var myUint uint8 215 | if err := binary.Read(r, binary.BigEndian, &myUint); err != nil { 216 | return 0, err 217 | } 218 | 219 | return myUint, nil 220 | } 221 | func ReadUint16(r io.Reader) (uint16, error) { 222 | var myUint uint16 223 | if err := binary.Read(r, binary.BigEndian, &myUint); err != nil { 224 | return 0, err 225 | } 226 | 227 | return myUint, nil 228 | } 229 | 230 | func ReadUint32(r io.Reader) (uint32, error) { 231 | var myUint uint32 232 | if err := binary.Read(r, binary.BigEndian, &myUint); err != nil { 233 | return 0, err 234 | } 235 | 236 | return myUint, nil 237 | } 238 | 239 | func MakeRect(x, y, width, height int) image.Rectangle { 240 | return image.Rectangle{Min: image.Point{X: x, Y: y}, Max: image.Point{X: x + width, Y: y + height}} 241 | } 242 | func MakeRectFromVncRect(rect *Rectangle) image.Rectangle { 243 | return MakeRect(int(rect.X), int(rect.Y), int(rect.Width), int(rect.Height)) 244 | } 245 | -------------------------------------------------------------------------------- /encoding_util_test.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import "testing" 4 | 5 | func TestSetChanged(t *testing.T) { 6 | canvas := &VncCanvas{} 7 | rect := &Rectangle{X: 1, Y: 1, Width: 1024, Height: 64} 8 | canvas.SetChanged(rect) 9 | if canvas.Changed["64,0"] == false || 10 | canvas.Changed["64,1"] == false || 11 | canvas.Changed["64,4"] == false { 12 | t.Fail() 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /encoding_xcursor.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "math" 6 | ) 7 | 8 | type XCursorPseudoEncoding struct { 9 | PrimaryR uint8 10 | PrimaryG uint8 11 | PrimaryB uint8 12 | SecondaryR uint8 13 | SecondaryG uint8 14 | SecondaryB uint8 15 | Bitmap []byte 16 | Bitmask []byte 17 | } 18 | 19 | func (*XCursorPseudoEncoding) Supported(Conn) bool { 20 | return true 21 | } 22 | func (*XCursorPseudoEncoding) Reset() error { 23 | return nil 24 | } 25 | 26 | func (*XCursorPseudoEncoding) Type() EncodingType { return EncXCursorPseudo } 27 | 28 | // Read implements the Encoding interface. 29 | func (enc *XCursorPseudoEncoding) Read(c Conn, rect *Rectangle) error { 30 | if err := binary.Read(c, binary.BigEndian, &enc.PrimaryR); err != nil { 31 | return err 32 | } 33 | if err := binary.Read(c, binary.BigEndian, &enc.PrimaryG); err != nil { 34 | return err 35 | } 36 | if err := binary.Read(c, binary.BigEndian, &enc.PrimaryB); err != nil { 37 | return err 38 | } 39 | if err := binary.Read(c, binary.BigEndian, &enc.SecondaryR); err != nil { 40 | return err 41 | } 42 | if err := binary.Read(c, binary.BigEndian, &enc.SecondaryG); err != nil { 43 | return err 44 | } 45 | if err := binary.Read(c, binary.BigEndian, &enc.SecondaryB); err != nil { 46 | return err 47 | } 48 | 49 | bitmapsize := int(math.Floor((float64(rect.Width)+7)/8) * float64(rect.Height)) 50 | bitmasksize := int(math.Floor((float64(rect.Width)+7)/8) * float64(rect.Height)) 51 | 52 | enc.Bitmap = make([]byte, bitmapsize) 53 | enc.Bitmask = make([]byte, bitmasksize) 54 | if err := binary.Read(c, binary.BigEndian, &enc.Bitmap); err != nil { 55 | return err 56 | } 57 | if err := binary.Read(c, binary.BigEndian, &enc.Bitmask); err != nil { 58 | return err 59 | } 60 | 61 | return nil 62 | } 63 | 64 | func (enc *XCursorPseudoEncoding) Write(c Conn, rect *Rectangle) error { 65 | if err := binary.Write(c, binary.BigEndian, enc.PrimaryR); err != nil { 66 | return err 67 | } 68 | if err := binary.Write(c, binary.BigEndian, enc.PrimaryG); err != nil { 69 | return err 70 | } 71 | if err := binary.Write(c, binary.BigEndian, enc.PrimaryB); err != nil { 72 | return err 73 | } 74 | if err := binary.Write(c, binary.BigEndian, enc.SecondaryR); err != nil { 75 | return err 76 | } 77 | if err := binary.Write(c, binary.BigEndian, enc.SecondaryG); err != nil { 78 | return err 79 | } 80 | if err := binary.Write(c, binary.BigEndian, enc.SecondaryB); err != nil { 81 | return err 82 | } 83 | 84 | if err := binary.Write(c, binary.BigEndian, enc.Bitmap); err != nil { 85 | return err 86 | } 87 | if err := binary.Write(c, binary.BigEndian, enc.Bitmask); err != nil { 88 | return err 89 | } 90 | 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /encoding_zlib.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bytes" 5 | "compress/zlib" 6 | "image/draw" 7 | "io" 8 | ) 9 | 10 | type ZLibEncoding struct { 11 | Image draw.Image 12 | unzipper io.Reader 13 | zippedBuff *bytes.Buffer 14 | } 15 | 16 | func (*ZLibEncoding) Type() EncodingType { 17 | return EncZlib 18 | } 19 | 20 | func (enc *ZLibEncoding) WriteTo(w io.Writer) (n int, err error) { 21 | return 0, nil 22 | } 23 | 24 | func (enc *ZLibEncoding) Write(c Conn, rect *Rectangle) error { 25 | return nil 26 | } 27 | 28 | func (enc *ZLibEncoding) SetTargetImage(img draw.Image) { 29 | enc.Image = img 30 | } 31 | 32 | func (*ZLibEncoding) Supported(Conn) bool { 33 | return true 34 | } 35 | func (enc *ZLibEncoding) Reset() error { 36 | enc.unzipper = nil 37 | return nil 38 | } 39 | 40 | func (enc *ZLibEncoding) Read(r Conn, rect *Rectangle) error { 41 | //func (z *ZLibEncoding) Read(pixelFmt *PixelFormat, rect *Rectangle, r io.Reader) (Encoding, error) { 42 | //conn := RfbReadHelper{Reader:r} 43 | //conn := &DataSource{conn: conn.c, PixelFormat: conn.PixelFormat} 44 | pf := r.PixelFormat() 45 | //bytesPerPixel := r.PixelFormat().BPP / 8 46 | //bytesBuff := &bytes.Buffer{} 47 | zippedLen, err := ReadUint32(r) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | b, err := ReadBytes(int(zippedLen), r) 53 | if err != nil { 54 | return err 55 | } 56 | bytesBuff := bytes.NewBuffer(b) 57 | 58 | if enc.unzipper == nil { 59 | enc.unzipper, err = zlib.NewReader(bytesBuff) 60 | enc.zippedBuff = bytesBuff 61 | if err != nil { 62 | return err 63 | } 64 | } else { 65 | enc.zippedBuff.Write(b) 66 | } 67 | DecodeRaw(enc.unzipper, &pf, rect, enc.Image) 68 | 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /encoding_zrle.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bytes" 5 | "compress/zlib" 6 | "errors" 7 | "image/color" 8 | "image/draw" 9 | "io" 10 | "github.com/amitbet/vnc2video/logger" 11 | ) 12 | 13 | type ZRLEEncoding struct { 14 | bytes []byte 15 | Image draw.Image 16 | unzipper io.Reader 17 | zippedBuff *bytes.Buffer 18 | } 19 | 20 | func (*ZRLEEncoding) Supported(Conn) bool { 21 | return true 22 | } 23 | 24 | func (enc *ZRLEEncoding) SetTargetImage(img draw.Image) { 25 | enc.Image = img 26 | } 27 | 28 | func (enc *ZRLEEncoding) Reset() error { 29 | enc.unzipper = nil 30 | return nil 31 | } 32 | 33 | func (*ZRLEEncoding) Type() EncodingType { return EncZRLE } 34 | 35 | func (z *ZRLEEncoding) WriteTo(w io.Writer) (n int, err error) { 36 | return w.Write(z.bytes) 37 | } 38 | 39 | func (enc *ZRLEEncoding) Write(c Conn, rect *Rectangle) error { 40 | return nil 41 | } 42 | 43 | func IsCPixelSpecific(pf *PixelFormat) bool { 44 | significant := int(pf.RedMax<= 2 && subEnc <= 16: 139 | err = enc.handlePaletteTile(tileOffsetX, tileOffsetY, tileWidth, tileHeight, subEnc, pf, rect) 140 | if err != nil { 141 | return err 142 | } 143 | case subEnc == 128: 144 | err = enc.handlePlainRLETile(tileOffsetX, tileOffsetY, tileWidth, tileHeight, pf, rect) 145 | if err != nil { 146 | return err 147 | } 148 | case subEnc >= 130 && subEnc <= 255: 149 | err = enc.handlePaletteRLETile(tileOffsetX, tileOffsetY, tileWidth, tileHeight, subEnc, pf, rect) 150 | if err != nil { 151 | return err 152 | } 153 | default: 154 | logger.Errorf("Unknown ZRLE subencoding: %v", subEnc) 155 | } 156 | } 157 | } 158 | return nil 159 | } 160 | 161 | func (enc *ZRLEEncoding) handlePaletteRLETile(tileOffsetX, tileOffsetY, tileWidth, tileHeight int, subEnc uint8, pf *PixelFormat, rect *Rectangle) error { 162 | // Palette RLE 163 | paletteSize := subEnc - 128 164 | palette := make([]*color.RGBA, paletteSize) 165 | var err error 166 | 167 | // Read RLE palette 168 | for j := 0; j < int(paletteSize); j++ { 169 | palette[j], err = readCPixel(enc.unzipper, pf) 170 | if err != nil { 171 | logger.Errorf("renderZRLE: error while reading color in palette RLE subencoding: %v", err) 172 | return err 173 | } 174 | } 175 | var index uint8 176 | runLen := 0 177 | for y := 0; y < tileHeight; y++ { 178 | for x := 0; x < tileWidth; x++ { 179 | 180 | if runLen == 0 { 181 | 182 | // Read length and index 183 | index, err = ReadUint8(enc.unzipper) 184 | if err != nil { 185 | logger.Errorf("renderZRLE: error while reading length and index in palette RLE subencoding: %v", err) 186 | //return err 187 | } 188 | runLen = 1 189 | 190 | // Run is represented by index | 0x80 191 | // Otherwise, single pixel 192 | if (index & 0x80) != 0 { 193 | 194 | index -= 128 195 | 196 | runLen, err = readRunLength(enc.unzipper) 197 | if err != nil { 198 | logger.Errorf("handlePlainRLETile: error while reading runlength in plain RLE subencoding: %v", err) 199 | return err 200 | } 201 | 202 | } 203 | //logger.Tracef("renderZRLE: writing pixel: col=%v times=%d", palette[index], runLen) 204 | } 205 | 206 | // Write pixel to image 207 | enc.Image.Set(tileOffsetX+int(rect.X)+x, tileOffsetY+int(rect.Y)+y, palette[index]) 208 | runLen-- 209 | } 210 | } 211 | return nil 212 | } 213 | 214 | func (enc *ZRLEEncoding) handlePaletteTile(tileOffsetX, tileOffsetY, tileWidth, tileHeight int, subEnc uint8, pf *PixelFormat, rect *Rectangle) error { 215 | //subenc here is also palette size 216 | paletteSize := subEnc 217 | palette := make([]*color.RGBA, paletteSize) 218 | var err error 219 | // Read palette 220 | for j := 0; j < int(paletteSize); j++ { 221 | palette[j], err = readCPixel(enc.unzipper, pf) 222 | if err != nil { 223 | logger.Errorf("renderZRLE: error while reading CPixel for palette tile: %v", err) 224 | return err 225 | } 226 | } 227 | // Calculate index size 228 | var indexBits, mask uint32 229 | if paletteSize == 2 { 230 | indexBits = 1 231 | mask = 0x80 232 | } else if paletteSize <= 4 { 233 | indexBits = 2 234 | mask = 0xC0 235 | } else { 236 | indexBits = 4 237 | mask = 0xF0 238 | } 239 | for y := 0; y < tileHeight; y++ { 240 | 241 | // Packing only occurs per-row 242 | bitsAvailable := uint32(0) 243 | buffer := uint32(0) 244 | 245 | for x := 0; x < tileWidth; x++ { 246 | 247 | // Buffer more bits if necessary 248 | if bitsAvailable == 0 { 249 | bits, err := ReadUint8(enc.unzipper) 250 | if err != nil { 251 | logger.Errorf("renderZRLE: error while reading first uint8 into buffer: %v", err) 252 | return err 253 | } 254 | buffer = uint32(bits) 255 | bitsAvailable = 8 256 | } 257 | 258 | // Read next pixel 259 | index := (buffer & mask) >> (8 - indexBits) 260 | buffer <<= indexBits 261 | bitsAvailable -= indexBits 262 | 263 | // Write pixel to image 264 | enc.Image.Set(tileOffsetX+int(rect.X)+x, tileOffsetY+int(rect.Y)+y, palette[index]) 265 | } 266 | } 267 | return err 268 | } 269 | 270 | func (enc *ZRLEEncoding) handlePlainRLETile(tileOffsetX int, tileOffsetY int, tileWidth int, tileHeight int, pf *PixelFormat, rect *Rectangle) error { 271 | var col *color.RGBA 272 | var err error 273 | runLen := 0 274 | for y := 0; y < tileHeight; y++ { 275 | for x := 0; x < tileWidth; x++ { 276 | 277 | if runLen == 0 { 278 | 279 | // Read length and color 280 | col, err = readCPixel(enc.unzipper, pf) 281 | if err != nil { 282 | logger.Errorf("handlePlainRLETile: error while reading CPixel in plain RLE subencoding: %v", err) 283 | return err 284 | } 285 | runLen, err = readRunLength(enc.unzipper) 286 | if err != nil { 287 | logger.Errorf("handlePlainRLETile: error while reading runlength in plain RLE subencoding: %v", err) 288 | return err 289 | } 290 | 291 | } 292 | 293 | // Write pixel to image 294 | enc.Image.Set(tileOffsetX+int(rect.X)+x, tileOffsetY+int(rect.Y)+y, col) 295 | runLen-- 296 | } 297 | } 298 | return err 299 | } 300 | 301 | func readRunLength(r io.Reader) (int, error) { 302 | runLen := 1 303 | 304 | addition, err := ReadUint8(r) 305 | if err != nil { 306 | logger.Errorf("renderZRLE: error while reading addition to runLen in plain RLE subencoding: %v", err) 307 | return 0, err 308 | } 309 | runLen += int(addition) 310 | 311 | for addition == 255 { 312 | addition, err = ReadUint8(r) 313 | if err != nil { 314 | logger.Errorf("renderZRLE: error while reading addition to runLen in-loop plain RLE subencoding: %v", err) 315 | return 0, err 316 | } 317 | runLen += int(addition) 318 | } 319 | return runLen, nil 320 | } 321 | 322 | // Reads cpixel color from reader 323 | func readCPixel(c io.Reader, pf *PixelFormat) (*color.RGBA, error) { 324 | if pf.TrueColor == 0 { 325 | return nil, errors.New("support for non true color formats was not implemented") 326 | } 327 | 328 | isZRLEFormat := IsCPixelSpecific(pf) 329 | var col *color.RGBA 330 | if isZRLEFormat { 331 | tbytes, err := ReadBytes(3, c) 332 | if err != nil { 333 | return nil, err 334 | } 335 | 336 | if pf.BigEndian != 1 { 337 | col = &color.RGBA{ 338 | B: uint8(tbytes[0]), 339 | G: uint8(tbytes[1]), 340 | R: uint8(tbytes[2]), 341 | A: uint8(1), 342 | } 343 | } else { 344 | col = &color.RGBA{ 345 | R: uint8(tbytes[0]), 346 | G: uint8(tbytes[1]), 347 | B: uint8(tbytes[2]), 348 | A: uint8(1), 349 | } 350 | } 351 | return col, nil 352 | } 353 | 354 | col, err := ReadColor(c, pf) 355 | if err != nil { 356 | logger.Errorf("readCPixel: Error while reading zrle: %v", err) 357 | } 358 | 359 | return col, nil 360 | } 361 | -------------------------------------------------------------------------------- /encodingtype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=EncodingType"; DO NOT EDIT. 2 | 3 | package vnc2video 4 | 5 | import "fmt" 6 | 7 | const _EncodingType_name = "EncContinuousUpdatesPseudoEncFencePseudoEncClientRedirectEncXvpPseudoEncExtendedDesktopSizePseudoEncDesktopNamePseudoEncTightPngEncQEMUExtendedKeyEventPseudoEncQEMUPointerMotionChangePseudoEncCompressionLevel1EncCompressionLevel2EncCompressionLevel3EncCompressionLevel4EncCompressionLevel5EncCompressionLevel6EncCompressionLevel7EncCompressionLevel8EncCompressionLevel9EncCompressionLevel10EncXCursorPseudoEncCursorPseudoEncLastRectPseudoEncDesktopSizePseudoEncJPEGQualityLevelPseudo1EncJPEGQualityLevelPseudo2EncJPEGQualityLevelPseudo3EncJPEGQualityLevelPseudo4EncJPEGQualityLevelPseudo5EncJPEGQualityLevelPseudo6EncJPEGQualityLevelPseudo7EncJPEGQualityLevelPseudo8EncJPEGQualityLevelPseudo9EncJPEGQualityLevelPseudo10EncRawEncCopyRectEncRREEncCoRREEncHextileEncZlibEncTightEncZlibHexEncUltra1EncUltra2EncTRLEEncZRLEEncJPEGEncJRLEEncAtenAST2100EncAtenASTJPEGEncAtenHermonEncAtenYarkonEncAtenPilot3" 8 | 9 | var _EncodingType_map = map[EncodingType]string{ 10 | -313: _EncodingType_name[0:26], 11 | -312: _EncodingType_name[26:40], 12 | -311: _EncodingType_name[40:57], 13 | -309: _EncodingType_name[57:69], 14 | -308: _EncodingType_name[69:97], 15 | -307: _EncodingType_name[97:117], 16 | -260: _EncodingType_name[117:128], 17 | -258: _EncodingType_name[128:157], 18 | -257: _EncodingType_name[157:189], 19 | -256: _EncodingType_name[189:209], 20 | -255: _EncodingType_name[209:229], 21 | -254: _EncodingType_name[229:249], 22 | -253: _EncodingType_name[249:269], 23 | -252: _EncodingType_name[269:289], 24 | -251: _EncodingType_name[289:309], 25 | -250: _EncodingType_name[309:329], 26 | -249: _EncodingType_name[329:349], 27 | -248: _EncodingType_name[349:369], 28 | -247: _EncodingType_name[369:390], 29 | -240: _EncodingType_name[390:406], 30 | -239: _EncodingType_name[406:421], 31 | -224: _EncodingType_name[421:438], 32 | -223: _EncodingType_name[438:458], 33 | -32: _EncodingType_name[458:484], 34 | -31: _EncodingType_name[484:510], 35 | -30: _EncodingType_name[510:536], 36 | -29: _EncodingType_name[536:562], 37 | -28: _EncodingType_name[562:588], 38 | -27: _EncodingType_name[588:614], 39 | -26: _EncodingType_name[614:640], 40 | -25: _EncodingType_name[640:666], 41 | -24: _EncodingType_name[666:692], 42 | -23: _EncodingType_name[692:719], 43 | 0: _EncodingType_name[719:725], 44 | 1: _EncodingType_name[725:736], 45 | 2: _EncodingType_name[736:742], 46 | 4: _EncodingType_name[742:750], 47 | 5: _EncodingType_name[750:760], 48 | 6: _EncodingType_name[760:767], 49 | 7: _EncodingType_name[767:775], 50 | 8: _EncodingType_name[775:785], 51 | 9: _EncodingType_name[785:794], 52 | 10: _EncodingType_name[794:803], 53 | 15: _EncodingType_name[803:810], 54 | 16: _EncodingType_name[810:817], 55 | 21: _EncodingType_name[817:824], 56 | 22: _EncodingType_name[824:831], 57 | 87: _EncodingType_name[831:845], 58 | 88: _EncodingType_name[845:859], 59 | 89: _EncodingType_name[859:872], 60 | 96: _EncodingType_name[872:885], 61 | 97: _EncodingType_name[885:898], 62 | } 63 | 64 | func (i EncodingType) String() string { 65 | if str, ok := _EncodingType_map[i]; ok { 66 | return str 67 | } 68 | return fmt.Sprintf("EncodingType(%d)", i) 69 | } 70 | -------------------------------------------------------------------------------- /example/client/debug: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitbet/vnc2video/9d50b9dab1d92afd6c71ca5ccc5157135c121428/example/client/debug -------------------------------------------------------------------------------- /example/client/ffmpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitbet/vnc2video/9d50b9dab1d92afd6c71ca5ccc5157135c121428/example/client/ffmpeg -------------------------------------------------------------------------------- /example/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | "os" 8 | "os/signal" 9 | "runtime" 10 | "runtime/pprof" 11 | "syscall" 12 | "time" 13 | vnc "github.com/amitbet/vnc2video" 14 | "github.com/amitbet/vnc2video/encoders" 15 | "github.com/amitbet/vnc2video/logger" 16 | ) 17 | 18 | func main() { 19 | runtime.GOMAXPROCS(4) 20 | framerate := 12 21 | runWithProfiler := false 22 | 23 | // Establish TCP connection to VNC server. 24 | nc, err := net.DialTimeout("tcp", os.Args[1], 5*time.Second) 25 | if err != nil { 26 | logger.Fatalf("Error connecting to VNC host. %v", err) 27 | } 28 | 29 | logger.Tracef("starting up the client, connecting to: %s", os.Args[1]) 30 | // Negotiate connection with the server. 31 | cchServer := make(chan vnc.ServerMessage) 32 | cchClient := make(chan vnc.ClientMessage) 33 | errorCh := make(chan error) 34 | 35 | ccfg := &vnc.ClientConfig{ 36 | SecurityHandlers: []vnc.SecurityHandler{ 37 | //&vnc.ClientAuthATEN{Username: []byte(os.Args[2]), Password: []byte(os.Args[3])} 38 | &vnc.ClientAuthVNC{Password: []byte("12345")}, 39 | &vnc.ClientAuthNone{}, 40 | }, 41 | DrawCursor: true, 42 | PixelFormat: vnc.PixelFormat32bit, 43 | ClientMessageCh: cchClient, 44 | ServerMessageCh: cchServer, 45 | Messages: vnc.DefaultServerMessages, 46 | Encodings: []vnc.Encoding{ 47 | &vnc.RawEncoding{}, 48 | &vnc.TightEncoding{}, 49 | &vnc.HextileEncoding{}, 50 | &vnc.ZRLEEncoding{}, 51 | &vnc.CopyRectEncoding{}, 52 | &vnc.CursorPseudoEncoding{}, 53 | &vnc.CursorPosPseudoEncoding{}, 54 | &vnc.ZLibEncoding{}, 55 | &vnc.RREEncoding{}, 56 | }, 57 | ErrorCh: errorCh, 58 | } 59 | 60 | cc, err := vnc.Connect(context.Background(), nc, ccfg) 61 | screenImage := cc.Canvas 62 | if err != nil { 63 | logger.Fatalf("Error negotiating connection to VNC host. %v", err) 64 | } 65 | // out, err := os.Create("./output" + strconv.Itoa(counter) + ".jpg") 66 | // if err != nil { 67 | // fmt.Println(err)p 68 | // os.Exit(1) 69 | // } 70 | //vcodec := &encoders.MJPegImageEncoder{Quality: 60 , Framerate: framerate} 71 | //vcodec := &encoders.X264ImageEncoder{FFMpegBinPath: "./ffmpeg", Framerate: framerate} 72 | //vcodec := &encoders.HuffYuvImageEncoder{FFMpegBinPath: "./ffmpeg", Framerate: framerate} 73 | vcodec := &encoders.QTRLEImageEncoder{FFMpegBinPath: "./ffmpeg", Framerate: framerate} 74 | //vcodec := &encoders.VP8ImageEncoder{FFMpegBinPath:"./ffmpeg", Framerate: framerate} 75 | //vcodec := &encoders.DV9ImageEncoder{FFMpegBinPath:"./ffmpeg", Framerate: framerate} 76 | 77 | //counter := 0 78 | //vcodec.Init("./output" + strconv.Itoa(counter)) 79 | 80 | go vcodec.Run("./output.mp4") 81 | //windows 82 | ///go vcodec.Run("/Users/amitbet/Dropbox/go/src/vnc2webm/example/file-reader/ffmpeg", "./output.mp4") 83 | 84 | //go vcodec.Run("C:\\Users\\betzalel\\Dropbox\\go\\src\\vnc2video\\example\\client\\ffmpeg.exe", "output.mp4") 85 | //vcodec.Run("./output") 86 | 87 | //screenImage := vnc.NewVncCanvas(int(cc.Width()), int(cc.Height())) 88 | 89 | for _, enc := range ccfg.Encodings { 90 | myRenderer, ok := enc.(vnc.Renderer) 91 | 92 | if ok { 93 | myRenderer.SetTargetImage(screenImage) 94 | } 95 | } 96 | // var out *os.File 97 | 98 | logger.Tracef("connected to: %s", os.Args[1]) 99 | defer cc.Close() 100 | 101 | cc.SetEncodings([]vnc.EncodingType{ 102 | vnc.EncCursorPseudo, 103 | vnc.EncPointerPosPseudo, 104 | vnc.EncCopyRect, 105 | vnc.EncTight, 106 | vnc.EncZRLE, 107 | //vnc.EncHextile, 108 | //vnc.EncZlib, 109 | //vnc.EncRRE, 110 | }) 111 | //rect := image.Rect(0, 0, int(cc.Width()), int(cc.Height())) 112 | //screenImage := image.NewRGBA64(rect) 113 | // Process messages coming in on the ServerMessage channel. 114 | 115 | go func() { 116 | for { 117 | timeStart := time.Now() 118 | 119 | vcodec.Encode(screenImage.Image) 120 | 121 | timeTarget := timeStart.Add((1000 / time.Duration(framerate)) * time.Millisecond) 122 | timeLeft := timeTarget.Sub(time.Now()) 123 | if timeLeft > 0 { 124 | time.Sleep(timeLeft) 125 | } 126 | } 127 | }() 128 | 129 | sigc := make(chan os.Signal, 1) 130 | signal.Notify(sigc, 131 | syscall.SIGHUP, 132 | syscall.SIGINT, 133 | syscall.SIGTERM, 134 | syscall.SIGQUIT) 135 | frameBufferReq := 0 136 | timeStart := time.Now() 137 | 138 | if runWithProfiler { 139 | profFile := "prof.file" 140 | f, err := os.Create(profFile) 141 | if err != nil { 142 | log.Fatal(err) 143 | } 144 | pprof.StartCPUProfile(f) 145 | defer pprof.StopCPUProfile() 146 | } 147 | 148 | for { 149 | select { 150 | case err := <-errorCh: 151 | panic(err) 152 | case msg := <-cchClient: 153 | logger.Tracef("Received client message type:%v msg:%v\n", msg.Type(), msg) 154 | case msg := <-cchServer: 155 | //logger.Tracef("Received server message type:%v msg:%v\n", msg.Type(), msg) 156 | 157 | // out, err := os.Create("./output" + strconv.Itoa(counter) + ".jpg") 158 | // if err != nil { 159 | // fmt.Println(err) 160 | // os.Exit(1) 161 | // } 162 | 163 | if msg.Type() == vnc.FramebufferUpdateMsgType { 164 | secsPassed := time.Now().Sub(timeStart).Seconds() 165 | frameBufferReq++ 166 | reqPerSec := float64(frameBufferReq) / secsPassed 167 | //counter++ 168 | //jpeg.Encode(out, screenImage, nil) 169 | ///vcodec.Encode(screenImage) 170 | logger.Infof("reqs=%d, seconds=%f, Req Per second= %f", frameBufferReq, secsPassed, reqPerSec) 171 | 172 | reqMsg := vnc.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: cc.Width(), Height: cc.Height()} 173 | //cc.ResetAllEncodings() 174 | reqMsg.Write(cc) 175 | } 176 | case signal := <-sigc: 177 | if signal != nil { 178 | vcodec.Close() 179 | pprof.StopCPUProfile() 180 | time.Sleep(2 * time.Second) 181 | os.Exit(1) 182 | } 183 | } 184 | } 185 | //cc.Wait() 186 | } 187 | -------------------------------------------------------------------------------- /example/file-reader/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "time" 7 | vnc "github.com/amitbet/vnc2video" 8 | "github.com/amitbet/vnc2video/encoders" 9 | "github.com/amitbet/vnc2video/logger" 10 | ) 11 | 12 | func main() { 13 | framerate := 10 14 | speedupFactor := 3.0 15 | fastFramerate := int(float64(framerate) * speedupFactor) 16 | 17 | if len(os.Args) <= 1 { 18 | logger.Errorf("please provide a fbs file name") 19 | return 20 | } 21 | if _, err := os.Stat(os.Args[1]); os.IsNotExist(err) { 22 | logger.Errorf("File doesn't exist", err) 23 | return 24 | } 25 | encs := []vnc.Encoding{ 26 | &vnc.RawEncoding{}, 27 | &vnc.TightEncoding{}, 28 | &vnc.CopyRectEncoding{}, 29 | &vnc.ZRLEEncoding{}, 30 | } 31 | 32 | fbs, err := vnc.NewFbsConn( 33 | os.Args[1], 34 | encs, 35 | ) 36 | if err != nil { 37 | logger.Error("failed to open fbs reader:", err) 38 | //return nil, err 39 | } 40 | 41 | //launch video encoding process: 42 | vcodec := &encoders.X264ImageEncoder{FFMpegBinPath: "./ffmpeg", Framerate: framerate} 43 | //vcodec := &encoders.DV8ImageEncoder{} 44 | //vcodec := &encoders.DV9ImageEncoder{} 45 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 46 | logger.Tracef("current dir: %s", dir) 47 | go vcodec.Run("./output.mp4") 48 | 49 | //screenImage := image.NewRGBA(image.Rect(0, 0, int(fbs.Width()), int(fbs.Height()))) 50 | screenImage := vnc.NewVncCanvas(int(fbs.Width()), int(fbs.Height())) 51 | screenImage.DrawCursor = false 52 | 53 | for _, enc := range encs { 54 | myRenderer, ok := enc.(vnc.Renderer) 55 | 56 | if ok { 57 | myRenderer.SetTargetImage(screenImage) 58 | } 59 | } 60 | 61 | go func() { 62 | frameMillis := (1000.0 / float64(fastFramerate)) - 1 //a couple of millis, adjusting for time lost in software commands 63 | frameDuration := time.Duration(frameMillis * float64(time.Millisecond)) 64 | //logger.Error("milis= ", frameMillis) 65 | 66 | for { 67 | timeStart := time.Now() 68 | 69 | vcodec.Encode(screenImage.Image) 70 | timeTarget := timeStart.Add(frameDuration) 71 | timeLeft := timeTarget.Sub(time.Now()) 72 | //.Add(1 * time.Millisecond) 73 | if timeLeft > 0 { 74 | time.Sleep(timeLeft) 75 | //logger.Error("sleeping= ", timeLeft) 76 | } 77 | } 78 | }() 79 | 80 | msgReader := vnc.NewFBSPlayHelper(fbs) 81 | //loop over all messages, feed images to video codec: 82 | for { 83 | _, err := msgReader.ReadFbsMessage(true, speedupFactor) 84 | //vcodec.Encode(screenImage.Image) 85 | if err != nil { 86 | os.Exit(-1) 87 | } 88 | //vcodec.Encode(screenImage) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /example/proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/base64" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/http" 11 | _ "net/http/pprof" 12 | "net/url" 13 | "strings" 14 | "sync" 15 | "time" 16 | vnc "github.com/amitbet/vnc2video" 17 | "github.com/amitbet/vnc2video/logger" 18 | ) 19 | 20 | type Auth struct { 21 | Username []byte 22 | Password []byte 23 | } 24 | 25 | type Proxy struct { 26 | cc vnc.Conn 27 | conns chan vnc.Conn 28 | inp chan vnc.ClientMessage 29 | out chan vnc.ServerMessage 30 | } 31 | 32 | var ( 33 | cliconns = make(map[string]*Proxy) 34 | srvconns = make(map[vnc.Conn]string) 35 | m sync.Mutex 36 | ) 37 | 38 | func newConn(hostport string, password []byte) (vnc.Conn, chan vnc.ClientMessage, chan vnc.ServerMessage, chan vnc.Conn, error) { 39 | fmt.Printf("new conn to %s with %s\n", hostport, password) 40 | if cc, ok := cliconns[hostport]; ok { 41 | return cc.cc, cc.inp, cc.out, cc.conns, nil 42 | } 43 | c, err := net.DialTimeout("tcp", hostport, 10*time.Second) 44 | if err != nil { 45 | return nil, nil, nil, nil, err 46 | } 47 | cchServer := make(chan vnc.ServerMessage) 48 | cchClient := make(chan vnc.ClientMessage) 49 | errorCh := make(chan error) 50 | ccfg := &vnc.ClientConfig{ 51 | SecurityHandlers: []vnc.SecurityHandler{&vnc.ClientAuthVNC{Password: password}}, 52 | PixelFormat: vnc.PixelFormat32bit, 53 | ClientMessageCh: cchClient, 54 | ServerMessageCh: cchServer, 55 | //ServerMessages: vnc.DefaultServerMessages, 56 | Encodings: []vnc.Encoding{&vnc.RawEncoding{}}, 57 | ErrorCh: errorCh, 58 | } 59 | csrv := make(chan vnc.Conn) 60 | inp := make(chan vnc.ClientMessage) 61 | out := make(chan vnc.ServerMessage) 62 | fmt.Printf("connect to vnc\n") 63 | cc, err := vnc.Connect(context.Background(), c, ccfg) 64 | if err != nil { 65 | return nil, nil, nil, nil, err 66 | } 67 | fmt.Printf("connected to vnc %#+v\n", cc) 68 | ds := &vnc.DefaultClientMessageHandler{} 69 | go ds.Handle(cc) 70 | go handleIO(cc, inp, out, csrv) 71 | 72 | return cc, inp, out, csrv, nil 73 | } 74 | 75 | func handleIO(cli vnc.Conn, inp chan vnc.ClientMessage, out chan vnc.ServerMessage, csrv chan vnc.Conn) { 76 | fmt.Printf("handle io\n") 77 | ccfg := cli.Config().(*vnc.ClientConfig) 78 | defer cli.Close() 79 | var conns []vnc.Conn 80 | //var prepared bool 81 | 82 | for { 83 | select { 84 | case err := <-ccfg.ErrorCh: 85 | for _, srv := range conns { 86 | srv.Close() 87 | } 88 | fmt.Printf("err %v\n", err) 89 | return 90 | case msg := <-ccfg.ServerMessageCh: 91 | for _, srv := range conns { 92 | scfg := srv.Config().(*vnc.ServerConfig) 93 | scfg.ServerMessageCh <- msg 94 | } 95 | case msg := <-inp: 96 | // messages from real clients 97 | fmt.Printf("3 %#+v\n", msg) 98 | switch msg.Type() { 99 | case vnc.SetPixelFormatMsgType: 100 | 101 | case vnc.SetEncodingsMsgType: 102 | var encTypes []vnc.EncodingType 103 | encs := []vnc.Encoding{ 104 | // &vnc.TightPngEncoding{}, 105 | &vnc.CopyRectEncoding{}, 106 | &vnc.RawEncoding{}, 107 | } 108 | for _, senc := range encs { 109 | for _, cenc := range msg.(*vnc.SetEncodings).Encodings { 110 | if cenc == senc.Type() { 111 | encTypes = append(encTypes, senc.Type()) 112 | } 113 | } 114 | } 115 | ccfg.ClientMessageCh <- &vnc.SetEncodings{Encodings: encTypes} 116 | default: 117 | ccfg.ClientMessageCh <- msg 118 | } 119 | case msg := <-out: 120 | fmt.Printf("4 %#+v\n", msg) 121 | case srv := <-csrv: 122 | conns = append(conns, srv) 123 | } 124 | 125 | } 126 | 127 | } 128 | 129 | type HijackHandler struct{} 130 | 131 | func (*HijackHandler) Handle(c vnc.Conn) error { 132 | m.Lock() 133 | defer m.Unlock() 134 | hostport, ok := srvconns[c] 135 | if !ok { 136 | return fmt.Errorf("client connect in server pool not found") 137 | } 138 | proxy, ok := cliconns[hostport] 139 | if !ok { 140 | return fmt.Errorf("client connect to qemu not found") 141 | } 142 | cfg := c.Config().(*vnc.ServerConfig) 143 | cfg.ClientMessageCh = proxy.inp 144 | cfg.ServerMessageCh = proxy.out 145 | 146 | proxy.conns <- c 147 | ds := &vnc.DefaultServerMessageHandler{} 148 | go ds.Handle(c) 149 | return nil 150 | } 151 | 152 | type AuthVNCHTTP struct { 153 | c *http.Client 154 | vnc.ServerAuthVNC 155 | } 156 | 157 | func (auth *AuthVNCHTTP) Auth(c vnc.Conn) error { 158 | auth.ServerAuthVNC.Challenge = []byte("clodo.ruclodo.ru") 159 | if err := auth.ServerAuthVNC.WriteChallenge(c); err != nil { 160 | return err 161 | } 162 | if err := auth.ServerAuthVNC.ReadChallenge(c); err != nil { 163 | return err 164 | } 165 | 166 | buf := new(bytes.Buffer) 167 | enc := base64.NewEncoder(base64.StdEncoding, buf) 168 | enc.Write(auth.ServerAuthVNC.Crypted) 169 | enc.Close() 170 | 171 | v := url.Values{} 172 | v.Set("hash", buf.String()) 173 | buf.Reset() 174 | src, _, _ := net.SplitHostPort(c.Conn().RemoteAddr().String()) 175 | v.Set("ip", src) 176 | res, err := auth.c.PostForm("https://api.ix.clodo.ru/system/vnc", v) 177 | if err != nil { 178 | return err 179 | } 180 | if res.StatusCode != 200 || res.Body == nil { 181 | if res.Body != nil { 182 | io.Copy(buf, res.Body) 183 | } 184 | fmt.Printf("failed to get auth data: code %d body %s\n", res.StatusCode, buf.String()) 185 | defer buf.Reset() 186 | return fmt.Errorf("failed to get auth data: code %d body %s", res.StatusCode, buf.String()) 187 | } 188 | _, err = io.Copy(buf, res.Body) 189 | if err != nil { 190 | return fmt.Errorf("failed to get auth data: %s", err.Error()) 191 | } 192 | logger.Debugf("http auth: %s\n", buf.Bytes()) 193 | res.Body.Close() 194 | data := strings.Split(buf.String(), " ") 195 | if len(data) < 2 { 196 | return fmt.Errorf("failed to get auth data data invalid") 197 | } 198 | buf.Reset() 199 | 200 | hostport := string(data[0]) 201 | password := []byte(data[1]) 202 | 203 | m.Lock() 204 | defer m.Unlock() 205 | cc, inp, out, conns, err := newConn(hostport, password) 206 | if err != nil { 207 | return err 208 | } 209 | cliconns[hostport] = &Proxy{cc, conns, inp, out} 210 | srvconns[c] = hostport 211 | c.SetWidth(cc.Width()) 212 | c.SetHeight(cc.Height()) 213 | return nil 214 | } 215 | 216 | func (*AuthVNCHTTP) Type() vnc.SecurityType { 217 | return vnc.SecTypeVNC 218 | } 219 | 220 | func (*AuthVNCHTTP) SubType() vnc.SecuritySubType { 221 | return vnc.SecSubTypeUnknown 222 | } 223 | 224 | func main() { 225 | go func() { 226 | logger.Info(http.ListenAndServe(":6060", nil)) 227 | }() 228 | 229 | ln, err := net.Listen("tcp", ":6900") 230 | if err != nil { 231 | logger.Fatalf("Error listen. %v", err) 232 | } 233 | 234 | schClient := make(chan vnc.ClientMessage) 235 | schServer := make(chan vnc.ServerMessage) 236 | 237 | scfg := &vnc.ServerConfig{ 238 | SecurityHandlers: []vnc.SecurityHandler{ 239 | &AuthVNCHTTP{c: &http.Client{}}, 240 | }, 241 | Encodings: []vnc.Encoding{ 242 | // &vnc.TightPngEncoding{}, 243 | &vnc.CopyRectEncoding{}, 244 | &vnc.RawEncoding{}, 245 | }, 246 | PixelFormat: vnc.PixelFormat32bit, 247 | ClientMessageCh: schClient, 248 | ServerMessageCh: schServer, 249 | //ClientMessages: vnc.DefaultClientMessages, 250 | DesktopName: []byte("vnc proxy"), 251 | } 252 | scfg.Handlers = append(scfg.Handlers, vnc.DefaultServerHandlers...) 253 | scfg.Handlers = append(scfg.Handlers[:len(scfg.Handlers)-1], &HijackHandler{}) 254 | vnc.Serve(context.Background(), ln, scfg) 255 | } 256 | -------------------------------------------------------------------------------- /example/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "image" 7 | "math" 8 | "net" 9 | "time" 10 | vnc "github.com/amitbet/vnc2video" 11 | "github.com/amitbet/vnc2video/logger" 12 | ) 13 | 14 | func main() { 15 | ln, err := net.Listen("tcp", ":5900") 16 | if err != nil { 17 | logger.Fatalf("Error listen. %v", err) 18 | } 19 | 20 | chServer := make(chan vnc.ClientMessage) 21 | chClient := make(chan vnc.ServerMessage) 22 | 23 | im := image.NewRGBA(image.Rect(0, 0, width, height)) 24 | tick := time.NewTicker(time.Second / 2) 25 | defer tick.Stop() 26 | 27 | cfg := &vnc.ServerConfig{ 28 | Width: 800, 29 | Height: 600, 30 | //VersionHandler: vnc.ServerVersionHandler, 31 | //SecurityHandler: vnc.ServerSecurityHandler, 32 | SecurityHandlers: []vnc.SecurityHandler{&vnc.ClientAuthNone{}}, 33 | //ClientInitHandler: vnc.ServerClientInitHandler, 34 | //ServerInitHandler: vnc.ServerServerInitHandler, 35 | Encodings: []vnc.Encoding{&vnc.RawEncoding{}}, 36 | PixelFormat: vnc.PixelFormat32bit, 37 | ClientMessageCh: chServer, 38 | ServerMessageCh: chClient, 39 | Messages: vnc.DefaultClientMessages, 40 | } 41 | cfg.Handlers = vnc.DefaultServerHandlers 42 | go vnc.Serve(context.Background(), ln, cfg) 43 | 44 | // Process messages coming in on the ClientMessage channel. 45 | for { 46 | select { 47 | case <-tick.C: 48 | drawImage(im, 0) 49 | fmt.Printf("tick\n") 50 | case msg := <-chClient: 51 | switch msg.Type() { 52 | default: 53 | logger.Tracef("11 Received message type:%v msg:%v\n", msg.Type(), msg) 54 | } 55 | case msg := <-chServer: 56 | switch msg.Type() { 57 | default: 58 | logger.Tracef("22 Received message type:%v msg:%v\n", msg.Type(), msg) 59 | } 60 | } 61 | } 62 | } 63 | 64 | const ( 65 | width = 800 66 | height = 600 67 | ) 68 | 69 | func drawImage(im *image.RGBA, anim int) { 70 | pos := 0 71 | const border = 50 72 | for y := 0; y < height; y++ { 73 | for x := 0; x < width; x++ { 74 | var r, g, b uint8 75 | switch { 76 | case x < border*2.5 && x < int((1.1+math.Sin(float64(y+anim*2)/40))*border): 77 | r = 255 78 | case x > width-border*2.5 && x > width-int((1.1+math.Sin(math.Pi+float64(y+anim*2)/40))*border): 79 | g = 255 80 | case y < border*2.5 && y < int((1.1+math.Sin(float64(x+anim*2)/40))*border): 81 | r, g = 255, 255 82 | case y > height-border*2.5 && y > height-int((1.1+math.Sin(math.Pi+float64(x+anim*2)/40))*border): 83 | b = 255 84 | default: 85 | r, g, b = uint8(x+anim), uint8(y+anim), uint8(x+y+anim*3) 86 | } 87 | im.Pix[pos] = r 88 | im.Pix[pos+1] = g 89 | im.Pix[pos+2] = b 90 | pos += 4 // skipping alpha 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /fbs-connection.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "github.com/amitbet/vnc2video/logger" 7 | 8 | "io" 9 | "time" 10 | ) 11 | 12 | // Conn represents vnc conection 13 | type FbsConn struct { 14 | FbsReader 15 | 16 | protocol string 17 | //c net.IServerConn 18 | //config *ClientConfig 19 | colorMap ColorMap 20 | 21 | // Encodings supported by the client. This should not be modified 22 | // directly. Instead, SetEncodings should be used. 23 | encodings []Encoding 24 | 25 | // Height of the frame buffer in pixels, sent from the server. 26 | fbHeight uint16 27 | 28 | // Width of the frame buffer in pixels, sent from the server. 29 | fbWidth uint16 30 | desktopName string 31 | // The pixel format associated with the connection. This shouldn't 32 | // be modified. If you wish to set a new pixel format, use the 33 | // SetPixelFormat method. 34 | pixelFormat PixelFormat 35 | } 36 | 37 | // func (c *FbsConn) Close() error { 38 | // return c.fbs.Close() 39 | // } 40 | 41 | // // Read reads data from conn 42 | // func (c *FbsConn) Read(buf []byte) (int, error) { 43 | // return c.fbs.Read(buf) 44 | // } 45 | 46 | //dummy, no writing to this conn... 47 | func (c *FbsConn) Write(buf []byte) (int, error) { 48 | return len(buf), nil 49 | } 50 | 51 | func (c *FbsConn) Conn() net.Conn { 52 | return nil 53 | } 54 | 55 | func (c *FbsConn) Config() interface{} { 56 | return nil 57 | } 58 | 59 | func (c *FbsConn) Protocol() string { 60 | return "RFB 003.008" 61 | } 62 | func (c *FbsConn) PixelFormat() PixelFormat { 63 | return c.pixelFormat 64 | } 65 | 66 | func (c *FbsConn) SetPixelFormat(pf PixelFormat) error { 67 | c.pixelFormat = pf 68 | return nil 69 | } 70 | 71 | func (c *FbsConn) ColorMap() ColorMap { return c.colorMap } 72 | func (c *FbsConn) SetColorMap(cm ColorMap) { c.colorMap = cm } 73 | func (c *FbsConn) Encodings() []Encoding { return c.encodings } 74 | func (c *FbsConn) SetEncodings([]EncodingType) error { return nil } 75 | func (c *FbsConn) Width() uint16 { return c.fbWidth } 76 | func (c *FbsConn) Height() uint16 { return c.fbHeight } 77 | func (c *FbsConn) SetWidth(w uint16) { c.fbWidth = w } 78 | func (c *FbsConn) SetHeight(h uint16) { c.fbHeight = h } 79 | func (c *FbsConn) DesktopName() []byte { return []byte(c.desktopName) } 80 | func (c *FbsConn) SetDesktopName(d []byte) { c.desktopName = string(d) } 81 | func (c *FbsConn) Flush() error { return nil } 82 | func (c *FbsConn) Wait() {} 83 | func (c *FbsConn) SetProtoVersion(string) {} 84 | func (c *FbsConn) SetSecurityHandler(SecurityHandler) error { return nil } 85 | func (c *FbsConn) SecurityHandler() SecurityHandler { return nil } 86 | func (c *FbsConn) GetEncInstance(typ EncodingType) Encoding { 87 | for _, enc := range c.encodings { 88 | if enc.Type() == typ { 89 | return enc 90 | } 91 | } 92 | return nil 93 | } 94 | 95 | type VncStreamFileReader interface { 96 | io.Reader 97 | CurrentTimestamp() int 98 | ReadStartSession() (*ServerInit, error) 99 | CurrentPixelFormat() *PixelFormat 100 | Encodings() []Encoding 101 | } 102 | 103 | type FBSPlayHelper struct { 104 | Conn *FbsConn 105 | //Fbs VncStreamFileReader 106 | serverMessageMap map[uint8]ServerMessage 107 | firstSegDone bool 108 | startTime int 109 | } 110 | 111 | func NewFbsConn(filename string, encs []Encoding) (*FbsConn, error) { 112 | 113 | fbs, err := NewFbsReader(filename) 114 | if err != nil { 115 | logger.Error("failed to open fbs reader:", err) 116 | return nil, err 117 | } 118 | 119 | //NewFbsReader("/Users/amitbet/vncRec/recording.rbs") 120 | initMsg, err := fbs.ReadStartSession() 121 | if err != nil { 122 | logger.Error("failed to open read fbs start session:", err) 123 | return nil, err 124 | } 125 | fbsConn := &FbsConn{FbsReader: *fbs} 126 | fbsConn.encodings = encs 127 | fbsConn.SetPixelFormat(initMsg.PixelFormat) 128 | fbsConn.SetHeight(initMsg.FBHeight) 129 | fbsConn.SetWidth(initMsg.FBWidth) 130 | fbsConn.SetDesktopName([]byte(initMsg.NameText)) 131 | 132 | return fbsConn, nil 133 | } 134 | 135 | func NewFBSPlayHelper(r *FbsConn) *FBSPlayHelper { 136 | h := &FBSPlayHelper{Conn: r} 137 | h.startTime = int(time.Now().UnixNano() / int64(time.Millisecond)) 138 | 139 | h.serverMessageMap = make(map[uint8]ServerMessage) 140 | h.serverMessageMap[0] = &FramebufferUpdate{} 141 | h.serverMessageMap[1] = &SetColorMapEntries{} 142 | h.serverMessageMap[2] = &Bell{} 143 | h.serverMessageMap[3] = &ServerCutText{} 144 | 145 | return h 146 | } 147 | 148 | // func (handler *FBSPlayHelper) Consume(seg *RfbSegment) error { 149 | 150 | // switch seg.SegmentType { 151 | // case SegmentFullyParsedClientMessage: 152 | // clientMsg := seg.Message.(ClientMessage) 153 | // logger.Tracef("ClientUpdater.Consume:(vnc-server-bound) got ClientMessage type=%s", clientMsg.Type()) 154 | // switch clientMsg.Type() { 155 | 156 | // case FramebufferUpdateRequestMsgType: 157 | // if !handler.firstSegDone { 158 | // handler.firstSegDone = true 159 | // handler.startTime = int(time.Now().UnixNano() / int64(time.Millisecond)) 160 | // } 161 | // handler.sendFbsMessage() 162 | // } 163 | // // server.MsgFramebufferUpdateRequest: 164 | // } 165 | // return nil 166 | // } 167 | 168 | func (h *FBSPlayHelper) ReadFbsMessage(SyncWithTimestamps bool, SpeedFactor float64) (ServerMessage, error) { 169 | var messageType uint8 170 | //messages := make(map[uint8]ServerMessage) 171 | fbs := h.Conn 172 | //conn := h.Conn 173 | err := binary.Read(fbs, binary.BigEndian, &messageType) 174 | if err != nil { 175 | logger.Error("FBSConn.NewConnHandler: Error in reading FBS: ", err) 176 | return nil, err 177 | } 178 | startTimeMsgHandling := time.Now() 179 | //IClientConn{} 180 | //binary.Write(h.Conn, binary.BigEndian, messageType) 181 | msg := h.serverMessageMap[messageType] 182 | if msg == nil { 183 | logger.Error("FBSConn.NewConnHandler: Error unknown message type: ", messageType) 184 | return nil, err 185 | } 186 | //read the actual message data 187 | //err = binary.Read(fbs, binary.BigEndian, &msg) 188 | parsedMsg, err := msg.Read(fbs) 189 | if err != nil { 190 | logger.Error("FBSConn.NewConnHandler: Error in reading FBS message: ", err) 191 | return nil, err 192 | } 193 | 194 | millisSinceStart := int(startTimeMsgHandling.UnixNano()/int64(time.Millisecond)) - h.startTime 195 | adjestedTimeStamp := float64(fbs.CurrentTimestamp()) / SpeedFactor 196 | millisToSleep := adjestedTimeStamp - float64(millisSinceStart) 197 | 198 | if millisToSleep > 0 && SyncWithTimestamps { 199 | 200 | time.Sleep(time.Duration(millisToSleep) * time.Millisecond) 201 | } else if millisToSleep < -400 { 202 | logger.Errorf("rendering time is noticeably off, change speedup factor: videoTimeLine: %f, currentTime:%d, offset: %f", adjestedTimeStamp, millisSinceStart, millisToSleep) 203 | } 204 | 205 | return parsedMsg, nil 206 | } 207 | -------------------------------------------------------------------------------- /fbs-reader.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | "os" 8 | //"vncproxy/common" 9 | //"vncproxy/encodings" 10 | "github.com/amitbet/vnc2video/logger" 11 | //"vncproxy/encodings" 12 | //"vncproxy/encodings" 13 | ) 14 | 15 | type FbsReader struct { 16 | reader io.ReadCloser 17 | buffer bytes.Buffer 18 | currentTimestamp int 19 | //pixelFormat *PixelFormat 20 | //encodings []IEncoding 21 | } 22 | 23 | func (fbs *FbsReader) Close() error { 24 | return fbs.reader.Close() 25 | } 26 | 27 | func (fbs *FbsReader) CurrentTimestamp() int { 28 | return fbs.currentTimestamp 29 | } 30 | 31 | func (fbs *FbsReader) Read(p []byte) (n int, err error) { 32 | if fbs.buffer.Len() < len(p) { 33 | seg, err := fbs.ReadSegment() 34 | 35 | if err != nil { 36 | logger.Error("FBSReader.Read: error reading FBSsegment: ", err) 37 | return 0, err 38 | } 39 | fbs.buffer.Write(seg.bytes) 40 | fbs.currentTimestamp = int(seg.timestamp) 41 | } 42 | return fbs.buffer.Read(p) 43 | } 44 | 45 | //func (fbs *FbsReader) CurrentPixelFormat() *PixelFormat { return fbs.pixelFormat } 46 | //func (fbs *FbsReader) CurrentColorMap() *common.ColorMap { return &common.ColorMap{} } 47 | //func (fbs *FbsReader) Encodings() []IEncoding { return fbs.encodings } 48 | 49 | func NewFbsReader(fbsFile string) (*FbsReader, error) { 50 | 51 | reader, err := os.OpenFile(fbsFile, os.O_RDONLY, 0644) 52 | if err != nil { 53 | logger.Error("NewFbsReader: can't open fbs file: ", fbsFile) 54 | return nil, err 55 | } 56 | return &FbsReader{reader: reader}, //encodings: []IEncoding{ 57 | // //&encodings.CopyRectEncoding{}, 58 | // //&encodings.ZLibEncoding{}, 59 | // //&encodings.ZRLEEncoding{}, 60 | // //&encodings.CoRREEncoding{}, 61 | // //&encodings.HextileEncoding{}, 62 | // &TightEncoding{}, 63 | // //&TightPngEncoding{}, 64 | // //&EncCursorPseudo{}, 65 | // &RawEncoding{}, 66 | // //&encodings.RREEncoding{}, 67 | //}, 68 | nil 69 | 70 | } 71 | 72 | func (fbs *FbsReader) ReadStartSession() (*ServerInit, error) { 73 | 74 | initMsg := ServerInit{} 75 | reader := fbs.reader 76 | 77 | var framebufferWidth uint16 78 | var framebufferHeight uint16 79 | var SecTypeNone uint32 80 | //read rfb header information (the only part done without the [size|data|timestamp] block wrapper) 81 | //.("FBS 001.000\n") 82 | bytes := make([]byte, 12) 83 | _, err := reader.Read(bytes) 84 | if err != nil { 85 | logger.Error("FbsReader.ReadStartSession: error reading rbs init message - FBS file Version:", err) 86 | return nil, err 87 | } 88 | 89 | //read the version message into the buffer, it is written in the first fbs block 90 | //RFB 003.008\n 91 | bytes = make([]byte, 12) 92 | _, err = fbs.Read(bytes) 93 | if err != nil { 94 | logger.Error("FbsReader.ReadStartSession: error reading rbs init - RFB Version: ", err) 95 | return nil, err 96 | } 97 | 98 | //push sec type and fb dimensions 99 | binary.Read(fbs, binary.BigEndian, &SecTypeNone) 100 | if err != nil { 101 | logger.Error("FbsReader.ReadStartSession: error reading rbs init - SecType: ", err) 102 | } 103 | 104 | //read frame buffer width, height 105 | binary.Read(fbs, binary.BigEndian, &framebufferWidth) 106 | if err != nil { 107 | logger.Error("FbsReader.ReadStartSession: error reading rbs init - FBWidth: ", err) 108 | return nil, err 109 | } 110 | initMsg.FBWidth = framebufferWidth 111 | 112 | binary.Read(fbs, binary.BigEndian, &framebufferHeight) 113 | if err != nil { 114 | logger.Error("FbsReader.ReadStartSession: error reading rbs init - FBHeight: ", err) 115 | return nil, err 116 | } 117 | initMsg.FBHeight = framebufferHeight 118 | 119 | //read pixel format 120 | pixelFormat := &PixelFormat{} 121 | binary.Read(fbs, binary.BigEndian, pixelFormat) 122 | if err != nil { 123 | logger.Error("FbsReader.ReadStartSession: error reading rbs init - Pixelformat: ", err) 124 | return nil, err 125 | } 126 | 127 | initMsg.PixelFormat = *pixelFormat 128 | 129 | //read desktop name 130 | var desknameLen uint32 131 | binary.Read(fbs, binary.BigEndian, &desknameLen) 132 | if err != nil { 133 | logger.Error("FbsReader.ReadStartSession: error reading rbs init - deskname Len: ", err) 134 | return nil, err 135 | } 136 | initMsg.NameLength = desknameLen 137 | 138 | bytes = make([]byte, desknameLen) 139 | fbs.Read(bytes) 140 | if err != nil { 141 | logger.Error("FbsReader.ReadStartSession: error reading rbs init - desktopName: ", err) 142 | return nil, err 143 | } 144 | 145 | initMsg.NameText = bytes 146 | 147 | return &initMsg, nil 148 | } 149 | 150 | func (fbs *FbsReader) ReadSegment() (*FbsSegment, error) { 151 | reader := fbs.reader 152 | var bytesLen uint32 153 | 154 | //read length 155 | err := binary.Read(reader, binary.BigEndian, &bytesLen) 156 | if err != nil { 157 | logger.Error("FbsReader.ReadSegment: reading len, error reading rbs file: ", err) 158 | return nil, err 159 | } 160 | 161 | paddedSize := (bytesLen + 3) & 0x7FFFFFFC 162 | 163 | //read bytes 164 | bytes := make([]byte, paddedSize) 165 | _, err = reader.Read(bytes) 166 | if err != nil { 167 | logger.Error("FbsReader.ReadSegment: reading bytes, error reading rbs file: ", err) 168 | return nil, err 169 | } 170 | 171 | //remove padding 172 | actualBytes := bytes[:bytesLen] 173 | 174 | //read timestamp 175 | var timeSinceStart uint32 176 | binary.Read(reader, binary.BigEndian, &timeSinceStart) 177 | if err != nil { 178 | logger.Error("FbsReader.ReadSegment: read timestamp, error reading rbs file: ", err) 179 | return nil, err 180 | } 181 | 182 | //timeStamp := time.Unix(timeSinceStart, 0) 183 | seg := &FbsSegment{bytes: actualBytes, timestamp: timeSinceStart} 184 | return seg, nil 185 | } 186 | 187 | type FbsSegment struct { 188 | bytes []byte 189 | timestamp uint32 190 | } 191 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/amitbet/vnc2video 2 | 3 | go 1.12 4 | 5 | require github.com/icza/mjpeg v0.0.0-20170217094447-85dfbe473743 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/icza/mjpeg v0.0.0-20170217094447-85dfbe473743 h1:u5kZEGcjrCRAS99gyW/wptM3KjGYkVv80WKexNvxBuA= 2 | github.com/icza/mjpeg v0.0.0-20170217094447-85dfbe473743/go.mod h1:Eja3x31oRrEOzl6ihhsxY23gXaTYWLP3Gwj5nMAJ7m0= 3 | -------------------------------------------------------------------------------- /handlers.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "github.com/amitbet/vnc2video/logger" 7 | ) 8 | 9 | // Handler represents handler of handshake 10 | type Handler interface { 11 | Handle(Conn) error 12 | } 13 | 14 | // ProtoVersionLength protocol version length 15 | const ProtoVersionLength = 12 16 | 17 | const ( 18 | // ProtoVersionUnknown unknown version 19 | ProtoVersionUnknown = "" 20 | // ProtoVersion33 sets if proto 003.003 21 | ProtoVersion33 = "RFB 003.003\n" 22 | // ProtoVersion38 sets if proto 003.008 23 | ProtoVersion38 = "RFB 003.008\n" 24 | // ProtoVersion37 sets if proto 003.007 25 | ProtoVersion37 = "RFB 003.007\n" 26 | ) 27 | 28 | // ParseProtoVersion parse protocol version 29 | func ParseProtoVersion(pv []byte) (uint, uint, error) { 30 | var major, minor uint 31 | 32 | if len(pv) < ProtoVersionLength { 33 | return 0, 0, fmt.Errorf("ProtocolVersion message too short (%v < %v)", len(pv), ProtoVersionLength) 34 | } 35 | 36 | l, err := fmt.Sscanf(string(pv), "RFB %d.%d\n", &major, &minor) 37 | if l != 2 { 38 | return 0, 0, fmt.Errorf("error parsing protocol version") 39 | } 40 | if err != nil { 41 | return 0, 0, err 42 | } 43 | 44 | return major, minor, nil 45 | } 46 | 47 | // DefaultClientVersionHandler represents default handler 48 | type DefaultClientVersionHandler struct{} 49 | 50 | // Handle provide version handler for client side 51 | func (*DefaultClientVersionHandler) Handle(c Conn) error { 52 | var version [ProtoVersionLength]byte 53 | 54 | if err := binary.Read(c, binary.BigEndian, &version); err != nil { 55 | return err 56 | } 57 | 58 | major, minor, err := ParseProtoVersion(version[:]) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | pv := ProtoVersionUnknown 64 | if major == 3 { 65 | if minor >= 8 { 66 | pv = ProtoVersion38 67 | } else if minor >= 3 { 68 | pv = ProtoVersion38 69 | } 70 | } 71 | if pv == ProtoVersionUnknown { 72 | return fmt.Errorf("ProtocolVersion handshake failed; unsupported version '%v'", string(version[:])) 73 | } 74 | c.SetProtoVersion(string(version[:])) 75 | 76 | if err := binary.Write(c, binary.BigEndian, []byte(pv)); err != nil { 77 | return err 78 | } 79 | return c.Flush() 80 | } 81 | 82 | // DefaultServerVersionHandler represents default server handler 83 | type DefaultServerVersionHandler struct{} 84 | 85 | // Handle provide server version handler 86 | func (*DefaultServerVersionHandler) Handle(c Conn) error { 87 | var version [ProtoVersionLength]byte 88 | if err := binary.Write(c, binary.BigEndian, []byte(ProtoVersion38)); err != nil { 89 | return err 90 | } 91 | if err := c.Flush(); err != nil { 92 | return err 93 | } 94 | if err := binary.Read(c, binary.BigEndian, &version); err != nil { 95 | return err 96 | } 97 | major, minor, err := ParseProtoVersion(version[:]) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | pv := ProtoVersionUnknown 103 | if major == 3 { 104 | if minor >= 8 { 105 | pv = ProtoVersion38 106 | } else if minor >= 3 { 107 | pv = ProtoVersion33 108 | } 109 | } 110 | if pv == ProtoVersionUnknown { 111 | return fmt.Errorf("ProtocolVersion handshake failed; unsupported version '%v'", string(version[:])) 112 | } 113 | 114 | c.SetProtoVersion(pv) 115 | return nil 116 | } 117 | 118 | // DefaultClientSecurityHandler used for client security handler 119 | type DefaultClientSecurityHandler struct{} 120 | 121 | // Handle provide client side security handler 122 | func (*DefaultClientSecurityHandler) Handle(c Conn) error { 123 | cfg := c.Config().(*ClientConfig) 124 | var numSecurityTypes uint8 125 | if err := binary.Read(c, binary.BigEndian, &numSecurityTypes); err != nil { 126 | return err 127 | } 128 | secTypes := make([]SecurityType, numSecurityTypes) 129 | if err := binary.Read(c, binary.BigEndian, &secTypes); err != nil { 130 | return err 131 | } 132 | 133 | var secType SecurityHandler 134 | for _, st := range cfg.SecurityHandlers { 135 | for _, sc := range secTypes { 136 | if st.Type() == sc { 137 | secType = st 138 | } 139 | } 140 | } 141 | 142 | if err := binary.Write(c, binary.BigEndian, cfg.SecurityHandlers[0].Type()); err != nil { 143 | return err 144 | } 145 | 146 | if err := c.Flush(); err != nil { 147 | return err 148 | } 149 | 150 | err := secType.Auth(c) 151 | if err != nil { 152 | logger.Error("Authentication error: ", err) 153 | return err 154 | } 155 | 156 | var authCode uint32 157 | if err := binary.Read(c, binary.BigEndian, &authCode); err != nil { 158 | return err 159 | } 160 | 161 | logger.Tracef("authenticating, secType: %d, auth code(0=success): %d", secType.Type(), authCode) 162 | if authCode == 1 { 163 | var reasonLength uint32 164 | if err := binary.Read(c, binary.BigEndian, &reasonLength); err != nil { 165 | return err 166 | } 167 | reasonText := make([]byte, reasonLength) 168 | if err := binary.Read(c, binary.BigEndian, &reasonText); err != nil { 169 | return err 170 | } 171 | return fmt.Errorf("%s", reasonText) 172 | } 173 | c.SetSecurityHandler(secType) 174 | return nil 175 | } 176 | 177 | // DefaultServerSecurityHandler used for server security handler 178 | type DefaultServerSecurityHandler struct{} 179 | 180 | // Handle provide server side security handler 181 | func (*DefaultServerSecurityHandler) Handle(c Conn) error { 182 | cfg := c.Config().(*ServerConfig) 183 | var secType SecurityType 184 | if c.Protocol() == ProtoVersion37 || c.Protocol() == ProtoVersion38 { 185 | if err := binary.Write(c, binary.BigEndian, uint8(len(cfg.SecurityHandlers))); err != nil { 186 | return err 187 | } 188 | 189 | for _, sectype := range cfg.SecurityHandlers { 190 | if err := binary.Write(c, binary.BigEndian, sectype.Type()); err != nil { 191 | return err 192 | } 193 | } 194 | } else { 195 | st := uint32(0) 196 | for _, sectype := range cfg.SecurityHandlers { 197 | if uint32(sectype.Type()) > st { 198 | st = uint32(sectype.Type()) 199 | secType = sectype.Type() 200 | } 201 | } 202 | if err := binary.Write(c, binary.BigEndian, st); err != nil { 203 | return err 204 | } 205 | } 206 | if err := c.Flush(); err != nil { 207 | return err 208 | } 209 | 210 | if c.Protocol() == ProtoVersion38 { 211 | if err := binary.Read(c, binary.BigEndian, &secType); err != nil { 212 | return err 213 | } 214 | } 215 | secTypes := make(map[SecurityType]SecurityHandler) 216 | for _, sType := range cfg.SecurityHandlers { 217 | secTypes[sType.Type()] = sType 218 | } 219 | 220 | sType, ok := secTypes[secType] 221 | if !ok { 222 | return fmt.Errorf("security type %d not implemented", secType) 223 | } 224 | 225 | var authCode uint32 226 | authErr := sType.Auth(c) 227 | if authErr != nil { 228 | authCode = uint32(1) 229 | } 230 | 231 | if err := binary.Write(c, binary.BigEndian, authCode); err != nil { 232 | return err 233 | } 234 | 235 | if authErr == nil { 236 | if err := c.Flush(); err != nil { 237 | return err 238 | } 239 | c.SetSecurityHandler(sType) 240 | return nil 241 | } 242 | 243 | if c.Protocol() == ProtoVersion38 { 244 | if err := binary.Write(c, binary.BigEndian, uint32(len(authErr.Error()))); err != nil { 245 | return err 246 | } 247 | if err := binary.Write(c, binary.BigEndian, []byte(authErr.Error())); err != nil { 248 | return err 249 | } 250 | if err := c.Flush(); err != nil { 251 | return err 252 | } 253 | } 254 | return authErr 255 | } 256 | 257 | // DefaultClientServerInitHandler default client server init handler 258 | type DefaultClientServerInitHandler struct{} 259 | 260 | // Handle provide default server init handler 261 | func (*DefaultClientServerInitHandler) Handle(c Conn) error { 262 | logger.Trace("starting DefaultClientServerInitHandler") 263 | var err error 264 | srvInit := ServerInit{} 265 | 266 | if err = binary.Read(c, binary.BigEndian, &srvInit.FBWidth); err != nil { 267 | return err 268 | } 269 | if err = binary.Read(c, binary.BigEndian, &srvInit.FBHeight); err != nil { 270 | return err 271 | } 272 | if err = binary.Read(c, binary.BigEndian, &srvInit.PixelFormat); err != nil { 273 | return err 274 | } 275 | if err = binary.Read(c, binary.BigEndian, &srvInit.NameLength); err != nil { 276 | return err 277 | } 278 | 279 | srvInit.NameText = make([]byte, srvInit.NameLength) 280 | if err = binary.Read(c, binary.BigEndian, &srvInit.NameText); err != nil { 281 | return err 282 | } 283 | logger.Tracef("DefaultClientServerInitHandler got serverInit: %v", srvInit) 284 | c.SetDesktopName(srvInit.NameText) 285 | if c.Protocol() == "aten1" { 286 | c.SetWidth(800) 287 | c.SetHeight(600) 288 | c.SetPixelFormat(NewPixelFormatAten()) 289 | } else { 290 | c.SetWidth(srvInit.FBWidth) 291 | c.SetHeight(srvInit.FBHeight) 292 | 293 | //telling the server to use 32bit pixels (with 24 dept, tight standard format) 294 | pixelMsg := SetPixelFormat{PF: PixelFormat32bit} 295 | pixelMsg.Write(c) 296 | c.SetPixelFormat(PixelFormat32bit) 297 | //c.SetPixelFormat(srvInit.PixelFormat) 298 | } 299 | if c.Protocol() == "aten1" { 300 | ikvm := struct { 301 | _ [8]byte 302 | IKVMVideoEnable uint8 303 | IKVMKMEnable uint8 304 | IKVMKickEnable uint8 305 | VUSBEnable uint8 306 | }{} 307 | if err = binary.Read(c, binary.BigEndian, &ikvm); err != nil { 308 | return err 309 | } 310 | } 311 | /* 312 | caps := struct { 313 | ServerMessagesNum uint16 314 | ClientMessagesNum uint16 315 | EncodingsNum uint16 316 | _ [2]byte 317 | }{} 318 | if err := binary.Read(c, binary.BigEndian, &caps); err != nil { 319 | return err 320 | } 321 | 322 | caps.ServerMessagesNum = uint16(1) 323 | var item [16]byte 324 | for i := uint16(0); i < caps.ServerMessagesNum; i++ { 325 | if err := binary.Read(c, binary.BigEndian, &item); err != nil { 326 | return err 327 | } 328 | fmt.Printf("server message cap %s\n", item) 329 | } 330 | 331 | for i := uint16(0); i < caps.ClientMessagesNum; i++ { 332 | if err := binary.Read(c, binary.BigEndian, &item); err != nil { 333 | return err 334 | } 335 | fmt.Printf("client message cap %s\n", item) 336 | } 337 | for i := uint16(0); i < caps.EncodingsNum; i++ { 338 | if err := binary.Read(c, binary.BigEndian, &item); err != nil { 339 | return err 340 | } 341 | fmt.Printf("encoding cap %s\n", item) 342 | } 343 | // var pad [1]byte 344 | // if err := binary.Read(c, binary.BigEndian, &pad); err != nil { 345 | // return err 346 | // } 347 | }*/ 348 | return nil 349 | } 350 | 351 | // DefaultServerServerInitHandler default server server init handler 352 | type DefaultServerServerInitHandler struct{} 353 | 354 | // Handle provide default server server init handler 355 | func (*DefaultServerServerInitHandler) Handle(c Conn) error { 356 | if err := binary.Write(c, binary.BigEndian, c.Width()); err != nil { 357 | return err 358 | } 359 | if err := binary.Write(c, binary.BigEndian, c.Height()); err != nil { 360 | return err 361 | } 362 | if err := binary.Write(c, binary.BigEndian, c.PixelFormat()); err != nil { 363 | return err 364 | } 365 | if err := binary.Write(c, binary.BigEndian, uint32(len(c.DesktopName()))); err != nil { 366 | return err 367 | } 368 | if err := binary.Write(c, binary.BigEndian, []byte(c.DesktopName())); err != nil { 369 | return err 370 | } 371 | return c.Flush() 372 | } 373 | 374 | // DefaultClientClientInitHandler default client client init handler 375 | type DefaultClientClientInitHandler struct{} 376 | 377 | // Handle provide default client client init handler 378 | func (*DefaultClientClientInitHandler) Handle(c Conn) error { 379 | logger.Trace("starting DefaultClientClientInitHandler") 380 | cfg := c.Config().(*ClientConfig) 381 | var shared uint8 382 | if cfg.Exclusive { 383 | shared = 0 384 | } else { 385 | shared = 1 386 | } 387 | if err := binary.Write(c, binary.BigEndian, shared); err != nil { 388 | return err 389 | } 390 | logger.Tracef("DefaultClientClientInitHandler sending: shared=%d", shared) 391 | return c.Flush() 392 | } 393 | 394 | // DefaultServerClientInitHandler default server client init handler 395 | type DefaultServerClientInitHandler struct{} 396 | 397 | // Handle provide default server client init handler 398 | func (*DefaultServerClientInitHandler) Handle(c Conn) error { 399 | var shared uint8 400 | if err := binary.Read(c, binary.BigEndian, &shared); err != nil { 401 | return err 402 | } 403 | /* TODO 404 | if shared != 1 { 405 | c.SetShared(false) 406 | } 407 | */ 408 | return nil 409 | } 410 | -------------------------------------------------------------------------------- /image.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "image" 7 | "github.com/amitbet/vnc2video/logger" 8 | ) 9 | 10 | //var _ draw.Drawer = (*ServerConn)(nil) 11 | //var _ draw.Image = (*ServerConn)(nil) 12 | 13 | // Color represents a single color in a color map. 14 | type Color struct { 15 | pf *PixelFormat 16 | cm *ColorMap 17 | cmIndex uint32 // Only valid if pf.TrueColor is false. 18 | R, G, B uint16 19 | } 20 | 21 | // ColorMap represent color map 22 | type ColorMap [256]Color 23 | 24 | // NewColor returns a new Color object 25 | func NewColor(pf *PixelFormat, cm *ColorMap) *Color { 26 | return &Color{ 27 | pf: pf, 28 | cm: cm, 29 | } 30 | } 31 | 32 | // Rectangle represents a rectangle of pixel data 33 | type Rectangle struct { 34 | X, Y uint16 35 | Width, Height uint16 36 | EncType EncodingType 37 | Enc Encoding 38 | } 39 | 40 | // String return string representation 41 | func (rect *Rectangle) String() string { 42 | return fmt.Sprintf("rect x: %d, y: %d, width: %d, height: %d, enc: %s", rect.X, rect.Y, rect.Width, rect.Height, rect.EncType) 43 | } 44 | 45 | // NewRectangle returns new rectangle 46 | func NewRectangle() *Rectangle { 47 | return &Rectangle{} 48 | } 49 | 50 | // Write marshal color to conn 51 | func (clr *Color) Write(c Conn) error { 52 | var err error 53 | pf := c.PixelFormat() 54 | order := pf.order() 55 | pixel := clr.cmIndex 56 | if clr.pf.TrueColor != 0 { 57 | pixel = uint32(clr.R) << pf.RedShift 58 | pixel |= uint32(clr.G) << pf.GreenShift 59 | pixel |= uint32(clr.B) << pf.BlueShift 60 | } 61 | 62 | switch pf.BPP { 63 | case 8: 64 | err = binary.Write(c, order, byte(pixel)) 65 | case 16: 66 | err = binary.Write(c, order, uint16(pixel)) 67 | case 32: 68 | err = binary.Write(c, order, uint32(pixel)) 69 | } 70 | 71 | return err 72 | } 73 | 74 | // Read unmarshal color from conn 75 | func (clr *Color) Read(c Conn) error { 76 | order := clr.pf.order() 77 | var pixel uint32 78 | 79 | switch clr.pf.BPP { 80 | case 8: 81 | var px uint8 82 | if err := binary.Read(c, order, &px); err != nil { 83 | return err 84 | } 85 | pixel = uint32(px) 86 | case 16: 87 | var px uint16 88 | if err := binary.Read(c, order, &px); err != nil { 89 | return err 90 | } 91 | pixel = uint32(px) 92 | case 32: 93 | var px uint32 94 | if err := binary.Read(c, order, &px); err != nil { 95 | return err 96 | } 97 | pixel = uint32(px) 98 | } 99 | 100 | if clr.pf.TrueColor != 0 { 101 | clr.R = uint16((pixel >> clr.pf.RedShift) & uint32(clr.pf.RedMax)) 102 | clr.G = uint16((pixel >> clr.pf.GreenShift) & uint32(clr.pf.GreenMax)) 103 | clr.B = uint16((pixel >> clr.pf.BlueShift) & uint32(clr.pf.BlueMax)) 104 | } else { 105 | *clr = clr.cm[pixel] 106 | clr.cmIndex = pixel 107 | } 108 | return nil 109 | } 110 | 111 | func colorsToImage(x, y, width, height uint16, colors []Color) *image.RGBA64 { 112 | rect := image.Rect(int(x), int(y), int(x+width), int(y+height)) 113 | rgba := image.NewRGBA64(rect) 114 | a := uint16(1) 115 | for i, color := range colors { 116 | rgba.Pix[4*i+0] = uint8(color.R >> 8) 117 | rgba.Pix[4*i+1] = uint8(color.R) 118 | rgba.Pix[4*i+2] = uint8(color.G >> 8) 119 | rgba.Pix[4*i+3] = uint8(color.G) 120 | rgba.Pix[4*i+4] = uint8(color.B >> 8) 121 | rgba.Pix[4*i+5] = uint8(color.B) 122 | rgba.Pix[4*i+6] = uint8(a >> 8) 123 | rgba.Pix[4*i+7] = uint8(a) 124 | } 125 | return rgba 126 | } 127 | 128 | // Write marshal rectangle to conn 129 | func (rect *Rectangle) Write(c Conn) error { 130 | var err error 131 | 132 | if err = binary.Write(c, binary.BigEndian, rect.X); err != nil { 133 | return err 134 | } 135 | if err = binary.Write(c, binary.BigEndian, rect.Y); err != nil { 136 | return err 137 | } 138 | if err = binary.Write(c, binary.BigEndian, rect.Width); err != nil { 139 | return err 140 | } 141 | if err = binary.Write(c, binary.BigEndian, rect.Height); err != nil { 142 | return err 143 | } 144 | if err = binary.Write(c, binary.BigEndian, rect.EncType); err != nil { 145 | return err 146 | } 147 | 148 | return rect.Enc.Write(c, rect) 149 | } 150 | 151 | // Read unmarshal rectangle from conn 152 | func (rect *Rectangle) Read(c Conn) error { 153 | var err error 154 | 155 | if err = binary.Read(c, binary.BigEndian, &rect.X); err != nil { 156 | return err 157 | } 158 | if err = binary.Read(c, binary.BigEndian, &rect.Y); err != nil { 159 | return err 160 | } 161 | if err = binary.Read(c, binary.BigEndian, &rect.Width); err != nil { 162 | return err 163 | } 164 | if err = binary.Read(c, binary.BigEndian, &rect.Height); err != nil { 165 | return err 166 | } 167 | if err = binary.Read(c, binary.BigEndian, &rect.EncType); err != nil { 168 | return err 169 | } 170 | logger.Debug(rect) 171 | switch rect.EncType { 172 | // case EncCopyRect: 173 | // rect.Enc = &CopyRectEncoding{} 174 | // case EncTight: 175 | // rect.Enc = c.GetEncInstance(rect.EncType) 176 | // case EncTightPng: 177 | // rect.Enc = &TightPngEncoding{} 178 | // case EncRaw: 179 | // if strings.HasPrefix(c.Protocol(), "aten") { 180 | // rect.Enc = &AtenHermon{} 181 | // } else { 182 | // rect.Enc = &RawEncoding{} 183 | // } 184 | case EncDesktopSizePseudo: 185 | rect.Enc = &DesktopSizePseudoEncoding{} 186 | case EncDesktopNamePseudo: 187 | rect.Enc = &DesktopNamePseudoEncoding{} 188 | // case EncXCursorPseudo: 189 | // rect.Enc = &XCursorPseudoEncoding{} 190 | // case EncAtenHermon: 191 | // rect.Enc = &AtenHermon{} 192 | default: 193 | rect.Enc = c.GetEncInstance(rect.EncType) 194 | if rect.Enc == nil { 195 | return fmt.Errorf("unsupported encoding %s", rect.EncType) 196 | } 197 | } 198 | 199 | return rect.Enc.Read(c, rect) 200 | } 201 | 202 | // Area returns the total area in pixels of the Rectangle 203 | func (rect *Rectangle) Area() int { return int(rect.Width) * int(rect.Height) } 204 | -------------------------------------------------------------------------------- /key_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Key"; DO NOT EDIT. 2 | 3 | package vnc2video 4 | 5 | import "fmt" 6 | 7 | const _Key_name = "SpaceExclaimQuoteDblNumberSignDollarPercentAmpersandApostropheParenLeftParenRightAsteriskPlusCommaMinusPeriodSlashDigit0Digit1Digit2Digit3Digit4Digit5Digit6Digit7Digit8Digit9ColonSemicolonLessEqualGreaterQuestionAtABCDEFGHIJKLMNOPQRSTUVWXYZBracketLeftBackslashBracketRightAsciiCircumUnderscoreGraveSmallASmallBSmallCSmallDSmallESmallFSmallGSmallHSmallISmallJSmallKSmallLSmallMSmallNSmallOSmallPSmallQSmallRSmallSSmallTSmallUSmallVSmallWSmallXSmallYSmallZBraceLeftBarBraceRightAsciiTildeBackSpaceTabLinefeedClearReturnPauseScrollLockSysReqEscapeHomeLeftUpRightDownPageUpPageDownEndBeginSelectModeSwitchNumLockKeypadSpaceKeypadTabKeypadEnterKeypadF1KeypadF2KeypadF3KeypadF4KeypadHomeKeypadLeftKeypadUpKeypadRightKeypadDownKeypadPriorKeypadPageUpKeypadNextKeypadPageDownKeypadEndKeypadBeginKeypadInsertKeypadDeleteKeypadMultiplyKeypadAddKeypadSeparatorKeypadSubtractKeypadDecimalKeypadDivideKeypad0Keypad1Keypad2Keypad3Keypad4Keypad5Keypad6Keypad7Keypad8Keypad9KeypadEqualF1F2F3F4F5F6F7F8F9F10F11F12ShiftLeftShiftRightControlLeftControlRightCapsLockShiftLockMetaLeftMetaRightAltLeftAltRightSuperLeftSuperRightHyperLeftHyperRightDelete" 8 | 9 | var _Key_map = map[Key]string{ 10 | 32: _Key_name[0:5], 11 | 33: _Key_name[5:12], 12 | 34: _Key_name[12:20], 13 | 35: _Key_name[20:30], 14 | 36: _Key_name[30:36], 15 | 37: _Key_name[36:43], 16 | 38: _Key_name[43:52], 17 | 39: _Key_name[52:62], 18 | 40: _Key_name[62:71], 19 | 41: _Key_name[71:81], 20 | 42: _Key_name[81:89], 21 | 43: _Key_name[89:93], 22 | 44: _Key_name[93:98], 23 | 45: _Key_name[98:103], 24 | 46: _Key_name[103:109], 25 | 47: _Key_name[109:114], 26 | 48: _Key_name[114:120], 27 | 49: _Key_name[120:126], 28 | 50: _Key_name[126:132], 29 | 51: _Key_name[132:138], 30 | 52: _Key_name[138:144], 31 | 53: _Key_name[144:150], 32 | 54: _Key_name[150:156], 33 | 55: _Key_name[156:162], 34 | 56: _Key_name[162:168], 35 | 57: _Key_name[168:174], 36 | 58: _Key_name[174:179], 37 | 59: _Key_name[179:188], 38 | 60: _Key_name[188:192], 39 | 61: _Key_name[192:197], 40 | 62: _Key_name[197:204], 41 | 63: _Key_name[204:212], 42 | 64: _Key_name[212:214], 43 | 65: _Key_name[214:215], 44 | 66: _Key_name[215:216], 45 | 67: _Key_name[216:217], 46 | 68: _Key_name[217:218], 47 | 69: _Key_name[218:219], 48 | 70: _Key_name[219:220], 49 | 71: _Key_name[220:221], 50 | 72: _Key_name[221:222], 51 | 73: _Key_name[222:223], 52 | 74: _Key_name[223:224], 53 | 75: _Key_name[224:225], 54 | 76: _Key_name[225:226], 55 | 77: _Key_name[226:227], 56 | 78: _Key_name[227:228], 57 | 79: _Key_name[228:229], 58 | 80: _Key_name[229:230], 59 | 81: _Key_name[230:231], 60 | 82: _Key_name[231:232], 61 | 83: _Key_name[232:233], 62 | 84: _Key_name[233:234], 63 | 85: _Key_name[234:235], 64 | 86: _Key_name[235:236], 65 | 87: _Key_name[236:237], 66 | 88: _Key_name[237:238], 67 | 89: _Key_name[238:239], 68 | 90: _Key_name[239:240], 69 | 91: _Key_name[240:251], 70 | 92: _Key_name[251:260], 71 | 93: _Key_name[260:272], 72 | 94: _Key_name[272:283], 73 | 95: _Key_name[283:293], 74 | 96: _Key_name[293:298], 75 | 97: _Key_name[298:304], 76 | 98: _Key_name[304:310], 77 | 99: _Key_name[310:316], 78 | 100: _Key_name[316:322], 79 | 101: _Key_name[322:328], 80 | 102: _Key_name[328:334], 81 | 103: _Key_name[334:340], 82 | 104: _Key_name[340:346], 83 | 105: _Key_name[346:352], 84 | 106: _Key_name[352:358], 85 | 107: _Key_name[358:364], 86 | 108: _Key_name[364:370], 87 | 109: _Key_name[370:376], 88 | 110: _Key_name[376:382], 89 | 111: _Key_name[382:388], 90 | 112: _Key_name[388:394], 91 | 113: _Key_name[394:400], 92 | 114: _Key_name[400:406], 93 | 115: _Key_name[406:412], 94 | 116: _Key_name[412:418], 95 | 117: _Key_name[418:424], 96 | 118: _Key_name[424:430], 97 | 119: _Key_name[430:436], 98 | 120: _Key_name[436:442], 99 | 121: _Key_name[442:448], 100 | 122: _Key_name[448:454], 101 | 123: _Key_name[454:463], 102 | 124: _Key_name[463:466], 103 | 125: _Key_name[466:476], 104 | 126: _Key_name[476:486], 105 | 65288: _Key_name[486:495], 106 | 65289: _Key_name[495:498], 107 | 65290: _Key_name[498:506], 108 | 65291: _Key_name[506:511], 109 | 65293: _Key_name[511:517], 110 | 65299: _Key_name[517:522], 111 | 65300: _Key_name[522:532], 112 | 65301: _Key_name[532:538], 113 | 65307: _Key_name[538:544], 114 | 65360: _Key_name[544:548], 115 | 65361: _Key_name[548:552], 116 | 65362: _Key_name[552:554], 117 | 65363: _Key_name[554:559], 118 | 65364: _Key_name[559:563], 119 | 65365: _Key_name[563:569], 120 | 65366: _Key_name[569:577], 121 | 65367: _Key_name[577:580], 122 | 65368: _Key_name[580:585], 123 | 65376: _Key_name[585:591], 124 | 65406: _Key_name[591:601], 125 | 65407: _Key_name[601:608], 126 | 65408: _Key_name[608:619], 127 | 65417: _Key_name[619:628], 128 | 65421: _Key_name[628:639], 129 | 65425: _Key_name[639:647], 130 | 65426: _Key_name[647:655], 131 | 65427: _Key_name[655:663], 132 | 65428: _Key_name[663:671], 133 | 65429: _Key_name[671:681], 134 | 65430: _Key_name[681:691], 135 | 65431: _Key_name[691:699], 136 | 65432: _Key_name[699:710], 137 | 65433: _Key_name[710:720], 138 | 65434: _Key_name[720:731], 139 | 65435: _Key_name[731:743], 140 | 65436: _Key_name[743:753], 141 | 65437: _Key_name[753:767], 142 | 65438: _Key_name[767:776], 143 | 65439: _Key_name[776:787], 144 | 65440: _Key_name[787:799], 145 | 65441: _Key_name[799:811], 146 | 65442: _Key_name[811:825], 147 | 65443: _Key_name[825:834], 148 | 65444: _Key_name[834:849], 149 | 65445: _Key_name[849:863], 150 | 65446: _Key_name[863:876], 151 | 65447: _Key_name[876:888], 152 | 65448: _Key_name[888:895], 153 | 65449: _Key_name[895:902], 154 | 65450: _Key_name[902:909], 155 | 65451: _Key_name[909:916], 156 | 65452: _Key_name[916:923], 157 | 65453: _Key_name[923:930], 158 | 65454: _Key_name[930:937], 159 | 65455: _Key_name[937:944], 160 | 65456: _Key_name[944:951], 161 | 65457: _Key_name[951:958], 162 | 65469: _Key_name[958:969], 163 | 65470: _Key_name[969:971], 164 | 65471: _Key_name[971:973], 165 | 65472: _Key_name[973:975], 166 | 65473: _Key_name[975:977], 167 | 65474: _Key_name[977:979], 168 | 65475: _Key_name[979:981], 169 | 65476: _Key_name[981:983], 170 | 65477: _Key_name[983:985], 171 | 65478: _Key_name[985:987], 172 | 65479: _Key_name[987:990], 173 | 65480: _Key_name[990:993], 174 | 65481: _Key_name[993:996], 175 | 65505: _Key_name[996:1005], 176 | 65506: _Key_name[1005:1015], 177 | 65507: _Key_name[1015:1026], 178 | 65508: _Key_name[1026:1038], 179 | 65509: _Key_name[1038:1046], 180 | 65510: _Key_name[1046:1055], 181 | 65511: _Key_name[1055:1063], 182 | 65512: _Key_name[1063:1072], 183 | 65513: _Key_name[1072:1079], 184 | 65514: _Key_name[1079:1087], 185 | 65515: _Key_name[1087:1096], 186 | 65516: _Key_name[1096:1106], 187 | 65517: _Key_name[1106:1115], 188 | 65518: _Key_name[1115:1125], 189 | 65535: _Key_name[1125:1131], 190 | } 191 | 192 | func (i Key) String() string { 193 | if str, ok := _Key_map[i]; ok { 194 | return str 195 | } 196 | return fmt.Sprintf("Key(%d)", i) 197 | } 198 | -------------------------------------------------------------------------------- /keys.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import "fmt" 4 | 5 | // Key represents a VNC key press. 6 | type Key uint32 7 | 8 | //go:generate stringer -type=Key 9 | 10 | // Keys is a slice of Key values. 11 | type Keys []Key 12 | 13 | var keymap = map[rune]Key{ 14 | '-': Minus, 15 | '0': Digit0, 16 | '1': Digit1, 17 | '2': Digit2, 18 | '3': Digit3, 19 | '4': Digit4, 20 | '5': Digit5, 21 | '6': Digit6, 22 | '7': Digit7, 23 | '8': Digit8, 24 | '9': Digit9, 25 | } 26 | 27 | // IntToKeys returns Keys that represent the key presses required to type an int. 28 | func IntToKeys(v int) Keys { 29 | k := Keys{} 30 | for _, c := range fmt.Sprintf("%d", v) { 31 | k = append(k, keymap[c]) 32 | } 33 | return k 34 | } 35 | 36 | // Latin 1 (byte 3 = 0) 37 | // ISO/IEC 8859-1 = Unicode U+0020..U+00FF 38 | const ( 39 | Space Key = iota + 0x0020 40 | Exclaim // exclamation mark 41 | QuoteDbl 42 | NumberSign 43 | Dollar 44 | Percent 45 | Ampersand 46 | Apostrophe 47 | ParenLeft 48 | ParenRight 49 | Asterisk 50 | Plus 51 | Comma 52 | Minus 53 | Period 54 | Slash 55 | Digit0 56 | Digit1 57 | Digit2 58 | Digit3 59 | Digit4 60 | Digit5 61 | Digit6 62 | Digit7 63 | Digit8 64 | Digit9 65 | Colon 66 | Semicolon 67 | Less 68 | Equal 69 | Greater 70 | Question 71 | At 72 | A 73 | B 74 | C 75 | D 76 | E 77 | F 78 | G 79 | H 80 | I 81 | J 82 | K 83 | L 84 | M 85 | N 86 | O 87 | P 88 | Q 89 | R 90 | S 91 | T 92 | U 93 | V 94 | W 95 | X 96 | Y 97 | Z 98 | BracketLeft 99 | Backslash 100 | BracketRight 101 | AsciiCircum 102 | Underscore 103 | Grave 104 | SmallA 105 | SmallB 106 | SmallC 107 | SmallD 108 | SmallE 109 | SmallF 110 | SmallG 111 | SmallH 112 | SmallI 113 | SmallJ 114 | SmallK 115 | SmallL 116 | SmallM 117 | SmallN 118 | SmallO 119 | SmallP 120 | SmallQ 121 | SmallR 122 | SmallS 123 | SmallT 124 | SmallU 125 | SmallV 126 | SmallW 127 | SmallX 128 | SmallY 129 | SmallZ 130 | BraceLeft 131 | Bar 132 | BraceRight 133 | AsciiTilde 134 | ) 135 | 136 | const ( 137 | BackSpace Key = iota + 0xff08 138 | Tab 139 | Linefeed 140 | Clear 141 | _ 142 | Return 143 | ) 144 | 145 | const ( 146 | Pause Key = iota + 0xff13 147 | ScrollLock 148 | SysReq 149 | Escape Key = 0xff1b 150 | Delete Key = 0xffff 151 | ) 152 | 153 | const ( // Cursor control & motion. 154 | Home Key = iota + 0xff50 155 | Left 156 | Up 157 | Right 158 | Down 159 | PageUp 160 | PageDown 161 | End 162 | Begin 163 | ) 164 | 165 | const ( // Misc functions. 166 | Select Key = 0xff60 167 | Print 168 | Execute 169 | Insert 170 | Undo 171 | Redo 172 | Menu 173 | Find 174 | Cancel 175 | Help 176 | Break 177 | ModeSwitch Key = 0xff7e 178 | NumLock Key = 0xff7f 179 | ) 180 | 181 | const ( // Keypad functions. 182 | KeypadSpace Key = 0xff80 183 | KeypadTab Key = 0xff89 184 | KeypadEnter Key = 0xff8d 185 | ) 186 | 187 | const ( // Keypad functions cont. 188 | KeypadF1 Key = iota + 0xff91 189 | KeypadF2 190 | KeypadF3 191 | KeypadF4 192 | KeypadHome 193 | KeypadLeft 194 | KeypadUp 195 | KeypadRight 196 | KeypadDown 197 | KeypadPrior 198 | KeypadPageUp 199 | KeypadNext 200 | KeypadPageDown 201 | KeypadEnd 202 | KeypadBegin 203 | KeypadInsert 204 | KeypadDelete 205 | KeypadMultiply 206 | KeypadAdd 207 | KeypadSeparator 208 | KeypadSubtract 209 | KeypadDecimal 210 | KeypadDivide 211 | Keypad0 212 | Keypad1 213 | Keypad2 214 | Keypad3 215 | Keypad4 216 | Keypad5 217 | Keypad6 218 | Keypad7 219 | Keypad8 220 | Keypad9 221 | KeypadEqual Key = 0xffbd 222 | ) 223 | 224 | const ( 225 | F1 Key = iota + 0xffbe 226 | F2 227 | F3 228 | F4 229 | F5 230 | F6 231 | F7 232 | F8 233 | F9 234 | F10 235 | F11 236 | F12 237 | ) 238 | 239 | const ( 240 | ShiftLeft Key = iota + 0xffe1 241 | ShiftRight 242 | ControlLeft 243 | ControlRight 244 | CapsLock 245 | ShiftLock 246 | MetaLeft 247 | MetaRight 248 | AltLeft 249 | AltRight 250 | SuperLeft 251 | SuperRight 252 | HyperLeft 253 | HyperRight 254 | ) 255 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import "fmt" 4 | 5 | var simpleLogger = SimpleLogger{LogLevelWarn} 6 | 7 | type Logger interface { 8 | Trace(v ...interface{}) 9 | Tracef(format string, v ...interface{}) 10 | Debug(v ...interface{}) 11 | Debugf(format string, v ...interface{}) 12 | Info(v ...interface{}) 13 | Infof(format string, v ...interface{}) 14 | DebugfNoCR(format string, v ...interface{}) 15 | Warn(v ...interface{}) 16 | Warnf(format string, v ...interface{}) 17 | Error(v ...interface{}) 18 | Errorf(format string, v ...interface{}) 19 | Fatal(v ...interface{}) 20 | Fatalf(format string, v ...interface{}) 21 | } 22 | type LogLevel int 23 | 24 | const ( 25 | LogLevelTrace LogLevel = iota 26 | LogLevelDebug 27 | LogLevelInfo 28 | LogLevelWarn 29 | LogLevelError 30 | LogLevelFatal 31 | ) 32 | 33 | type SimpleLogger struct { 34 | level LogLevel 35 | } 36 | 37 | func (sl *SimpleLogger) Trace(v ...interface{}) { 38 | if sl.level <= LogLevelTrace { 39 | arr := []interface{}{"[Trace]"} 40 | for _, item := range v { 41 | arr = append(arr, item) 42 | } 43 | 44 | fmt.Println(arr...) 45 | } 46 | } 47 | func (sl *SimpleLogger) Tracef(format string, v ...interface{}) { 48 | if sl.level <= LogLevelTrace { 49 | fmt.Printf("[Trace] "+format+"\n", v...) 50 | } 51 | } 52 | 53 | func (sl *SimpleLogger) Debug(v ...interface{}) { 54 | if sl.level <= LogLevelDebug { 55 | arr := []interface{}{"[Debug]"} 56 | for _, item := range v { 57 | arr = append(arr, item) 58 | } 59 | 60 | fmt.Println(arr...) 61 | } 62 | } 63 | func (sl *SimpleLogger) Debugf(format string, v ...interface{}) { 64 | if sl.level <= LogLevelDebug { 65 | fmt.Printf("[Debug] "+format+"\n", v...) 66 | } 67 | } 68 | func (sl *SimpleLogger) Info(v ...interface{}) { 69 | if sl.level <= LogLevelInfo { 70 | arr := []interface{}{"[Info ]"} 71 | for _, item := range v { 72 | arr = append(arr, item) 73 | } 74 | fmt.Println(arr...) 75 | } 76 | } 77 | func (sl *SimpleLogger) DebugfNoCR(format string, v ...interface{}) { 78 | if sl.level <= LogLevelDebug { 79 | fmt.Printf("[Info ] "+format, v...) 80 | } 81 | } 82 | 83 | func (sl *SimpleLogger) Infof(format string, v ...interface{}) { 84 | if sl.level <= LogLevelInfo { 85 | fmt.Printf("[Info ] "+format+"\n", v...) 86 | } 87 | } 88 | func (sl *SimpleLogger) Warn(v ...interface{}) { 89 | if sl.level <= LogLevelWarn { 90 | arr := []interface{}{"[Warn ]"} 91 | for _, item := range v { 92 | arr = append(arr, item) 93 | } 94 | fmt.Println(arr...) 95 | } 96 | } 97 | func (sl *SimpleLogger) Warnf(format string, v ...interface{}) { 98 | if sl.level <= LogLevelWarn { 99 | fmt.Printf("[Warn ] "+format+"\n", v...) 100 | } 101 | } 102 | func (sl *SimpleLogger) Error(v ...interface{}) { 103 | if sl.level <= LogLevelError { 104 | arr := []interface{}{"[Error]"} 105 | for _, item := range v { 106 | arr = append(arr, item) 107 | } 108 | fmt.Println(arr...) 109 | } 110 | } 111 | func (sl *SimpleLogger) Errorf(format string, v ...interface{}) { 112 | if sl.level <= LogLevelError { 113 | fmt.Printf("[Error] "+format+"\n", v...) 114 | } 115 | } 116 | func (sl *SimpleLogger) Fatal(v ...interface{}) { 117 | if sl.level <= LogLevelFatal { 118 | arr := []interface{}{"[Fatal]"} 119 | for _, item := range v { 120 | arr = append(arr, item) 121 | } 122 | fmt.Println(arr...) 123 | 124 | } 125 | } 126 | func (sl *SimpleLogger) Fatalf(format string, v ...interface{}) { 127 | if sl.level <= LogLevelFatal { 128 | fmt.Printf("[Fatal] "+format+"\n", v) 129 | } 130 | } 131 | func Trace(v ...interface{}) { 132 | simpleLogger.Trace(v...) 133 | } 134 | func Tracef(format string, v ...interface{}) { 135 | simpleLogger.Tracef(format, v...) 136 | } 137 | 138 | func Debug(v ...interface{}) { 139 | simpleLogger.Debug(v...) 140 | } 141 | func Debugf(format string, v ...interface{}) { 142 | simpleLogger.Tracef(format, v...) 143 | } 144 | 145 | func Info(v ...interface{}) { 146 | simpleLogger.Info(v...) 147 | } 148 | func Infof(format string, v ...interface{}) { 149 | simpleLogger.Infof(format, v...) 150 | } 151 | func DebugfNoCR(format string, v ...interface{}) { 152 | simpleLogger.DebugfNoCR(format, v...) 153 | } 154 | func Warn(v ...interface{}) { 155 | simpleLogger.Warn(v...) 156 | } 157 | func Warnf(format string, v ...interface{}) { 158 | simpleLogger.Warnf(format, v...) 159 | } 160 | 161 | func Error(v ...interface{}) { 162 | simpleLogger.Error(v...) 163 | } 164 | func Errorf(format string, v ...interface{}) { 165 | simpleLogger.Errorf(format, v...) 166 | } 167 | 168 | func Fatal(v ...interface{}) { 169 | simpleLogger.Fatal(v...) 170 | } 171 | func Fatalf(format string, v ...interface{}) { 172 | simpleLogger.Fatalf(format, v...) 173 | } 174 | -------------------------------------------------------------------------------- /messages_aten.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | ) 7 | 8 | // Aten IKVM server message types 9 | const ( 10 | AteniKVMFrontGroundEventMsgType ServerMessageType = 4 11 | AteniKVMKeepAliveEventMsgType ServerMessageType = 22 12 | AteniKVMVideoGetInfoMsgType ServerMessageType = 51 13 | AteniKVMMouseGetInfoMsgType ServerMessageType = 55 14 | AteniKVMSessionMessageMsgType ServerMessageType = 57 15 | AteniKVMGetViewerLangMsgType ServerMessageType = 60 16 | ) 17 | 18 | // Aten IKVM client message types 19 | const ( 20 | AteniKVMKeyEventMsgType ClientMessageType = 4 21 | AteniKVMPointerEventMsgType ClientMessageType = 5 22 | ) 23 | 24 | // AteniKVMKeyEvent holds the wire format message 25 | type AteniKVMKeyEvent struct { 26 | _ [1]byte // padding 27 | Down uint8 // down-flag 28 | _ [2]byte // padding 29 | Key Key // key 30 | _ [9]byte // padding 31 | } 32 | 33 | // AteniKVMPointerEvent holds the wire format message 34 | type AteniKVMPointerEvent struct { 35 | _ [1]byte // padding 36 | Mask uint8 // mask 37 | X uint16 // x 38 | Y uint16 // y 39 | _ [11]byte // padding 40 | } 41 | 42 | func (msg *AteniKVMPointerEvent) Supported(c Conn) bool { 43 | return false 44 | } 45 | 46 | func (msg *AteniKVMPointerEvent) String() string { 47 | return fmt.Sprintf("mask: %d, x:%d, y:%d", msg.Mask, msg.X, msg.Y) 48 | } 49 | 50 | func (msg *AteniKVMPointerEvent) Type() ClientMessageType { 51 | return AteniKVMPointerEventMsgType 52 | } 53 | 54 | func (*AteniKVMPointerEvent) Read(c Conn) (ClientMessage, error) { 55 | msg := AteniKVMPointerEvent{} 56 | if err := binary.Read(c, binary.BigEndian, &msg); err != nil { 57 | return nil, err 58 | } 59 | return &msg, nil 60 | } 61 | 62 | func (msg *AteniKVMPointerEvent) Write(c Conn) error { 63 | if !msg.Supported(c) { 64 | return nil 65 | } 66 | if err := binary.Write(c, binary.BigEndian, msg.Type()); err != nil { 67 | return err 68 | } 69 | if err := binary.Write(c, binary.BigEndian, msg); err != nil { 70 | return err 71 | } 72 | return c.Flush() 73 | } 74 | 75 | func (msg *AteniKVMKeyEvent) Supported(c Conn) bool { 76 | return false 77 | } 78 | 79 | func (msg *AteniKVMKeyEvent) String() string { 80 | return fmt.Sprintf("down:%d, key:%s", msg.Down, msg.Key) 81 | } 82 | 83 | func (msg *AteniKVMKeyEvent) Type() ClientMessageType { 84 | return AteniKVMKeyEventMsgType 85 | } 86 | 87 | func (*AteniKVMKeyEvent) Read(c Conn) (ClientMessage, error) { 88 | msg := AteniKVMKeyEvent{} 89 | if err := binary.Read(c, binary.BigEndian, &msg); err != nil { 90 | return nil, err 91 | } 92 | return &msg, nil 93 | } 94 | 95 | func (msg *AteniKVMKeyEvent) Write(c Conn) error { 96 | if !msg.Supported(c) { 97 | return nil 98 | } 99 | if err := binary.Write(c, binary.BigEndian, msg.Type()); err != nil { 100 | return err 101 | } 102 | if err := binary.Write(c, binary.BigEndian, msg); err != nil { 103 | return err 104 | } 105 | return c.Flush() 106 | } 107 | 108 | // AteniKVMFrontGroundEvent unknown aten ikvm message 109 | type AteniKVMFrontGroundEvent struct { 110 | _ [20]byte 111 | } 112 | 113 | func (msg *AteniKVMFrontGroundEvent) Supported(c Conn) bool { 114 | return false 115 | } 116 | 117 | // String return string representation 118 | func (msg *AteniKVMFrontGroundEvent) String() string { 119 | return fmt.Sprintf("%v", msg.Type()) 120 | } 121 | 122 | // Type return ServerMessageType 123 | func (*AteniKVMFrontGroundEvent) Type() ServerMessageType { 124 | return AteniKVMFrontGroundEventMsgType 125 | } 126 | 127 | // Read unmarshal message from conn 128 | func (*AteniKVMFrontGroundEvent) Read(c Conn) (ServerMessage, error) { 129 | msg := &AteniKVMFrontGroundEvent{} 130 | var pad [20]byte 131 | if err := binary.Read(c, binary.BigEndian, &pad); err != nil { 132 | return nil, err 133 | } 134 | return msg, nil 135 | } 136 | 137 | // Write marshal message to conn 138 | func (msg *AteniKVMFrontGroundEvent) Write(c Conn) error { 139 | if !msg.Supported(c) { 140 | return nil 141 | } 142 | var pad [20]byte 143 | if err := binary.Write(c, binary.BigEndian, msg.Type()); err != nil { 144 | return err 145 | } 146 | if err := binary.Write(c, binary.BigEndian, pad); err != nil { 147 | return err 148 | } 149 | return c.Flush() 150 | } 151 | 152 | // AteniKVMKeepAliveEvent unknown aten ikvm message 153 | type AteniKVMKeepAliveEvent struct { 154 | _ [1]byte 155 | } 156 | 157 | func (msg *AteniKVMKeepAliveEvent) Supported(c Conn) bool { 158 | return false 159 | } 160 | 161 | // String return string representation 162 | func (msg *AteniKVMKeepAliveEvent) String() string { 163 | return fmt.Sprintf("%v", msg.Type()) 164 | } 165 | 166 | // Type return ServerMessageType 167 | func (*AteniKVMKeepAliveEvent) Type() ServerMessageType { 168 | return AteniKVMKeepAliveEventMsgType 169 | } 170 | 171 | // Read unmarshal message from conn 172 | func (*AteniKVMKeepAliveEvent) Read(c Conn) (ServerMessage, error) { 173 | msg := &AteniKVMKeepAliveEvent{} 174 | var pad [1]byte 175 | if err := binary.Read(c, binary.BigEndian, &pad); err != nil { 176 | return nil, err 177 | } 178 | return msg, nil 179 | } 180 | 181 | // Write marshal message to conn 182 | func (msg *AteniKVMKeepAliveEvent) Write(c Conn) error { 183 | if !msg.Supported(c) { 184 | return nil 185 | } 186 | var pad [1]byte 187 | if err := binary.Write(c, binary.BigEndian, msg.Type()); err != nil { 188 | return err 189 | } 190 | if err := binary.Write(c, binary.BigEndian, pad); err != nil { 191 | return err 192 | } 193 | return c.Flush() 194 | } 195 | 196 | // AteniKVMVideoGetInfo unknown aten ikvm message 197 | type AteniKVMVideoGetInfo struct { 198 | _ [20]byte 199 | } 200 | 201 | func (msg *AteniKVMVideoGetInfo) Supported(c Conn) bool { 202 | return false 203 | } 204 | 205 | // String return string representation 206 | func (msg *AteniKVMVideoGetInfo) String() string { 207 | return fmt.Sprintf("%v", msg.Type()) 208 | } 209 | 210 | // Type return ServerMessageType 211 | func (*AteniKVMVideoGetInfo) Type() ServerMessageType { 212 | return AteniKVMVideoGetInfoMsgType 213 | } 214 | 215 | // Read unmarshal message from conn 216 | func (*AteniKVMVideoGetInfo) Read(c Conn) (ServerMessage, error) { 217 | msg := &AteniKVMVideoGetInfo{} 218 | var pad [40]byte 219 | if err := binary.Read(c, binary.BigEndian, &pad); err != nil { 220 | return nil, err 221 | } 222 | return msg, nil 223 | } 224 | 225 | // Write marshal message to conn 226 | func (msg *AteniKVMVideoGetInfo) Write(c Conn) error { 227 | if !msg.Supported(c) { 228 | return nil 229 | } 230 | var pad [4]byte 231 | if err := binary.Write(c, binary.BigEndian, msg.Type()); err != nil { 232 | return err 233 | } 234 | if err := binary.Write(c, binary.BigEndian, pad); err != nil { 235 | return err 236 | } 237 | return c.Flush() 238 | } 239 | 240 | // AteniKVMMouseGetInfo unknown aten ikvm message 241 | type AteniKVMMouseGetInfo struct { 242 | _ [2]byte 243 | } 244 | 245 | func (msg *AteniKVMMouseGetInfo) Supported(c Conn) bool { 246 | return false 247 | } 248 | 249 | // String return string representation 250 | func (msg *AteniKVMMouseGetInfo) String() string { 251 | return fmt.Sprintf("%v", msg.Type()) 252 | } 253 | 254 | // Type return ServerMessageType 255 | func (*AteniKVMMouseGetInfo) Type() ServerMessageType { 256 | return AteniKVMMouseGetInfoMsgType 257 | } 258 | 259 | // Read unmarshal message from conn 260 | func (*AteniKVMMouseGetInfo) Read(c Conn) (ServerMessage, error) { 261 | msg := &AteniKVMFrontGroundEvent{} 262 | var pad [2]byte 263 | if err := binary.Read(c, binary.BigEndian, &pad); err != nil { 264 | return nil, err 265 | } 266 | return msg, nil 267 | } 268 | 269 | // Write marshal message to conn 270 | func (msg *AteniKVMMouseGetInfo) Write(c Conn) error { 271 | if !msg.Supported(c) { 272 | return nil 273 | } 274 | var pad [2]byte 275 | if err := binary.Write(c, binary.BigEndian, msg.Type()); err != nil { 276 | return err 277 | } 278 | if err := binary.Write(c, binary.BigEndian, pad); err != nil { 279 | return err 280 | } 281 | return c.Flush() 282 | } 283 | 284 | // AteniKVMSessionMessage unknown aten ikvm message 285 | type AteniKVMSessionMessage struct { 286 | _ [264]byte 287 | } 288 | 289 | func (msg *AteniKVMSessionMessage) Supported(c Conn) bool { 290 | return false 291 | } 292 | 293 | // String return string representation 294 | func (msg *AteniKVMSessionMessage) String() string { 295 | return fmt.Sprintf("%v", msg.Type()) 296 | } 297 | 298 | // Type return ServerMessageType 299 | func (*AteniKVMSessionMessage) Type() ServerMessageType { 300 | return AteniKVMSessionMessageMsgType 301 | } 302 | 303 | // Read unmarshal message from conn 304 | func (*AteniKVMSessionMessage) Read(c Conn) (ServerMessage, error) { 305 | msg := &AteniKVMSessionMessage{} 306 | var pad [264]byte 307 | if err := binary.Read(c, binary.BigEndian, &pad); err != nil { 308 | return nil, err 309 | } 310 | return msg, nil 311 | } 312 | 313 | // Write marshal message to conn 314 | func (msg *AteniKVMSessionMessage) Write(c Conn) error { 315 | if !msg.Supported(c) { 316 | return nil 317 | } 318 | var pad [264]byte 319 | if err := binary.Write(c, binary.BigEndian, msg.Type()); err != nil { 320 | return err 321 | } 322 | if err := binary.Write(c, binary.BigEndian, pad); err != nil { 323 | return err 324 | } 325 | return nil 326 | } 327 | 328 | // AteniKVMGetViewerLang unknown aten ikvm message 329 | type AteniKVMGetViewerLang struct { 330 | _ [8]byte 331 | } 332 | 333 | func (msg *AteniKVMGetViewerLang) Supported(c Conn) bool { 334 | return false 335 | } 336 | 337 | // String return string representation 338 | func (msg *AteniKVMGetViewerLang) String() string { 339 | return fmt.Sprintf("%v", msg.Type()) 340 | } 341 | 342 | // Type return ServerMessageType 343 | func (*AteniKVMGetViewerLang) Type() ServerMessageType { 344 | return AteniKVMGetViewerLangMsgType 345 | } 346 | 347 | // Read unmarshal message from conn 348 | func (*AteniKVMGetViewerLang) Read(c Conn) (ServerMessage, error) { 349 | msg := &AteniKVMGetViewerLang{} 350 | var pad [8]byte 351 | if err := binary.Read(c, binary.BigEndian, &pad); err != nil { 352 | return nil, err 353 | } 354 | return msg, nil 355 | } 356 | 357 | // Write marshal message to conn 358 | func (msg *AteniKVMGetViewerLang) Write(c Conn) error { 359 | if !msg.Supported(c) { 360 | return nil 361 | } 362 | var pad [8]byte 363 | if err := binary.Write(c, binary.BigEndian, msg.Type()); err != nil { 364 | return err 365 | } 366 | if err := binary.Write(c, binary.BigEndian, pad); err != nil { 367 | return err 368 | } 369 | return c.Flush() 370 | } 371 | -------------------------------------------------------------------------------- /pixel_format.go: -------------------------------------------------------------------------------- 1 | // Implementation of RFC 6143 §7.4 Pixel Format Data Structure. 2 | 3 | package vnc2video 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | var ( 13 | // PixelFormat8bit returns 8 bit pixel format 14 | PixelFormat8bit = NewPixelFormat(8) 15 | // PixelFormat16bit returns 16 bit pixel format 16 | PixelFormat16bit = NewPixelFormat(16) 17 | // PixelFormat32bit returns 32 bit pixel format 18 | PixelFormat32bit = NewPixelFormat(32) 19 | // PixelFormatAten returns pixel format used in Aten IKVM 20 | PixelFormatAten = NewPixelFormatAten() 21 | ) 22 | 23 | // PixelFormat describes the way a pixel is formatted for a VNC connection 24 | type PixelFormat struct { 25 | BPP uint8 // bits-per-pixel 26 | Depth uint8 // depth 27 | BigEndian uint8 // big-endian-flag 28 | TrueColor uint8 // true-color-flag 29 | RedMax, GreenMax, BlueMax uint16 // red-, green-, blue-max (2^BPP-1) 30 | RedShift, GreenShift, BlueShift uint8 // red-, green-, blue-shift 31 | _ [3]byte // padding 32 | } 33 | 34 | const pixelFormatLen = 16 35 | 36 | // NewPixelFormat returns a populated PixelFormat structure 37 | func NewPixelFormat(bpp uint8) PixelFormat { 38 | bigEndian := uint8(0) 39 | // rgbMax := uint16(math.Exp2(float64(bpp))) - 1 40 | rMax := uint16(255) 41 | gMax := uint16(255) 42 | bMax := uint16(255) 43 | var ( 44 | tc = uint8(1) 45 | rs, gs, bs uint8 46 | depth uint8 47 | ) 48 | switch bpp { 49 | case 8: 50 | tc = 0 51 | depth = 8 52 | rs, gs, bs = 0, 0, 0 53 | case 16: 54 | depth = 16 55 | rs, gs, bs = 0, 4, 8 56 | case 32: 57 | depth = 24 58 | // rs, gs, bs = 0, 8, 16 59 | rs, gs, bs = 16, 8, 0 60 | } 61 | return PixelFormat{bpp, depth, bigEndian, tc, rMax, gMax, bMax, rs, gs, bs, [3]byte{}} 62 | } 63 | 64 | // NewPixelFormatAten returns Aten IKVM pixel format 65 | func NewPixelFormatAten() PixelFormat { 66 | return PixelFormat{16, 15, 0, 1, (1 << 5) - 1, (1 << 5) - 1, (1 << 5) - 1, 10, 5, 0, [3]byte{}} 67 | } 68 | 69 | // Marshal implements the Marshaler interface 70 | func (pf PixelFormat) Marshal() ([]byte, error) { 71 | // Validation checks. 72 | switch pf.BPP { 73 | case 8, 16, 32: 74 | default: 75 | return nil, fmt.Errorf("Invalid BPP value %v; must be 8, 16, or 32", pf.BPP) 76 | } 77 | 78 | if pf.Depth < pf.BPP { 79 | return nil, fmt.Errorf("Invalid Depth value %v; cannot be < BPP", pf.Depth) 80 | } 81 | switch pf.Depth { 82 | case 8, 16, 32: 83 | default: 84 | return nil, fmt.Errorf("Invalid Depth value %v; must be 8, 16, or 32", pf.Depth) 85 | } 86 | 87 | // Create the slice of bytes 88 | buf := bPool.Get().(*bytes.Buffer) 89 | buf.Reset() 90 | defer bPool.Put(buf) 91 | 92 | if err := binary.Write(buf, binary.BigEndian, &pf); err != nil { 93 | return nil, err 94 | } 95 | 96 | return buf.Bytes(), nil 97 | } 98 | 99 | // Read reads from an io.Reader, and populates the PixelFormat 100 | func (pf PixelFormat) Read(r io.Reader) error { 101 | buf := make([]byte, pixelFormatLen) 102 | if _, err := io.ReadAtLeast(r, buf, pixelFormatLen); err != nil { 103 | return err 104 | } 105 | return pf.Unmarshal(buf) 106 | } 107 | 108 | // Unmarshal implements the Unmarshaler interface 109 | func (pf PixelFormat) Unmarshal(data []byte) error { 110 | buf := bPool.Get().(*bytes.Buffer) 111 | buf.Reset() 112 | defer bPool.Put(buf) 113 | 114 | if _, err := buf.Write(data); err != nil { 115 | return err 116 | } 117 | 118 | if err := binary.Read(buf, binary.BigEndian, &pf); err != nil { 119 | return err 120 | } 121 | 122 | return nil 123 | } 124 | 125 | // String implements the fmt.Stringer interface 126 | func (pf PixelFormat) String() string { 127 | 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 }", 128 | pf.BPP, pf.Depth, pf.BigEndian, pf.TrueColor, pf.RedMax, pf.GreenMax, pf.BlueMax, pf.RedShift, pf.GreenShift, pf.BlueShift) 129 | } 130 | 131 | func (pf PixelFormat) order() binary.ByteOrder { 132 | if pf.BigEndian == 1 { 133 | return binary.BigEndian 134 | } 135 | return binary.LittleEndian 136 | } 137 | -------------------------------------------------------------------------------- /rgb-image.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | ) 7 | 8 | // RGBA is an in-memory image whose At method returns color.RGBA values. 9 | type RGBImage struct { 10 | // Pix holds the image's pixels, in R, G, B, A order. The pixel at 11 | // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3]. 12 | Pix []uint8 13 | // Stride is the Pix stride (in bytes) between vertically adjacent pixels. 14 | Stride int 15 | // Rect is the image's bounds. 16 | Rect image.Rectangle 17 | } 18 | 19 | type RGBColor struct { 20 | R, G, B uint8 21 | } 22 | 23 | func (c RGBColor) RGBA() (r, g, b, a uint32) { 24 | return uint32(c.R), uint32(c.G), uint32(c.B), 1 25 | } 26 | 27 | func (p *RGBImage) ColorModel() color.Model { return nil } 28 | 29 | func (p *RGBImage) Bounds() image.Rectangle { return p.Rect } 30 | 31 | func (p *RGBImage) At(x, y int) color.Color { 32 | col := p.RGBAt(x, y) 33 | return color.RGBA{col.R, col.G, col.B, 1} 34 | } 35 | 36 | func (p *RGBImage) RGBAt(x, y int) *RGBColor { 37 | if !(image.Point{x, y}.In(p.Rect)) { 38 | return &RGBColor{} 39 | } 40 | i := p.PixOffset(x, y) 41 | return &RGBColor{p.Pix[i+0], p.Pix[i+1], p.Pix[i+2]} 42 | } 43 | 44 | // PixOffset returns the index of the first element of Pix that corresponds to 45 | // the pixel at (x, y). 46 | func (p *RGBImage) PixOffset(x, y int) int { 47 | return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3 48 | } 49 | 50 | func (p *RGBImage) Set(x, y int, c color.Color) { 51 | if !(image.Point{x, y}.In(p.Rect)) { 52 | return 53 | } 54 | i := p.PixOffset(x, y) 55 | c1 := color.RGBAModel.Convert(c).(color.RGBA) 56 | p.Pix[i+0] = c1.R 57 | p.Pix[i+1] = c1.G 58 | p.Pix[i+2] = c1.B 59 | } 60 | 61 | func (p *RGBImage) SetRGB(x, y int, c color.RGBA) { 62 | if !(image.Point{x, y}.In(p.Rect)) { 63 | return 64 | } 65 | i := p.PixOffset(x, y) 66 | p.Pix[i+0] = c.R 67 | p.Pix[i+1] = c.G 68 | p.Pix[i+2] = c.B 69 | } 70 | 71 | // SubImage returns an image representing the portion of the image p visible 72 | // through r. The returned value shares pixels with the original image. 73 | func (p *RGBImage) SubImage(r image.Rectangle) image.Image { 74 | r = r.Intersect(p.Rect) 75 | // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside 76 | // either r1 or r2 if the intersection is empty. Without explicitly checking for 77 | // this, the Pix[i:] expression below can panic. 78 | if r.Empty() { 79 | return &RGBImage{} 80 | } 81 | i := p.PixOffset(r.Min.X, r.Min.Y) 82 | return &RGBImage{ 83 | Pix: p.Pix[i:], 84 | Stride: p.Stride, 85 | Rect: r, 86 | } 87 | } 88 | 89 | // Opaque scans the entire image and reports whether it is fully opaque. 90 | func (p *RGBImage) Opaque() bool { 91 | if p.Rect.Empty() { 92 | return true 93 | } 94 | i0, i1 := 3, p.Rect.Dx()*3 95 | for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { 96 | for i := i0; i < i1; i += 3 { 97 | if p.Pix[i] != 0xff { 98 | return false 99 | } 100 | } 101 | i0 += p.Stride 102 | i1 += p.Stride 103 | } 104 | return true 105 | } 106 | 107 | // NewRGBA returns a new RGBA image with the given bounds. 108 | func NewRGBImage(r image.Rectangle) *RGBImage { 109 | w, h := r.Dx(), r.Dy() 110 | buf := make([]uint8, 3*w*h) 111 | return &RGBImage{buf, 3 * w, r} 112 | } 113 | -------------------------------------------------------------------------------- /security.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | type SecurityType uint8 4 | 5 | //go:generate stringer -type=SecurityType 6 | 7 | const ( 8 | SecTypeUnknown SecurityType = SecurityType(0) 9 | SecTypeNone SecurityType = SecurityType(1) 10 | SecTypeVNC SecurityType = SecurityType(2) 11 | SecTypeTight SecurityType = SecurityType(16) 12 | SecTypeATEN SecurityType = SecurityType(16) 13 | SecTypeVeNCrypt SecurityType = SecurityType(19) 14 | ) 15 | 16 | type SecuritySubType uint32 17 | 18 | //go:generate stringer -type=SecuritySubType 19 | 20 | const ( 21 | SecSubTypeUnknown SecuritySubType = SecuritySubType(0) 22 | ) 23 | 24 | const ( 25 | SecSubTypeVeNCrypt01Unknown SecuritySubType = SecuritySubType(0) 26 | SecSubTypeVeNCrypt01Plain SecuritySubType = SecuritySubType(19) 27 | SecSubTypeVeNCrypt01TLSNone SecuritySubType = SecuritySubType(20) 28 | SecSubTypeVeNCrypt01TLSVNC SecuritySubType = SecuritySubType(21) 29 | SecSubTypeVeNCrypt01TLSPlain SecuritySubType = SecuritySubType(22) 30 | SecSubTypeVeNCrypt01X509None SecuritySubType = SecuritySubType(23) 31 | SecSubTypeVeNCrypt01X509VNC SecuritySubType = SecuritySubType(24) 32 | SecSubTypeVeNCrypt01X509Plain SecuritySubType = SecuritySubType(25) 33 | ) 34 | 35 | const ( 36 | SecSubTypeVeNCrypt02Unknown SecuritySubType = SecuritySubType(0) 37 | SecSubTypeVeNCrypt02Plain SecuritySubType = SecuritySubType(256) 38 | SecSubTypeVeNCrypt02TLSNone SecuritySubType = SecuritySubType(257) 39 | SecSubTypeVeNCrypt02TLSVNC SecuritySubType = SecuritySubType(258) 40 | SecSubTypeVeNCrypt02TLSPlain SecuritySubType = SecuritySubType(259) 41 | SecSubTypeVeNCrypt02X509None SecuritySubType = SecuritySubType(260) 42 | SecSubTypeVeNCrypt02X509VNC SecuritySubType = SecuritySubType(261) 43 | SecSubTypeVeNCrypt02X509Plain SecuritySubType = SecuritySubType(262) 44 | ) 45 | 46 | type SecurityHandler interface { 47 | Type() SecurityType 48 | SubType() SecuritySubType 49 | Auth(Conn) error 50 | } 51 | -------------------------------------------------------------------------------- /security_aten.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | type ClientAuthATEN struct { 10 | Username []byte 11 | Password []byte 12 | } 13 | 14 | func (*ClientAuthATEN) Type() SecurityType { 15 | return SecTypeATEN 16 | } 17 | 18 | func (*ClientAuthATEN) SubType() SecuritySubType { 19 | return SecSubTypeUnknown 20 | } 21 | 22 | func charCodeAt(s string, n int) rune { 23 | for i, r := range s { 24 | if i == n { 25 | return r 26 | } 27 | } 28 | return 0 29 | } 30 | 31 | func (auth *ClientAuthATEN) Auth(c Conn) error { 32 | var definedAuthLen = 24 33 | 34 | if len(auth.Username) > definedAuthLen || len(auth.Password) > definedAuthLen { 35 | return fmt.Errorf("username/password is too long, allowed 0-23") 36 | } 37 | 38 | nt, err := readTightTunnels(c) 39 | if err != nil { 40 | return err 41 | } 42 | /* 43 | fmt.Printf("tunnels %d\n", nt) 44 | for i := uint32(0); i < nt; i++ { 45 | code, vendor, signature, err := readTightCaps(c) 46 | if err != nil { 47 | return err 48 | } 49 | fmt.Printf("code %d vendor %s signature %s\n", code, vendor, signature) 50 | } 51 | */ 52 | if ((nt&0xffff0ff0)>>0 == 0xaff90fb0) || (nt <= 0 || nt > 0x1000000) { 53 | c.SetProtoVersion("aten1") 54 | var skip [20]byte 55 | binary.Read(c, binary.BigEndian, &skip) 56 | //fmt.Printf("skip %v\n", skip) 57 | } 58 | 59 | username := make([]byte, definedAuthLen) 60 | password := make([]byte, definedAuthLen) 61 | copy(username, auth.Username) 62 | copy(password, auth.Password) 63 | challenge := bytes.Join([][]byte{username, password}, []byte("")) 64 | if err := binary.Write(c, binary.BigEndian, challenge); err != nil { 65 | return err 66 | } 67 | 68 | if err := c.Flush(); err != nil { 69 | return err 70 | } 71 | /* 72 | 73 | sendUsername := make([]byte, definedAuthLen) 74 | for i := 0; i < definedAuthLen; i++ { 75 | if i < len(auth.Username) { 76 | sendUsername[i] = byte(charCodeAt(string(auth.Username), i)) 77 | } else { 78 | sendUsername[i] = 0 79 | } 80 | } 81 | 82 | sendPassword := make([]byte, definedAuthLen) 83 | 84 | for i := 0; i < definedAuthLen; i++ { 85 | if i < len(auth.Password) { 86 | sendPassword[i] = byte(charCodeAt(string(auth.Password), i)) 87 | } else { 88 | sendPassword[i] = 0 89 | } 90 | } 91 | 92 | if err := binary.Write(c, binary.BigEndian, sendUsername); err != nil { 93 | return err 94 | } 95 | if err := binary.Write(c, binary.BigEndian, sendPassword); err != nil { 96 | return err 97 | } 98 | 99 | if err := c.Flush(); err != nil { 100 | return err 101 | } 102 | */ 103 | //var pp [10]byte 104 | //binary.Read(c, binary.BigEndian, &pp) 105 | //fmt.Printf("ddd %v\n", pp) 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /security_none.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | type ClientAuthNone struct{} 4 | 5 | func (*ClientAuthNone) Type() SecurityType { 6 | return SecTypeNone 7 | } 8 | 9 | func (*ClientAuthNone) SubType() SecuritySubType { 10 | return SecSubTypeUnknown 11 | } 12 | 13 | func (*ClientAuthNone) Auth(conn Conn) error { 14 | return nil 15 | } 16 | 17 | // ServerAuthNone is the "none" authentication. See 7.2.1. 18 | type ServerAuthNone struct{} 19 | 20 | func (*ServerAuthNone) Type() SecurityType { 21 | return SecTypeNone 22 | } 23 | 24 | func (*ServerAuthNone) SubType() SecuritySubType { 25 | return SecSubTypeUnknown 26 | } 27 | 28 | func (*ServerAuthNone) Auth(c Conn) error { 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /security_tight.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import "encoding/binary" 4 | 5 | func readTightTunnels(c Conn) (uint32, error) { 6 | var n uint32 7 | if err := binary.Read(c, binary.BigEndian, &n); err != nil { 8 | return 0, err 9 | } 10 | return n, nil 11 | } 12 | 13 | func readTightCaps(c Conn) (int32, []byte, []byte, error) { 14 | var code int32 15 | var vendor [4]byte 16 | var signature [8]byte 17 | if err := binary.Read(c, binary.BigEndian, &code); err != nil { 18 | return 0, nil, nil, err 19 | } 20 | if err := binary.Read(c, binary.BigEndian, &vendor); err != nil { 21 | return 0, nil, nil, err 22 | } 23 | if err := binary.Read(c, binary.BigEndian, &signature); err != nil { 24 | return 0, nil, nil, err 25 | } 26 | return code, vendor[:], signature[:], nil 27 | } 28 | -------------------------------------------------------------------------------- /security_vencryptplain.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | func (*ClientAuthVeNCrypt02Plain) Type() SecurityType { 10 | return SecTypeVeNCrypt 11 | } 12 | 13 | func (*ClientAuthVeNCrypt02Plain) SubType() SecuritySubType { 14 | return SecSubTypeVeNCrypt02Plain 15 | } 16 | 17 | // ClientAuthVeNCryptPlain see https://www.berrange.com/~dan/vencrypt.txt 18 | type ClientAuthVeNCrypt02Plain struct { 19 | Username []byte 20 | Password []byte 21 | } 22 | 23 | func (auth *ClientAuthVeNCrypt02Plain) Auth(c Conn) error { 24 | if err := binary.Write(c, binary.BigEndian, []uint8{0, 2}); err != nil { 25 | return err 26 | } 27 | if err := c.Flush(); err != nil { 28 | return err 29 | } 30 | var ( 31 | major, minor uint8 32 | ) 33 | 34 | if err := binary.Read(c, binary.BigEndian, &major); err != nil { 35 | return err 36 | } 37 | if err := binary.Read(c, binary.BigEndian, &minor); err != nil { 38 | return err 39 | } 40 | res := uint8(1) 41 | if major == 0 && minor == 2 { 42 | res = uint8(0) 43 | } 44 | if err := binary.Write(c, binary.BigEndian, res); err != nil { 45 | return err 46 | } 47 | c.Flush() 48 | if err := binary.Write(c, binary.BigEndian, uint8(1)); err != nil { 49 | return err 50 | } 51 | if err := binary.Write(c, binary.BigEndian, auth.SubType()); err != nil { 52 | return err 53 | } 54 | if err := c.Flush(); err != nil { 55 | return err 56 | } 57 | var secType SecuritySubType 58 | if err := binary.Read(c, binary.BigEndian, &secType); err != nil { 59 | return err 60 | } 61 | if secType != auth.SubType() { 62 | binary.Write(c, binary.BigEndian, uint8(1)) 63 | c.Flush() 64 | return fmt.Errorf("invalid sectype") 65 | } 66 | if len(auth.Password) == 0 || len(auth.Username) == 0 { 67 | return fmt.Errorf("Security Handshake failed; no username and/or password provided for VeNCryptAuth.") 68 | } 69 | /* 70 | if err := binary.Write(c, binary.BigEndian, uint32(len(auth.Username))); err != nil { 71 | return err 72 | } 73 | 74 | if err := binary.Write(c, binary.BigEndian, uint32(len(auth.Password))); err != nil { 75 | return err 76 | } 77 | 78 | if err := binary.Write(c, binary.BigEndian, auth.Username); err != nil { 79 | return err 80 | } 81 | 82 | if err := binary.Write(c, binary.BigEndian, auth.Password); err != nil { 83 | return err 84 | } 85 | */ 86 | var ( 87 | uLength, pLength uint32 88 | ) 89 | if err := binary.Read(c, binary.BigEndian, &uLength); err != nil { 90 | return err 91 | } 92 | if err := binary.Read(c, binary.BigEndian, &pLength); err != nil { 93 | return err 94 | } 95 | 96 | username := make([]byte, uLength) 97 | password := make([]byte, pLength) 98 | if err := binary.Read(c, binary.BigEndian, &username); err != nil { 99 | return err 100 | } 101 | 102 | if err := binary.Read(c, binary.BigEndian, &password); err != nil { 103 | return err 104 | } 105 | if !bytes.Equal(auth.Username, username) || !bytes.Equal(auth.Password, password) { 106 | return fmt.Errorf("invalid username/password") 107 | } 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /security_vnc.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bytes" 5 | "crypto/des" 6 | "encoding/binary" 7 | "fmt" 8 | ) 9 | 10 | // ServerAuthVNC is the standard password authentication. See 7.2.2. 11 | type ServerAuthVNC struct { 12 | Challenge []byte 13 | Password []byte 14 | Crypted []byte 15 | } 16 | 17 | func (*ServerAuthVNC) Type() SecurityType { 18 | return SecTypeVNC 19 | } 20 | func (*ServerAuthVNC) SubType() SecuritySubType { 21 | return SecSubTypeUnknown 22 | } 23 | 24 | func (auth *ServerAuthVNC) WriteChallenge(c Conn) error { 25 | if err := binary.Write(c, binary.BigEndian, auth.Challenge); err != nil { 26 | return err 27 | } 28 | return c.Flush() 29 | } 30 | 31 | func (auth *ServerAuthVNC) ReadChallenge(c Conn) error { 32 | var crypted [16]byte 33 | if err := binary.Read(c, binary.BigEndian, &crypted); err != nil { 34 | return err 35 | } 36 | auth.Crypted = crypted[:] 37 | return nil 38 | } 39 | 40 | func (auth *ServerAuthVNC) Auth(c Conn) error { 41 | if err := auth.WriteChallenge(c); err != nil { 42 | return err 43 | } 44 | 45 | if err := auth.ReadChallenge(c); err != nil { 46 | return err 47 | } 48 | 49 | encrypted, err := AuthVNCEncode(auth.Password, auth.Challenge) 50 | if err != nil { 51 | return err 52 | } 53 | if !bytes.Equal(encrypted, auth.Crypted) { 54 | return fmt.Errorf("password invalid") 55 | } 56 | return nil 57 | } 58 | 59 | // ClientAuthVNC is the standard password authentication. See 7.2.2. 60 | type ClientAuthVNC struct { 61 | Challenge []byte 62 | Password []byte 63 | } 64 | 65 | func (*ClientAuthVNC) Type() SecurityType { 66 | return SecTypeVNC 67 | } 68 | func (*ClientAuthVNC) SubType() SecuritySubType { 69 | return SecSubTypeUnknown 70 | } 71 | 72 | func (auth *ClientAuthVNC) Auth(c Conn) error { 73 | if len(auth.Password) == 0 { 74 | return fmt.Errorf("Security Handshake failed; no password provided for VNCAuth.") 75 | } 76 | 77 | var challenge [16]byte 78 | if err := binary.Read(c, binary.BigEndian, &challenge); err != nil { 79 | return err 80 | } 81 | 82 | encrypted, err := AuthVNCEncode(auth.Password, challenge[:]) 83 | if err != nil { 84 | return err 85 | } 86 | // Send the encrypted challenge back to server 87 | if err := binary.Write(c, binary.BigEndian, encrypted); err != nil { 88 | return err 89 | } 90 | 91 | return c.Flush() 92 | } 93 | 94 | func AuthVNCEncode(password []byte, challenge []byte) ([]byte, error) { 95 | if len(challenge) != 16 { 96 | return nil, fmt.Errorf("challenge size not 16 byte long") 97 | } 98 | // Copy password string to 8 byte 0-padded slice 99 | key := make([]byte, 8) 100 | copy(key, password) 101 | 102 | // Each byte of the password needs to be reversed. This is a 103 | // non RFC-documented behaviour of VNC clients and servers 104 | for i := range key { 105 | key[i] = (key[i]&0x55)<<1 | (key[i]&0xAA)>>1 // Swap adjacent bits 106 | key[i] = (key[i]&0x33)<<2 | (key[i]&0xCC)>>2 // Swap adjacent pairs 107 | key[i] = (key[i]&0x0F)<<4 | (key[i]&0xF0)>>4 // Swap the 2 halves 108 | } 109 | 110 | // Encrypt challenge with key. 111 | cipher, err := des.NewCipher(key) 112 | if err != nil { 113 | return nil, err 114 | } 115 | for i := 0; i < len(challenge); i += cipher.BlockSize() { 116 | cipher.Encrypt(challenge[i:i+cipher.BlockSize()], challenge[i:i+cipher.BlockSize()]) 117 | } 118 | 119 | return challenge, nil 120 | } 121 | -------------------------------------------------------------------------------- /securitysubtype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=SecuritySubType"; DO NOT EDIT. 2 | 3 | package vnc2video 4 | 5 | import "fmt" 6 | 7 | const ( 8 | _SecuritySubType_name_0 = "SecSubTypeUnknown" 9 | _SecuritySubType_name_1 = "SecSubTypeVeNCrypt01PlainSecSubTypeVeNCrypt01TLSNoneSecSubTypeVeNCrypt01TLSVNCSecSubTypeVeNCrypt01TLSPlainSecSubTypeVeNCrypt01X509NoneSecSubTypeVeNCrypt01X509VNCSecSubTypeVeNCrypt01X509Plain" 10 | _SecuritySubType_name_2 = "SecSubTypeVeNCrypt02PlainSecSubTypeVeNCrypt02TLSNoneSecSubTypeVeNCrypt02TLSVNCSecSubTypeVeNCrypt02TLSPlainSecSubTypeVeNCrypt02X509NoneSecSubTypeVeNCrypt02X509VNCSecSubTypeVeNCrypt02X509Plain" 11 | ) 12 | 13 | var ( 14 | _SecuritySubType_index_0 = [...]uint8{0, 17} 15 | _SecuritySubType_index_1 = [...]uint8{0, 25, 52, 78, 106, 134, 161, 190} 16 | _SecuritySubType_index_2 = [...]uint8{0, 25, 52, 78, 106, 134, 161, 190} 17 | ) 18 | 19 | func (i SecuritySubType) String() string { 20 | switch { 21 | case i == 0: 22 | return _SecuritySubType_name_0 23 | case 19 <= i && i <= 25: 24 | i -= 19 25 | return _SecuritySubType_name_1[_SecuritySubType_index_1[i]:_SecuritySubType_index_1[i+1]] 26 | case 256 <= i && i <= 262: 27 | i -= 256 28 | return _SecuritySubType_name_2[_SecuritySubType_index_2[i]:_SecuritySubType_index_2[i+1]] 29 | default: 30 | return fmt.Sprintf("SecuritySubType(%d)", i) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /securitytype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=SecurityType"; DO NOT EDIT. 2 | 3 | package vnc2video 4 | 5 | import "fmt" 6 | 7 | const ( 8 | _SecurityType_name_0 = "SecTypeUnknownSecTypeNoneSecTypeVNC" 9 | _SecurityType_name_1 = "SecTypeTight" 10 | _SecurityType_name_2 = "SecTypeVeNCrypt" 11 | ) 12 | 13 | var ( 14 | _SecurityType_index_0 = [...]uint8{0, 14, 25, 35} 15 | _SecurityType_index_1 = [...]uint8{0, 12} 16 | _SecurityType_index_2 = [...]uint8{0, 15} 17 | ) 18 | 19 | func (i SecurityType) String() string { 20 | switch { 21 | case 0 <= i && i <= 2: 22 | return _SecurityType_name_0[_SecurityType_index_0[i]:_SecurityType_index_0[i+1]] 23 | case i == 16: 24 | return _SecurityType_name_1 25 | case i == 19: 26 | return _SecurityType_name_2 27 | default: 28 | return fmt.Sprintf("SecurityType(%d)", i) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package vnc2video 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "encoding/binary" 7 | "fmt" 8 | "net" 9 | "sync" 10 | ) 11 | 12 | var _ Conn = (*ServerConn)(nil) 13 | 14 | // Config returns config for server conn 15 | func (c *ServerConn) Config() interface{} { 16 | return c.cfg 17 | } 18 | func (c *ServerConn) GetEncInstance(typ EncodingType) Encoding { 19 | for _, enc := range c.encodings { 20 | if enc.Type() == typ { 21 | return enc 22 | } 23 | } 24 | return nil 25 | } 26 | 27 | // Conn returns underlining server net.Conn 28 | func (c *ServerConn) Conn() net.Conn { 29 | return c.c 30 | } 31 | 32 | // Wait waits connection to close 33 | func (c *ServerConn) Wait() { 34 | <-c.quit 35 | } 36 | 37 | // SetEncodings ??? sets server connection encodings 38 | func (c *ServerConn) SetEncodings(encs []EncodingType) error { 39 | encodings := make(map[EncodingType]Encoding) 40 | for _, enc := range c.cfg.Encodings { 41 | encodings[enc.Type()] = enc 42 | } 43 | for _, encType := range encs { 44 | if enc, ok := encodings[encType]; ok { 45 | c.encodings = append(c.encodings, enc) 46 | } 47 | } 48 | return nil 49 | } 50 | 51 | // SetProtoVersion ??? sets proto version 52 | func (c *ServerConn) SetProtoVersion(pv string) { 53 | c.protocol = pv 54 | } 55 | 56 | // Flush buffered data to server conn 57 | func (c *ServerConn) Flush() error { 58 | return c.bw.Flush() 59 | } 60 | 61 | // Close closing server conn 62 | func (c *ServerConn) Close() error { 63 | if c.quit != nil { 64 | close(c.quit) 65 | c.quit = nil 66 | } 67 | return c.c.Close() 68 | } 69 | 70 | // Read reads data from net.Conn 71 | func (c *ServerConn) Read(buf []byte) (int, error) { 72 | return c.br.Read(buf) 73 | } 74 | 75 | // Write writes data to net.Conn, must be Flashed 76 | func (c *ServerConn) Write(buf []byte) (int, error) { 77 | return c.bw.Write(buf) 78 | } 79 | 80 | // ColorMap returns server connection color map 81 | func (c *ServerConn) ColorMap() ColorMap { 82 | return c.colorMap 83 | } 84 | 85 | // SetColorMap sets connection color map 86 | func (c *ServerConn) SetColorMap(cm ColorMap) { 87 | c.colorMap = cm 88 | } 89 | 90 | // DesktopName returns connection desktop name 91 | func (c *ServerConn) DesktopName() []byte { 92 | return c.desktopName 93 | } 94 | 95 | // PixelFormat return connection pixel format 96 | func (c *ServerConn) PixelFormat() PixelFormat { 97 | return c.pixelFormat 98 | } 99 | 100 | // SetDesktopName sets connection desktop name 101 | func (c *ServerConn) SetDesktopName(name []byte) { 102 | c.desktopName = name 103 | } 104 | 105 | // SetPixelFormat sets pixel format for server conn 106 | func (c *ServerConn) SetPixelFormat(pf PixelFormat) error { 107 | c.pixelFormat = pf 108 | return nil 109 | } 110 | 111 | // Encodings returns connection encodings 112 | func (c *ServerConn) Encodings() []Encoding { 113 | return c.encodings 114 | } 115 | 116 | // Width returns framebuffer width 117 | func (c *ServerConn) Width() uint16 { 118 | return c.fbWidth 119 | } 120 | 121 | // Height returns framebuffer height 122 | func (c *ServerConn) Height() uint16 { 123 | return c.fbHeight 124 | } 125 | 126 | // Protocol returns protocol 127 | func (c *ServerConn) Protocol() string { 128 | return c.protocol 129 | } 130 | 131 | // SecurityHandler returns security handler 132 | func (c *ServerConn) SecurityHandler() SecurityHandler { 133 | return c.securityHandler 134 | } 135 | 136 | // SetSecurityHandler sets security handler 137 | func (c *ServerConn) SetSecurityHandler(sechandler SecurityHandler) error { 138 | c.securityHandler = sechandler 139 | return nil 140 | } 141 | 142 | // SetWidth sets framebuffer width 143 | func (c *ServerConn) SetWidth(w uint16) { 144 | // TODO send desktopsize pseudo encoding 145 | c.fbWidth = w 146 | } 147 | 148 | // SetHeight sets framebuffer height 149 | func (c *ServerConn) SetHeight(h uint16) { 150 | // TODO send desktopsize pseudo encoding 151 | c.fbHeight = h 152 | } 153 | 154 | // ServerConn underlining server conn 155 | type ServerConn struct { 156 | c net.Conn 157 | cfg *ServerConfig 158 | br *bufio.Reader 159 | bw *bufio.Writer 160 | protocol string 161 | // If the pixel format uses a color map, then this is the color 162 | // map that is used. This should not be modified directly, since 163 | // the data comes from the server. 164 | // Definition in §5 - Representation of Pixel Data. 165 | colorMap ColorMap 166 | 167 | // Name associated with the desktop, sent from the server. 168 | desktopName []byte 169 | 170 | // Encodings supported by the client. This should not be modified 171 | // directly. Instead, SetEncodings() should be used. 172 | encodings []Encoding 173 | 174 | securityHandler SecurityHandler 175 | 176 | // Height of the frame buffer in pixels, sent to the client. 177 | fbHeight uint16 178 | 179 | // Width of the frame buffer in pixels, sent to the client. 180 | fbWidth uint16 181 | 182 | // The pixel format associated with the connection. This shouldn't 183 | // be modified. If you wish to set a new pixel format, use the 184 | // SetPixelFormat method. 185 | pixelFormat PixelFormat 186 | 187 | quit chan struct{} 188 | } 189 | 190 | var ( 191 | // DefaultServerHandlers uses default handlers for hanshake 192 | DefaultServerHandlers = []Handler{ 193 | &DefaultServerVersionHandler{}, 194 | &DefaultServerSecurityHandler{}, 195 | &DefaultServerClientInitHandler{}, 196 | &DefaultServerServerInitHandler{}, 197 | &DefaultServerMessageHandler{}, 198 | } 199 | ) 200 | 201 | // ServerConfig config struct 202 | type ServerConfig struct { 203 | Handlers []Handler 204 | SecurityHandlers []SecurityHandler 205 | Encodings []Encoding 206 | PixelFormat PixelFormat 207 | ColorMap ColorMap 208 | ClientMessageCh chan ClientMessage 209 | ServerMessageCh chan ServerMessage 210 | Messages []ClientMessage 211 | DesktopName []byte 212 | Height uint16 213 | Width uint16 214 | ErrorCh chan error 215 | } 216 | 217 | // NewServerConn returns new Server connection fron net.Conn 218 | func NewServerConn(c net.Conn, cfg *ServerConfig) (*ServerConn, error) { 219 | return &ServerConn{ 220 | c: c, 221 | br: bufio.NewReader(c), 222 | bw: bufio.NewWriter(c), 223 | cfg: cfg, 224 | desktopName: cfg.DesktopName, 225 | encodings: cfg.Encodings, 226 | pixelFormat: cfg.PixelFormat, 227 | fbWidth: cfg.Width, 228 | fbHeight: cfg.Height, 229 | quit: make(chan struct{}), 230 | }, nil 231 | } 232 | 233 | // Serve serves requests from net.Listener using ServerConfig 234 | func Serve(ctx context.Context, ln net.Listener, cfg *ServerConfig) error { 235 | for { 236 | 237 | c, err := ln.Accept() 238 | if err != nil { 239 | continue 240 | } 241 | 242 | conn, err := NewServerConn(c, cfg) 243 | if err != nil { 244 | cfg.ErrorCh <- err 245 | continue 246 | } 247 | 248 | if len(cfg.Handlers) == 0 { 249 | cfg.Handlers = DefaultServerHandlers 250 | } 251 | 252 | handlerLoop: 253 | for _, h := range cfg.Handlers { 254 | if err := h.Handle(conn); err != nil { 255 | if cfg.ErrorCh != nil { 256 | cfg.ErrorCh <- err 257 | } 258 | conn.Close() 259 | break handlerLoop 260 | } 261 | } 262 | } 263 | } 264 | 265 | // DefaultServerMessageHandler default package handler 266 | type DefaultServerMessageHandler struct{} 267 | 268 | // Handle handles messages from clients 269 | func (*DefaultServerMessageHandler) Handle(c Conn) error { 270 | cfg := c.Config().(*ServerConfig) 271 | var err error 272 | var wg sync.WaitGroup 273 | 274 | defer c.Close() 275 | clientMessages := make(map[ClientMessageType]ClientMessage) 276 | for _, m := range cfg.Messages { 277 | clientMessages[m.Type()] = m 278 | } 279 | wg.Add(2) 280 | 281 | quit := make(chan struct{}) 282 | 283 | // server 284 | go func() { 285 | defer wg.Done() 286 | for { 287 | select { 288 | case <-quit: 289 | return 290 | case msg := <-cfg.ServerMessageCh: 291 | if err = msg.Write(c); err != nil { 292 | cfg.ErrorCh <- err 293 | if quit != nil { 294 | close(quit) 295 | quit = nil 296 | } 297 | return 298 | } 299 | } 300 | } 301 | }() 302 | 303 | // client 304 | go func() { 305 | defer wg.Done() 306 | for { 307 | select { 308 | case <-quit: 309 | return 310 | default: 311 | var messageType ClientMessageType 312 | if err := binary.Read(c, binary.BigEndian, &messageType); err != nil { 313 | cfg.ErrorCh <- err 314 | if quit != nil { 315 | close(quit) 316 | quit = nil 317 | } 318 | return 319 | } 320 | msg, ok := clientMessages[messageType] 321 | if !ok { 322 | cfg.ErrorCh <- fmt.Errorf("unsupported message-type: %v", messageType) 323 | close(quit) 324 | return 325 | } 326 | parsedMsg, err := msg.Read(c) 327 | if err != nil { 328 | cfg.ErrorCh <- err 329 | if quit != nil { 330 | close(quit) 331 | quit = nil 332 | } 333 | return 334 | } 335 | cfg.ClientMessageCh <- parsedMsg 336 | } 337 | } 338 | }() 339 | 340 | wg.Wait() 341 | return nil 342 | } 343 | -------------------------------------------------------------------------------- /tightcompression_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=TightCompression"; DO NOT EDIT. 2 | 3 | package vnc2video 4 | 5 | import "fmt" 6 | 7 | const ( 8 | _TightCompression_name_0 = "TightCompressionBasic" 9 | _TightCompression_name_1 = "TightCompressionFillTightCompressionJPEGTightCompressionPNG" 10 | ) 11 | 12 | var ( 13 | _TightCompression_index_0 = [...]uint8{0, 21} 14 | _TightCompression_index_1 = [...]uint8{0, 20, 40, 59} 15 | ) 16 | 17 | func (i TightCompression) String() string { 18 | switch { 19 | case i == 0: 20 | return _TightCompression_name_0 21 | case 8 <= i && i <= 10: 22 | i -= 8 23 | return _TightCompression_name_1[_TightCompression_index_1[i]:_TightCompression_index_1[i+1]] 24 | default: 25 | return fmt.Sprintf("TightCompression(%d)", i) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tightfilter_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=TightFilter"; DO NOT EDIT. 2 | 3 | package vnc2video 4 | 5 | import "fmt" 6 | 7 | const _TightFilter_name = "TightFilterCopyTightFilterPaletteTightFilterGradient" 8 | 9 | var _TightFilter_index = [...]uint8{0, 15, 33, 52} 10 | 11 | func (i TightFilter) String() string { 12 | if i >= TightFilter(len(_TightFilter_index)-1) { 13 | return fmt.Sprintf("TightFilter(%d)", i) 14 | } 15 | return _TightFilter_name[_TightFilter_index[i]:_TightFilter_index[i+1]] 16 | } 17 | --------------------------------------------------------------------------------