// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package rdpclient.rdp; import streamer.BaseElement; import streamer.ByteBuffer; import streamer.Element; import streamer.Link; import streamer.Pipeline; import streamer.PipelineImpl; import streamer.debug.FakeSink; import common.BitmapOrder; import common.BitmapRectangle; /** * @see http://msdn.microsoft.com/en-us/library/cc240624.aspx */ public class ServerBitmapUpdate extends BaseElement { public static final int UPDATETYPE_BITMAP = 0x0001; /** * Indicates that the bitmap data is compressed. The bitmapComprHdr field MUST * be present if the NO_BITMAP_COMPRESSION_HDR (0x0400) flag is not set. */ public static final int BITMAP_COMPRESSION = 0x0001; /** * Indicates that the bitmapComprHdr field is not present (removed for * bandwidth efficiency to save 8 bytes). */ private static final int NO_BITMAP_COMPRESSION_HDR = 0x0400; public ServerBitmapUpdate(String id) { super(id); } @Override public void handleData(ByteBuffer buf, Link link) { if (verbose) System.out.println("[" + this + "] INFO: Data received: " + buf + "."); // * DEBUG */System.out.println(buf.toHexString(buf.length)); BitmapOrder order = new BitmapOrder(); // (2 bytes): A 16-bit, unsigned integer. The update type. This field MUST // be set to UPDATETYPE_BITMAP (0x0001). int updateType = buf.readSignedShortLE(); if (updateType != UPDATETYPE_BITMAP) throw new RuntimeException("Unknown update type. Expected update type: UPDATETYPE_BITMAP (0x1). Actual update type: " + updateType + ", buf: " + buf + "."); // (2 bytes): A 16-bit, unsigned integer. The number of screen rectangles // present in the rectangles field. int numberRectangles = buf.readSignedShortLE(); // (variable): Variable-length array of TS_BITMAP_DATA structures, each of // which contains a rectangular clipping taken from the server-side screen // frame buffer. The number of screen clippings in the array is specified by // the numberRectangles field. BitmapRectangle[] rectangles = new BitmapRectangle[numberRectangles]; for (int i = 0; i < numberRectangles; i++) { rectangles[i] = readRectangle(buf); } order.rectangles = rectangles; buf.assertThatBufferIsFullyRead(); ByteBuffer data = new ByteBuffer(order); pushDataToAllOuts(data); buf.unref(); } public BitmapRectangle readRectangle(ByteBuffer buf) { BitmapRectangle rectangle = new BitmapRectangle(); // (2 bytes): A 16-bit, unsigned integer. Left bound of the rectangle. rectangle.x = buf.readSignedShortLE(); // (2 bytes): A 16-bit, unsigned integer. Top bound of the rectangle. rectangle.y = buf.readSignedShortLE(); // (2 bytes): A 16-bit, unsigned integer. Inclusive right bound of the // rectangle. int destRight = buf.readSignedShortLE(); rectangle.width = destRight - rectangle.x + 1; // (2 bytes): A 16-bit, unsigned integer. Inclusive bottom bound of the // rectangle. int destBottom = buf.readSignedShortLE(); rectangle.height = destBottom - rectangle.y + 1; // (2 bytes): A 16-bit, unsigned integer. The width of the rectangle. rectangle.bufferWidth = buf.readSignedShortLE(); // (2 bytes): A 16-bit, unsigned integer. The height of the rectangle. rectangle.bufferHeight = buf.readSignedShortLE(); // (2 bytes): A 16-bit, unsigned integer. The color depth of the rectangle // data in bits-per-pixel. rectangle.colorDepth = buf.readSignedShortLE(); // (2 bytes): A 16-bit, unsigned integer. The flags describing the format of // the bitmap data in the bitmapDataStream field. int flags = buf.readSignedShortLE(); // BITMAP_COMPRESSION 0x0001 // Indicates that the bitmap data is compressed. The bitmapComprHdr field // MUST be present if the NO_BITMAP_COMPRESSION_HDR (0x0400) flag is not // set. boolean compressed = ((flags & BITMAP_COMPRESSION) > 0); // (2 bytes): A 16-bit, unsigned integer. The size in bytes of the data in // the bitmapComprHdr and bitmapDataStream fields. int bitmapLength = buf.readSignedShortLE(); // NO_BITMAP_COMPRESSION_HDR 0x0400 // Indicates that the bitmapComprHdr field is not present (removed for // bandwidth efficiency to save 8 bytes). if (compressed && (flags & NO_BITMAP_COMPRESSION_HDR) == 0) { // (8 bytes): Optional Compressed Data Header structure specifying the // bitmap data in the bitmapDataStream. // This field MUST be present if the BITMAP_COMPRESSION (0x0001) flag is // present in the Flags field, but the NO_BITMAP_COMPRESSION_HDR (0x0400) // flag is not. // Note: Even when compression header is enabled, server sends nothing. // rectangle.compressedBitmapHeader = buf.readBytes(8); } // (variable): A variable-length array of bytes describing a bitmap image. // Bitmap data is either compressed or uncompressed, depending on whether // the BITMAP_COMPRESSION flag is present in the Flags field. Uncompressed // bitmap data is formatted as a bottom-up, left-to-right series of pixels. // Each pixel is a whole number of bytes. Each row contains a multiple of // four bytes (including up to three bytes of padding, as necessary). // Compressed bitmaps not in 32 bpp format are compressed using Interleaved // RLE and encapsulated in an RLE Compressed Bitmap Stream structure, // while compressed bitmaps at a color depth of 32 bpp are compressed // using RDP 6.0 Bitmap Compression and stored inside // an RDP 6.0 Bitmap Compressed Stream structure. if (!compressed) { rectangle.bitmapDataStream = buf.readBytes(bitmapLength); } else { ByteBuffer compressedImage = buf.readBytes(bitmapLength); //* DEBUG */System.out.println("Compressed image: " + compressedImage + ", depth: " + rectangle.bitsPerPixel + "."); rectangle.bitmapDataStream = RLEBitmapDecompression.rleDecompress(compressedImage, rectangle.bufferWidth, rectangle.bufferHeight, rectangle.colorDepth); compressedImage.unref(); } return rectangle; } /** * Example. */ public static void main(String args[]) { ByteBuffer packet = new ByteBuffer(new byte[] {0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x10, 0x00, 0x01, 0x04, 0x0a, 0x00, 0x0c, (byte)0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); Element bitmap = new ServerBitmapUpdate("bitmap") { { verbose = true; } }; FakeSink fakeSink = new FakeSink("sink") { { verbose = true; } }; Pipeline pipeline = new PipelineImpl("test"); // BufferedImageCanvas canvas = new BufferedImageCanvas(1024, 768); // Element adapter = new AwtRdpAdapter("test",canvas ); // pipeline.addAndLink(bitmap, adapter); pipeline.addAndLink(bitmap, fakeSink); bitmap.handleData(packet, null); } }