From cb64aed70171e40ef77370ccfce57fcfd237e9f1 Mon Sep 17 00:00:00 2001 From: Vinicius Tinti Date: Tue, 24 Jul 2018 08:29:50 -0300 Subject: [PATCH 1/6] Add screenshot example Add an example of taking a screenshot using the library based on 'vnc-screenshot' tool available on https://github.com/erasche/vnc-screenshot Signed-off-by: Vinicius Tinti --- examples/screenshot.go | 81 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 examples/screenshot.go diff --git a/examples/screenshot.go b/examples/screenshot.go new file mode 100644 index 0000000..440ab08 --- /dev/null +++ b/examples/screenshot.go @@ -0,0 +1,81 @@ +package main + +import ( + "bytes" + "image" + "image/color" + "image/png" + "io" + "io/ioutil" + "net" + + "github.com/mitchellh/go-vnc" +) + +func TakeScreenshot(address, password string) (*image.RGBA, error) { + nc, err := net.Dial("tcp", address) + if err != nil { + return nil, err + } + defer nc.Close() + + serverMessageChannel := make(chan vnc.ServerMessage) + + vncClient, err := vnc.Client(nc, &vnc.ClientConfig{ + Auth: []vnc.ClientAuth{ + &vnc.PasswordAuth{Password: password}, + }, + ServerMessages: []vnc.ServerMessage{ + &vnc.FramebufferUpdateMessage{}, + }, + ServerMessageCh: serverMessageChannel, + }) + if err != nil { + return nil, err + } + defer vncClient.Close() + + err = vncClient.FramebufferUpdateRequest(false, 0, 0, + vncClient.FrameBufferWidth, vncClient.FrameBufferHeight) + if err != nil { + return nil, err + } + + serverMessage := <-serverMessageChannel + + rects := serverMessage.(*vnc.FramebufferUpdateMessage).Rectangles + if len(rects) == 0 { + panic("vnc: framebuffer rects length") + } + + w := int(rects[0].Width) + h := int(rects[0].Height) + img := image.NewRGBA(image.Rect(0, 0, w, h)) + + enc := rects[0].Enc.(*vnc.RawEncoding) + for i, c := range enc.Colors { + x, y := i%w, i/w + r, g, b := uint8(c.R), uint8(c.G), uint8(c.B) + + img.Set(x, y, color.RGBA{r, g, b, 255}) + } + + return img, nil +} + +func main() { + img, err := TakeScreenshot("server:port", "password") + if err != nil { + panic(err) + } + + data := bytes.Buffer{} + pngEncoder := png.Encoder{CompressionLevel: png.NoCompression} + + err = pngEncoder.Encode(io.Writer(&data), img) + if err != nil { + panic(err) + } + + ioutil.WriteFile("screenshot.png", data.Bytes(), 0600) +} From 1d572b3ca22f4ee0ad0f4917c1806fbb91fca840 Mon Sep 17 00:00:00 2001 From: Vinicius Tinti Date: Mon, 23 Dec 2019 10:22:53 -0300 Subject: [PATCH 2/6] Fix screenshot example Signed-off-by: Vinicius Tinti --- examples/screenshot.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/screenshot.go b/examples/screenshot.go index 440ab08..33ce966 100644 --- a/examples/screenshot.go +++ b/examples/screenshot.go @@ -48,16 +48,20 @@ func TakeScreenshot(address, password string) (*image.RGBA, error) { panic("vnc: framebuffer rects length") } - w := int(rects[0].Width) - h := int(rects[0].Height) + w := int(vncClient.FrameBufferWidth) + h := int(vncClient.FrameBufferHeight) img := image.NewRGBA(image.Rect(0, 0, w, h)) - enc := rects[0].Enc.(*vnc.RawEncoding) - for i, c := range enc.Colors { - x, y := i%w, i/w - r, g, b := uint8(c.R), uint8(c.G), uint8(c.B) + for _, rect := range rects { + switch enc := rect.Enc.(type) { + case *vnc.RawEncoding: + for i, c := range enc.Colors { + x, y := i%w, i/w + r, g, b := uint8(c.R), uint8(c.G), uint8(c.B) - img.Set(x, y, color.RGBA{r, g, b, 255}) + img.Set(int(rect.X)+x, int(rect.Y)+y, color.RGBA{r, g, b, 255}) + } + } } return img, nil From 39f3f6b693031b0e890d8c0ee39eca4bebea3dc8 Mon Sep 17 00:00:00 2001 From: Vinicius Tinti Date: Mon, 23 Dec 2019 10:23:13 -0300 Subject: [PATCH 3/6] Add support for zlib encoding Signed-off-by: Vinicius Tinti --- encoding.go | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/encoding.go b/encoding.go index 7198595..b4a8192 100644 --- a/encoding.go +++ b/encoding.go @@ -1,6 +1,8 @@ package vnc import ( + "bytes" + "compress/zlib" "encoding/binary" "io" ) @@ -67,3 +69,115 @@ func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, return &RawEncoding{colors}, nil } + +// ZlibEncoding is raw pixel data sent by the server compressed by Zlib. +// +// A single Zlib stream is created. There is only a single header for a framebuffer request response. +type ZlibEncoding struct { + Colors []Color + ZStream *bytes.Buffer + ZReader io.ReadCloser +} + +func (*ZlibEncoding) Type() int32 { + return 6 +} + +func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + bytesPerPixel := c.PixelFormat.BPP / 8 + pixelBytes := make([]uint8, bytesPerPixel) + + var byteOrder binary.ByteOrder = binary.LittleEndian + if c.PixelFormat.BigEndian { + byteOrder = binary.BigEndian + } + + // Format + // 4 bytes | uint32 | length + // 'length' bytes | []byte | zlibData + + // Read zlib length + var zipLength uint32 + err := binary.Read(r, binary.BigEndian, &zipLength) + if err != nil { + return nil, err + } + + // Read all compressed data + zBytes := make([]byte, zipLength) + if _, err := io.ReadFull(r, zBytes); err != nil { + return nil, err + } + + // Create new zlib stream if needed + if ze.ZStream == nil { + // Create and save the buffer + ze.ZStream = new(bytes.Buffer) + ze.ZStream.Write(zBytes) + + // Create a reader for the buffer + ze.ZReader, err = zlib.NewReader(ze.ZStream) + if err != nil { + return nil, err + } + + // This is needed to avoid 'zlib missing header' + } else { + // Just append if already created + ze.ZStream.Write(zBytes) + } + + // Calculate zlib decompressed size + sizeToRead := int(rect.Height) * int(rect.Width) * int(bytesPerPixel) + + // Create buffer for bytes + colorBytes := make([]byte, sizeToRead) + + // Read all data from zlib stream + read, err := io.ReadFull(ze.ZReader, colorBytes) + if read != sizeToRead || err != nil { + return nil, err + } + + // Create buffer for raw encoding + colorReader := bytes.NewReader(colorBytes) + + colors := make([]Color, int(rect.Height)*int(rect.Width)) + + for y := uint16(0); y < rect.Height; y++ { + for x := uint16(0); x < rect.Width; x++ { + if _, err := io.ReadFull(colorReader, pixelBytes); err != nil { + return nil, err + } + + var rawPixel uint32 + if c.PixelFormat.BPP == 8 { + rawPixel = uint32(pixelBytes[0]) + } else if c.PixelFormat.BPP == 16 { + rawPixel = uint32(byteOrder.Uint16(pixelBytes)) + } else if c.PixelFormat.BPP == 32 { + rawPixel = byteOrder.Uint32(pixelBytes) + } + + color := &colors[int(y)*int(rect.Width)+int(x)] + if c.PixelFormat.TrueColor { + color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax)) + color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax)) + color.B = uint16((rawPixel >> c.PixelFormat.BlueShift) & uint32(c.PixelFormat.BlueMax)) + } else { + *color = c.ColorMap[rawPixel] + } + } + } + + return &ZlibEncoding{Colors: colors}, nil +} + +func (ze *ZlibEncoding) Close() { + if ze.ZStream != nil { + ze.ZStream = nil + ze.ZReader.Close() + ze.ZReader = nil + } +} + From 45d1dc4e46a3013ebde4036341ad1bfa64875732 Mon Sep 17 00:00:00 2001 From: Vinicius Tinti Date: Wed, 8 Jan 2020 13:27:12 -0300 Subject: [PATCH 4/6] Remove state in ZlibEncoding struct There is no need to store the state in ZLibEncoding struct. Signed-off-by: Vinicius Tinti --- encoding.go | 45 +++++++++++---------------------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/encoding.go b/encoding.go index b4a8192..2c4b2e0 100644 --- a/encoding.go +++ b/encoding.go @@ -75,8 +75,6 @@ func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, // A single Zlib stream is created. There is only a single header for a framebuffer request response. type ZlibEncoding struct { Colors []Color - ZStream *bytes.Buffer - ZReader io.ReadCloser } func (*ZlibEncoding) Type() int32 { @@ -97,44 +95,32 @@ func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encod // 'length' bytes | []byte | zlibData // Read zlib length - var zipLength uint32 - err := binary.Read(r, binary.BigEndian, &zipLength) + var zlibLength uint32 + err := binary.Read(r, binary.BigEndian, &zlibLength) if err != nil { return nil, err } // Read all compressed data - zBytes := make([]byte, zipLength) + zBytes := make([]byte, zlibLength) if _, err := io.ReadFull(r, zBytes); err != nil { return nil, err } - // Create new zlib stream if needed - if ze.ZStream == nil { - // Create and save the buffer - ze.ZStream = new(bytes.Buffer) - ze.ZStream.Write(zBytes) - - // Create a reader for the buffer - ze.ZReader, err = zlib.NewReader(ze.ZStream) - if err != nil { - return nil, err - } - - // This is needed to avoid 'zlib missing header' - } else { - // Just append if already created - ze.ZStream.Write(zBytes) + // Create a reader for the buffer + // This is needed to avoid 'zlib missing header' + zReader, err := zlib.NewReader(bytes.NewReader(zBytes)) + if err != nil { + return nil, err } - - // Calculate zlib decompressed size - sizeToRead := int(rect.Height) * int(rect.Width) * int(bytesPerPixel) + defer zReader.Close() // Create buffer for bytes + sizeToRead := int(rect.Height) * int(rect.Width) * int(bytesPerPixel) colorBytes := make([]byte, sizeToRead) // Read all data from zlib stream - read, err := io.ReadFull(ze.ZReader, colorBytes) + read, err := io.ReadFull(zReader, colorBytes) if read != sizeToRead || err != nil { return nil, err } @@ -172,12 +158,3 @@ func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encod return &ZlibEncoding{Colors: colors}, nil } - -func (ze *ZlibEncoding) Close() { - if ze.ZStream != nil { - ze.ZStream = nil - ze.ZReader.Close() - ze.ZReader = nil - } -} - From 1a81ddc39f481c33b4700e55123bfbc30cd8ffca Mon Sep 17 00:00:00 2001 From: Vinicius Tinti Date: Wed, 8 Jan 2020 13:27:46 -0300 Subject: [PATCH 5/6] Use own github repo for example Ignore upstream since it seems to be no longer maintained. Signed-off-by: Vinicius Tinti --- examples/screenshot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/screenshot.go b/examples/screenshot.go index 33ce966..ebc0561 100644 --- a/examples/screenshot.go +++ b/examples/screenshot.go @@ -9,7 +9,7 @@ import ( "io/ioutil" "net" - "github.com/mitchellh/go-vnc" + "github.com/tinti/go-vnc" ) func TakeScreenshot(address, password string) (*image.RGBA, error) { From ea0b86ec84fe2581e6ff86e73046e5e500efe6c0 Mon Sep 17 00:00:00 2001 From: Vinicius Tinti Date: Wed, 8 Jan 2020 13:30:15 -0300 Subject: [PATCH 6/6] Update example to use Zlib Encoding Signed-off-by: Vinicius Tinti --- examples/screenshot.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/examples/screenshot.go b/examples/screenshot.go index ebc0561..c1e47e0 100644 --- a/examples/screenshot.go +++ b/examples/screenshot.go @@ -35,6 +35,14 @@ func TakeScreenshot(address, password string) (*image.RGBA, error) { } defer vncClient.Close() + err = vncClient.SetEncodings([]vnc.Encoding{ + &vnc.ZlibEncoding{}, + &vnc.RawEncoding{}, + }) + if err != nil { + return nil, err + } + err = vncClient.FramebufferUpdateRequest(false, 0, 0, vncClient.FrameBufferWidth, vncClient.FrameBufferHeight) if err != nil { @@ -61,9 +69,19 @@ func TakeScreenshot(address, password string) (*image.RGBA, error) { img.Set(int(rect.X)+x, int(rect.Y)+y, color.RGBA{r, g, b, 255}) } + case *vnc.ZlibEncoding: + for i, c := range enc.Colors { + x, y := i%w, i/w + r, g, b := uint8(c.R), uint8(c.G), uint8(c.B) + + img.Set(int(rect.X)+x, int(rect.Y)+y, color.RGBA{r, g, b, 255}) + } + default: + panic("vnc: unkown encoding") } } + return img, nil }