Mercurial > hg > Members > nobuyasu > tightVNCProxy
comparison src/ProxyVncCanvas.java @ 15:89e1c5f84407
faile ZRLE encode. success raw encode.
author | e085711 |
---|---|
date | Sun, 17 Apr 2011 19:33:01 +0900 |
parents | 57f227139599 |
children | 4eda2dbb7fef |
comparison
equal
deleted
inserted
replaced
14:57f227139599 | 15:89e1c5f84407 |
---|---|
1 | |
2 | |
3 import java.awt.*; | 1 import java.awt.*; |
4 import java.awt.event.*; | 2 import java.awt.event.*; |
5 import java.awt.image.*; | 3 import java.awt.image.*; |
6 import java.io.*; | 4 import java.io.*; |
7 import java.lang.*; | 5 import java.lang.*; |
13 // | 11 // |
14 //VncCanvas is a subclass of Canvas which draws a VNC desktop on it. | 12 //VncCanvas is a subclass of Canvas which draws a VNC desktop on it. |
15 // | 13 // |
16 | 14 |
17 class ProxyVncCanvas extends Canvas implements KeyListener, MouseListener, | 15 class ProxyVncCanvas extends Canvas implements KeyListener, MouseListener, |
18 MouseMotionListener { | 16 MouseMotionListener { |
19 | 17 |
20 VncProxyService viewer; | 18 VncProxyService viewer; |
21 MyRfbProto rfb; | 19 MyRfbProto rfb; |
22 ColorModel cm8, cm24; | 20 ColorModel cm8, cm24; |
23 Color[] colors; | 21 Color[] colors; |
24 int bytesPixel; | 22 int bytesPixel; |
25 | 23 |
26 int maxWidth = 0, maxHeight = 0; | 24 int maxWidth = 0, maxHeight = 0; |
27 int scalingFactor; | 25 int scalingFactor; |
28 int scaledWidth, scaledHeight; | 26 int scaledWidth, scaledHeight; |
29 | 27 |
30 Image memImage; | 28 Image memImage; |
31 Graphics memGraphics; | 29 Graphics memGraphics; |
32 | 30 |
33 Image rawPixelsImage; | 31 Image rawPixelsImage; |
34 MemoryImageSource pixelsSource; | 32 MemoryImageSource pixelsSource; |
35 byte[] pixels8; | 33 byte[] pixels8; |
36 int[] pixels24; | 34 int[] pixels24; |
37 | 35 |
38 // Update statistics. | 36 // Update statistics. |
39 long statStartTime; // time on first framebufferUpdateRequest | 37 long statStartTime; // time on first framebufferUpdateRequest |
40 int statNumUpdates; // counter for FramebufferUpdate messages | 38 int statNumUpdates; // counter for FramebufferUpdate messages |
41 int statNumTotalRects; // rectangles in FramebufferUpdate messages | 39 int statNumTotalRects; // rectangles in FramebufferUpdate messages |
42 int statNumPixelRects; // the same, but excluding pseudo-rectangles | 40 int statNumPixelRects; // the same, but excluding pseudo-rectangles |
43 int statNumRectsTight; // Tight-encoded rectangles (including JPEG) | 41 int statNumRectsTight; // Tight-encoded rectangles (including JPEG) |
44 int statNumRectsTightJPEG; // JPEG-compressed Tight-encoded rectangles | 42 int statNumRectsTightJPEG; // JPEG-compressed Tight-encoded rectangles |
45 int statNumRectsZRLE; // ZRLE-encoded rectangles | 43 int statNumRectsZRLE; // ZRLE-encoded rectangles |
46 int statNumRectsHextile; // Hextile-encoded rectangles | 44 int statNumRectsHextile; // Hextile-encoded rectangles |
47 int statNumRectsRaw; // Raw-encoded rectangles | 45 int statNumRectsRaw; // Raw-encoded rectangles |
48 int statNumRectsCopy; // CopyRect rectangles | 46 int statNumRectsCopy; // CopyRect rectangles |
49 int statNumBytesEncoded; // number of bytes in updates, as received | 47 int statNumBytesEncoded; // number of bytes in updates, as received |
50 int statNumBytesDecoded; // number of bytes, as if Raw encoding was used | 48 int statNumBytesDecoded; // number of bytes, as if Raw encoding was used |
51 | 49 |
52 // ZRLE encoder's data. | 50 // ZRLE encoder's data. |
53 byte[] zrleBuf; | 51 byte[] zrleBuf; |
54 int zrleBufLen = 0; | 52 int zrleBufLen = 0; |
55 byte[] zrleTilePixels8; | 53 byte[] zrleTilePixels8; |
56 int[] zrleTilePixels24; | 54 int[] zrleTilePixels24; |
57 ZlibInStream zrleInStream; | 55 ZlibInStream zrleInStream; |
58 boolean zrleRecWarningShown = false; | 56 boolean zrleRecWarningShown = false; |
59 | 57 |
60 // Zlib encoder's data. | 58 // Zlib encoder's data. |
61 byte[] zlibBuf; | 59 byte[] zlibBuf; |
62 int zlibBufLen = 0; | 60 int zlibBufLen = 0; |
63 Inflater zlibInflater; | 61 Inflater zlibInflater; |
64 | 62 |
65 // Tight encoder's data. | 63 // Tight encoder's data. |
66 final static int tightZlibBufferSize = 512; | 64 final static int tightZlibBufferSize = 512; |
67 Inflater[] tightInflaters; | 65 Inflater[] tightInflaters; |
68 | 66 |
69 // Since JPEG images are loaded asynchronously, we have to remember | 67 // Since JPEG images are loaded asynchronously, we have to remember |
70 // their position in the framebuffer. Also, this jpegRect object is | 68 // their position in the framebuffer. Also, this jpegRect object is |
71 // used for synchronization between the rfbThread and a JVM's thread | 69 // used for synchronization between the rfbThread and a JVM's thread |
72 // which decodes and loads JPEG images. | 70 // which decodes and loads JPEG images. |
73 Rectangle jpegRect; | 71 Rectangle jpegRect; |
74 | 72 |
75 // True if we process keyboard and mouse events. | 73 // True if we process keyboard and mouse events. |
76 boolean inputEnabled; | 74 boolean inputEnabled; |
77 | 75 |
78 // | 76 |
79 // The constructors. | 77 // |
80 // | 78 // The constructors. |
81 | 79 // |
82 public ProxyVncCanvas(VncProxyService v, int maxWidth_, int maxHeight_) | 80 |
83 throws IOException { | 81 public ProxyVncCanvas(VncProxyService v, int maxWidth_, int maxHeight_) |
84 | 82 throws IOException { |
85 viewer = v; | 83 |
86 maxWidth = maxWidth_; | 84 viewer = v; |
87 maxHeight = maxHeight_; | 85 maxWidth = maxWidth_; |
88 | 86 maxHeight = maxHeight_; |
89 rfb = viewer.rfb; | 87 |
90 | 88 rfb = viewer.rfb; |
91 tightInflaters = new Inflater[4]; | 89 |
92 | 90 tightInflaters = new Inflater[4]; |
93 cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6)); | 91 |
94 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF); | 92 cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6)); |
95 | 93 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF); |
96 colors = new Color[256]; | 94 |
97 for (int i = 0; i < 256; i++) | 95 colors = new Color[256]; |
98 colors[i] = new Color(cm8.getRGB(i)); | 96 for (int i = 0; i < 256; i++) |
99 | 97 colors[i] = new Color(cm8.getRGB(i)); |
100 // setPixelFormat(); | 98 |
101 | 99 // setPixelFormat(); |
102 inputEnabled = false; | 100 |
103 // Keyboard listener is enabled even in view-only mode, to catch | 101 inputEnabled = false; |
104 // 'r' or 'R' key presses used to request screen update. | 102 // Keyboard listener is enabled even in view-only mode, to catch |
105 addKeyListener(this); | 103 // 'r' or 'R' key presses used to request screen update. |
106 } | 104 addKeyListener(this); |
107 | 105 } |
108 public ProxyVncCanvas(VncProxyService v) throws IOException { | 106 |
109 this(v, 0, 0); | 107 public ProxyVncCanvas(VncProxyService v) throws IOException { |
110 } | 108 this(v, 0, 0); |
111 | 109 } |
112 // | 110 |
113 // Callback methods to determine geometry of our Component. | 111 // |
114 // | 112 // Callback methods to determine geometry of our Component. |
115 | 113 // |
116 public Dimension getPreferredSize() { | 114 |
117 return new Dimension(scaledWidth, scaledHeight); | 115 public Dimension getPreferredSize() { |
118 } | 116 return new Dimension(scaledWidth, scaledHeight); |
119 | 117 } |
120 public Dimension getMinimumSize() { | 118 |
121 return new Dimension(scaledWidth, scaledHeight); | 119 public Dimension getMinimumSize() { |
122 } | 120 return new Dimension(scaledWidth, scaledHeight); |
123 | 121 } |
124 public Dimension getMaximumSize() { | 122 |
125 return new Dimension(scaledWidth, scaledHeight); | 123 public Dimension getMaximumSize() { |
126 } | 124 return new Dimension(scaledWidth, scaledHeight); |
127 | 125 } |
128 // | 126 |
129 // All painting is performed here. | 127 // |
130 // | 128 // All painting is performed here. |
131 | 129 // |
132 public void update(Graphics g) { | 130 |
133 paint(g); | 131 public void update(Graphics g) { |
134 } | 132 paint(g); |
135 | 133 } |
136 public void paint(Graphics g) { | 134 |
137 synchronized (memImage) { | 135 public void paint(Graphics g) { |
138 if (rfb.framebufferWidth == scaledWidth) { | 136 synchronized (memImage) { |
139 g.drawImage(memImage, 0, 0, null); | 137 if (rfb.framebufferWidth == scaledWidth) { |
138 g.drawImage(memImage, 0, 0, null); | |
139 } else { | |
140 paintScaledFrameBuffer(g); | |
141 } | |
142 } | |
143 if (showSoftCursor) { | |
144 int x0 = cursorX - hotX, y0 = cursorY - hotY; | |
145 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight); | |
146 if (r.intersects(g.getClipBounds())) { | |
147 g.drawImage(softCursor, x0, y0, null); | |
148 } | |
149 } | |
150 } | |
151 | |
152 public void paintScaledFrameBuffer(Graphics g) { | |
153 g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null); | |
154 } | |
155 | |
156 // | |
157 // Override the ImageObserver interface method to handle drawing of | |
158 // JPEG-encoded data. | |
159 // | |
160 | |
161 public boolean imageUpdate(Image img, int infoflags, int x, int y, | |
162 int width, int height) { | |
163 if ((infoflags & (ALLBITS | ABORT)) == 0) { | |
164 return true; // We need more image data. | |
140 } else { | 165 } else { |
141 paintScaledFrameBuffer(g); | 166 // If the whole image is available, draw it now. |
142 } | 167 if ((infoflags & ALLBITS) != 0) { |
143 } | 168 if (jpegRect != null) { |
144 if (showSoftCursor) { | 169 synchronized (jpegRect) { |
145 int x0 = cursorX - hotX, y0 = cursorY - hotY; | 170 memGraphics |
146 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight); | 171 .drawImage(img, jpegRect.x, jpegRect.y, null); |
147 if (r.intersects(g.getClipBounds())) { | 172 scheduleRepaint(jpegRect.x, jpegRect.y, jpegRect.width, |
148 g.drawImage(softCursor, x0, y0, null); | 173 jpegRect.height); |
149 } | 174 jpegRect.notify(); |
150 } | 175 } |
151 } | 176 } |
152 | 177 } |
153 public void paintScaledFrameBuffer(Graphics g) { | 178 return false; // All image data was processed. |
154 g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null); | 179 } |
155 } | 180 } |
156 | 181 |
157 // | 182 // |
158 // Override the ImageObserver interface method to handle drawing of | 183 // Start/stop receiving mouse events. Keyboard events are received |
159 // JPEG-encoded data. | 184 // even in view-only mode, because we want to map the 'r' key to the |
160 // | 185 // screen refreshing function. |
161 | 186 // |
162 public boolean imageUpdate(Image img, int infoflags, int x, int y, | 187 |
163 int width, int height) { | 188 public synchronized void enableInput(boolean enable) { |
164 if ((infoflags & (ALLBITS | ABORT)) == 0) { | 189 if (enable && !inputEnabled) { |
165 return true; // We need more image data. | 190 inputEnabled = true; |
166 } else { | 191 addMouseListener(this); |
167 // If the whole image is available, draw it now. | 192 addMouseMotionListener(this); |
168 if ((infoflags & ALLBITS) != 0) { | 193 if (viewer.showControls) { |
169 if (jpegRect != null) { | 194 viewer.buttonPanel.enableRemoteAccessControls(true); |
170 synchronized (jpegRect) { | 195 } |
171 memGraphics | 196 createSoftCursor(); // scaled cursor |
172 .drawImage(img, jpegRect.x, jpegRect.y, null); | 197 } else if (!enable && inputEnabled) { |
173 scheduleRepaint(jpegRect.x, jpegRect.y, jpegRect.width, | 198 inputEnabled = false; |
174 jpegRect.height); | 199 removeMouseListener(this); |
175 jpegRect.notify(); | 200 removeMouseMotionListener(this); |
176 } | 201 if (viewer.showControls) { |
177 } | 202 viewer.buttonPanel.enableRemoteAccessControls(false); |
178 } | 203 } |
179 return false; // All image data was processed. | 204 createSoftCursor(); // non-scaled cursor |
180 } | 205 } |
181 } | 206 } |
182 | 207 |
183 // | 208 public void setPixelFormat() throws IOException { |
184 // Start/stop receiving mouse events. Keyboard events are received | 209 if (viewer.options.eightBitColors) { |
185 // even in view-only mode, because we want to map the 'r' key to the | 210 rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6); |
186 // screen refreshing function. | 211 bytesPixel = 1; |
187 // | 212 } else { |
188 | 213 rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, |
189 public synchronized void enableInput(boolean enable) { | 214 0); |
190 if (enable && !inputEnabled) { | 215 bytesPixel = 4; |
191 inputEnabled = true; | 216 } |
192 addMouseListener(this); | 217 updateFramebufferSize(); |
193 addMouseMotionListener(this); | 218 } |
194 if (viewer.showControls) { | 219 |
195 viewer.buttonPanel.enableRemoteAccessControls(true); | 220 void updateFramebufferSize() { |
196 } | 221 |
197 createSoftCursor(); // scaled cursor | 222 // Useful shortcuts. |
198 } else if (!enable && inputEnabled) { | 223 int fbWidth = rfb.framebufferWidth; |
199 inputEnabled = false; | 224 int fbHeight = rfb.framebufferHeight; |
200 removeMouseListener(this); | 225 |
201 removeMouseMotionListener(this); | 226 // Calculate scaling factor for auto scaling. |
202 if (viewer.showControls) { | 227 if (maxWidth > 0 && maxHeight > 0) { |
203 viewer.buttonPanel.enableRemoteAccessControls(false); | 228 int f1 = maxWidth * 100 / fbWidth; |
204 } | 229 int f2 = maxHeight * 100 / fbHeight; |
205 createSoftCursor(); // non-scaled cursor | 230 scalingFactor = Math.min(f1, f2); |
206 } | 231 if (scalingFactor > 100) |
207 } | 232 scalingFactor = 100; |
208 | 233 System.out.println("Scaling desktop at " + scalingFactor + "%"); |
209 public void setPixelFormat() throws IOException { | 234 } |
210 if (viewer.options.eightBitColors) { | 235 |
211 rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6); | 236 // Update scaled framebuffer geometry. |
212 bytesPixel = 1; | 237 scaledWidth = (fbWidth * scalingFactor + 50) / 100; |
213 } else { | 238 scaledHeight = (fbHeight * scalingFactor + 50) / 100; |
214 rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, | 239 |
215 0); | 240 // Create new off-screen image either if it does not exist, or if |
216 bytesPixel = 4; | 241 // its geometry should be changed. It's not necessary to replace |
217 } | 242 // existing image if only pixel format should be changed. |
218 updateFramebufferSize(); | 243 if (memImage == null) { |
219 } | |
220 | |
221 void updateFramebufferSize() { | |
222 | |
223 // Useful shortcuts. | |
224 int fbWidth = rfb.framebufferWidth; | |
225 int fbHeight = rfb.framebufferHeight; | |
226 | |
227 // Calculate scaling factor for auto scaling. | |
228 if (maxWidth > 0 && maxHeight > 0) { | |
229 int f1 = maxWidth * 100 / fbWidth; | |
230 int f2 = maxHeight * 100 / fbHeight; | |
231 scalingFactor = Math.min(f1, f2); | |
232 if (scalingFactor > 100) | |
233 scalingFactor = 100; | |
234 System.out.println("Scaling desktop at " + scalingFactor + "%"); | |
235 } | |
236 | |
237 // Update scaled framebuffer geometry. | |
238 scaledWidth = (fbWidth * scalingFactor + 50) / 100; | |
239 scaledHeight = (fbHeight * scalingFactor + 50) / 100; | |
240 | |
241 // Create new off-screen image either if it does not exist, or if | |
242 // its geometry should be changed. It's not necessary to replace | |
243 // existing image if only pixel format should be changed. | |
244 if (memImage == null) { | |
245 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight); | |
246 memGraphics = memImage.getGraphics(); | |
247 } else if (memImage.getWidth(null) != fbWidth | |
248 || memImage.getHeight(null) != fbHeight) { | |
249 synchronized (memImage) { | |
250 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight); | 244 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight); |
251 memGraphics = memImage.getGraphics(); | 245 memGraphics = memImage.getGraphics(); |
252 } | 246 } else if (memImage.getWidth(null) != fbWidth |
253 } | 247 || memImage.getHeight(null) != fbHeight) { |
254 | 248 synchronized (memImage) { |
255 // Images with raw pixels should be re-allocated on every change | 249 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight); |
256 // of geometry or pixel format. | 250 memGraphics = memImage.getGraphics(); |
257 if (bytesPixel == 1) { | 251 } |
258 | 252 } |
259 pixels24 = null; | 253 |
260 pixels8 = new byte[fbWidth * fbHeight]; | 254 // Images with raw pixels should be re-allocated on every change |
261 | 255 // of geometry or pixel format. |
262 pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm8, | 256 if (bytesPixel == 1) { |
263 pixels8, 0, fbWidth); | 257 |
264 | 258 pixels24 = null; |
265 zrleTilePixels24 = null; | 259 pixels8 = new byte[fbWidth * fbHeight]; |
266 zrleTilePixels8 = new byte[64 * 64]; | 260 |
267 | 261 pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm8, |
268 } else { | 262 pixels8, 0, fbWidth); |
269 | 263 |
270 pixels8 = null; | 264 zrleTilePixels24 = null; |
271 pixels24 = new int[fbWidth * fbHeight]; | 265 zrleTilePixels8 = new byte[64 * 64]; |
272 | 266 |
273 pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm24, | 267 } else { |
274 pixels24, 0, fbWidth); | 268 |
275 | 269 pixels8 = null; |
276 zrleTilePixels8 = null; | 270 pixels24 = new int[fbWidth * fbHeight]; |
277 zrleTilePixels24 = new int[64 * 64]; | 271 |
278 | 272 pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm24, |
279 } | 273 pixels24, 0, fbWidth); |
280 pixelsSource.setAnimated(true); | 274 |
281 rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource); | 275 zrleTilePixels8 = null; |
282 | 276 zrleTilePixels24 = new int[64 * 64]; |
283 } | 277 |
284 | 278 } |
285 void resizeDesktopFrame() { | 279 pixelsSource.setAnimated(true); |
286 setSize(scaledWidth, scaledHeight); | 280 rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource); |
287 | 281 |
288 // FIXME: Find a better way to determine correct size of a | 282 } |
289 // ScrollPane. -- const | 283 |
290 Insets insets = viewer.desktopScrollPane.getInsets(); | 284 void resizeDesktopFrame() { |
291 viewer.desktopScrollPane.setSize( | 285 setSize(scaledWidth, scaledHeight); |
292 scaledWidth + 2 * Math.min(insets.left, insets.right), | 286 |
293 scaledHeight + 2 * Math.min(insets.top, insets.bottom)); | 287 // FIXME: Find a better way to determine correct size of a |
294 | 288 // ScrollPane. -- const |
295 viewer.vncFrame.pack(); | 289 Insets insets = viewer.desktopScrollPane.getInsets(); |
296 | 290 viewer.desktopScrollPane.setSize( |
297 // Try to limit the frame size to the screen size. | 291 scaledWidth + 2 * Math.min(insets.left, insets.right), |
298 | 292 scaledHeight + 2 * Math.min(insets.top, insets.bottom)); |
299 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize(); | 293 |
300 Dimension frameSize = viewer.vncFrame.getSize(); | 294 viewer.vncFrame.pack(); |
301 Dimension newSize = frameSize; | 295 |
302 | 296 // Try to limit the frame size to the screen size. |
303 // Reduce Screen Size by 30 pixels in each direction; | 297 |
304 // This is a (poor) attempt to account for | 298 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize(); |
305 // 1) Menu bar on Macintosh (should really also account for | 299 Dimension frameSize = viewer.vncFrame.getSize(); |
306 // Dock on OSX). Usually 22px on top of screen. | 300 Dimension newSize = frameSize; |
307 // 2) Taxkbar on Windows (usually about 28 px on bottom) | 301 |
308 // 3) Other obstructions. | 302 // Reduce Screen Size by 30 pixels in each direction; |
309 | 303 // This is a (poor) attempt to account for |
310 screenSize.height -= 30; | 304 // 1) Menu bar on Macintosh (should really also account for |
311 screenSize.width -= 30; | 305 // Dock on OSX). Usually 22px on top of screen. |
312 | 306 // 2) Taxkbar on Windows (usually about 28 px on bottom) |
313 boolean needToResizeFrame = false; | 307 // 3) Other obstructions. |
314 if (frameSize.height > screenSize.height) { | 308 |
315 newSize.height = screenSize.height; | 309 screenSize.height -= 30; |
316 needToResizeFrame = true; | 310 screenSize.width -= 30; |
317 } | 311 |
318 if (frameSize.width > screenSize.width) { | 312 boolean needToResizeFrame = false; |
319 newSize.width = screenSize.width; | 313 if (frameSize.height > screenSize.height) { |
320 needToResizeFrame = true; | 314 newSize.height = screenSize.height; |
321 } | 315 needToResizeFrame = true; |
322 if (needToResizeFrame) { | 316 } |
323 viewer.vncFrame.setSize(newSize); | 317 if (frameSize.width > screenSize.width) { |
324 } | 318 newSize.width = screenSize.width; |
325 | 319 needToResizeFrame = true; |
326 viewer.desktopScrollPane.doLayout(); | 320 } |
327 } | 321 if (needToResizeFrame) { |
328 | 322 viewer.vncFrame.setSize(newSize); |
329 // | 323 } |
330 // processNormalProtocol() - executed by the rfbThread to deal with the | 324 |
331 // RFB socket. | 325 viewer.desktopScrollPane.doLayout(); |
332 // | 326 } |
333 | 327 |
334 public void processNormalProtocol() throws Exception { | 328 // |
335 | 329 // processNormalProtocol() - executed by the rfbThread to deal with the |
336 // Start/stop session recording if necessary. | 330 // RFB socket. |
337 viewer.checkRecordingStatus(); | 331 // |
338 | 332 |
339 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth, | 333 public void processNormalProtocol() throws Exception { |
340 rfb.framebufferHeight, false); | 334 |
341 | 335 // Start/stop session recording if necessary. |
342 resetStats(); | 336 viewer.checkRecordingStatus(); |
343 boolean statsRestarted = false; | 337 |
344 | 338 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth, |
345 // | 339 rfb.framebufferHeight, false); |
346 // main dispatch loop | 340 |
347 // | 341 resetStats(); |
348 | 342 boolean statsRestarted = false; |
349 long count = 0; | 343 |
350 rfb.initServSock(5550); | 344 // |
351 try { | 345 // main dispatch loop |
346 // | |
347 | |
348 long count = 0; | |
349 rfb.initServSock(5550); | |
350 | |
351 // single thread | |
352 /* | |
353 try { | |
352 // rfb.setSoTimeout(1000); | 354 // rfb.setSoTimeout(1000); |
353 Socket newCli = rfb.accept(); | 355 Socket newCli = rfb.accept(); |
354 rfb.sendInitData(newCli); | 356 rfb.sendInitData(newCli); |
355 rfb.addSock(newCli); | 357 rfb.addSock(newCli); |
356 } catch (IOException e) { | 358 } catch (IOException e) { |
357 } | 359 } |
358 /* | 360 */ |
359 * Thread accept = new Thread(new acceptThread(rfb)); accept.start(); | 361 // multi thread |
360 */ | 362 Thread accept = new Thread(new acceptThread(rfb)); |
361 while (true) { | 363 accept.start(); |
362 | 364 |
363 if (rfb.MYVNC) { | 365 |
364 if (rfb.cliSize() > 0) { | 366 while (true) { |
365 System.out.println("\ncount=" + count); | 367 |
366 | 368 System.out.println("\ncount=" + count); |
367 int nBytes = 0; | 369 count++; |
368 rfb.mark(20); | 370 |
369 int msgType = rfb.readU8(); | 371 rfb.mark(16); |
370 System.out.println("msgType=" + msgType); | 372 int mesageType = rfb.readU8(); |
371 | 373 System.out.println("mesageType=" + mesageType); |
372 rfb.skipBytes(11); | 374 rfb.skipBytes(11); |
373 int encoding = rfb.readU32(); | 375 int encoding = rfb.readU32(); |
374 System.out.println("encoding=" + encoding); | 376 System.out.println("encoding=" + encoding); |
375 nBytes = rfb.readU32(); | 377 rfb.reset(); |
376 System.out.println("nBytes=" + nBytes); | 378 |
377 rfb.reset(); | 379 rfb.mark(rfb.available()); |
378 | 380 |
379 int len = rfb.available(); | 381 rfb.printNumBytesRead(); |
380 System.out.println("rfb.available()=" + len); | 382 int bufSize = (int)rfb.getNumBytesRead(); |
381 if (len > 0) { | 383 |
382 | 384 // Read message type from the server. |
383 if (nBytes > 0 & encoding == 16) {// 0より大きい(データがある)ときデータを転送 | 385 int msgType = rfb.readServerMessageType(); |
384 | 386 |
385 rfb.mark(nBytes + 20); | 387 // Process the message depending on its type. |
386 | 388 switch (msgType) { |
387 byte b[] = new byte[nBytes + 20]; | 389 case RfbProto.FramebufferUpdate: |
388 // byte b[] = new byte[18+rfb.rnBytes]; | 390 |
389 System.out.println("b.length=" + b.length); | 391 if (statNumUpdates == viewer.debugStatsExcludeUpdates |
390 | 392 && !statsRestarted) { |
391 rfb.readFully(b); | 393 resetStats(); |
392 | 394 statsRestarted = true; |
393 // rfb.cliSock.getOutputStream().write(b, 0, | 395 } else if (statNumUpdates == viewer.debugStatsMeasureUpdates |
394 // b.length); | 396 && statsRestarted) { |
395 rfb.sendData(b); | 397 viewer.disconnect(); |
396 } | 398 } |
397 } | 399 |
398 } | 400 rfb.readFramebufferUpdate(); |
399 } | 401 statNumUpdates++; |
400 byte buffer[] = new byte[rfb.available()]; | 402 |
401 rfb.readFully(buffer); | 403 boolean cursorPosReceived = false; |
402 rfb.sendData(buffer); | 404 |
403 | 405 for (int i = 0; i < rfb.updateNRects; i++) { |
404 count++; | 406 |
405 | 407 rfb.readFramebufferUpdateRectHdr(); |
408 statNumTotalRects++; | |
409 int rx = rfb.updateRectX, ry = rfb.updateRectY; | |
410 int rw = rfb.updateRectW, rh = rfb.updateRectH; | |
411 | |
412 if (rfb.updateRectEncoding == rfb.EncodingLastRect) | |
413 break; | |
414 | |
415 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) { | |
416 rfb.setFramebufferSize(rw, rh); | |
417 updateFramebufferSize(); | |
418 break; | |
419 } | |
420 | |
421 if (rfb.updateRectEncoding == rfb.EncodingXCursor | |
422 || rfb.updateRectEncoding == rfb.EncodingRichCursor) { | |
423 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, | |
424 rw, rh); | |
425 continue; | |
426 } | |
427 | |
428 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) { | |
429 softCursorMove(rx, ry); | |
430 cursorPosReceived = true; | |
431 continue; | |
432 } | |
433 | |
434 long numBytesReadBefore = rfb.getNumBytesRead(); | |
435 | |
436 rfb.startTiming(); | |
437 | |
438 switch (rfb.updateRectEncoding) { | |
439 case RfbProto.EncodingRaw: | |
440 statNumRectsRaw++; | |
441 handleRawRect(rx, ry, rw, rh); | |
442 rfb.printNumBytesRead(); | |
443 break; | |
444 case RfbProto.EncodingCopyRect: | |
445 statNumRectsCopy++; | |
446 handleCopyRect(rx, ry, rw, rh); | |
447 break; | |
448 case RfbProto.EncodingRRE: | |
449 handleRRERect(rx, ry, rw, rh); | |
450 break; | |
451 case RfbProto.EncodingCoRRE: | |
452 handleCoRRERect(rx, ry, rw, rh); | |
453 break; | |
454 case RfbProto.EncodingHextile: | |
455 statNumRectsHextile++; | |
456 handleHextileRect(rx, ry, rw, rh); | |
457 break; | |
458 case RfbProto.EncodingZRLE: | |
459 statNumRectsZRLE++; | |
460 handleZRLERect(rx, ry, rw, rh); | |
461 break; | |
462 case RfbProto.EncodingZlib: | |
463 handleZlibRect(rx, ry, rw, rh); | |
464 break; | |
465 case RfbProto.EncodingTight: | |
466 statNumRectsTight++; | |
467 handleTightRect(rx, ry, rw, rh); | |
468 break; | |
469 default: | |
470 throw new Exception("Unknown RFB rectangle encoding " | |
471 + rfb.updateRectEncoding); | |
472 } | |
473 | |
474 rfb.stopTiming(); | |
475 | |
476 statNumPixelRects++; | |
477 statNumBytesDecoded += rw * rh * bytesPixel; | |
478 statNumBytesEncoded += (int) (rfb.getNumBytesRead() - numBytesReadBefore); | |
479 } | |
480 | |
481 boolean fullUpdateNeeded = false; | |
482 | |
483 // Start/stop session recording if necessary. Request full | |
484 // update if a new session file was opened. | |
485 if (viewer.checkRecordingStatus()) | |
486 fullUpdateNeeded = true; | |
487 | |
488 // Defer framebuffer update request if necessary. But wake up | |
489 // immediately on keyboard or mouse event. Also, don't sleep | |
490 // if there is some data to receive, or if the last update | |
491 // included a PointerPos message. | |
492 if (viewer.deferUpdateRequests > 0 && rfb.available() == 0 | |
493 && !cursorPosReceived) { | |
494 synchronized (rfb) { | |
495 try { | |
496 rfb.wait(viewer.deferUpdateRequests); | |
497 } catch (InterruptedException e) { | |
498 } | |
499 } | |
500 } | |
501 | |
502 viewer.autoSelectEncodings(); | |
503 | |
504 // Before requesting framebuffer update, check if the pixel | |
505 // format should be changed. | |
406 /* | 506 /* |
407 // Read message type from the server. | 507 if (viewer.options.eightBitColors != (bytesPixel == 1)) { |
408 int msgType = rfb.readServerMessageType(); | 508 // Pixel format should be changed. |
409 | 509 setPixelFormat(); |
410 // Process the message depending on its type. | 510 fullUpdateNeeded = true; |
411 switch (msgType) { | 511 } |
412 case RfbProto.FramebufferUpdate: | 512 */ |
413 | 513 // Request framebuffer update if needed. |
414 if (statNumUpdates == viewer.debugStatsExcludeUpdates | 514 int w = rfb.framebufferWidth; |
415 && !statsRestarted) { | 515 int h = rfb.framebufferHeight; |
416 resetStats(); | 516 rfb.writeFramebufferUpdateRequest(0, 0, w, h, !fullUpdateNeeded); |
417 statsRestarted = true; | 517 |
418 } else if (statNumUpdates == viewer.debugStatsMeasureUpdates | 518 break; |
419 && statsRestarted) { | 519 |
420 viewer.disconnect(); | 520 case RfbProto.SetColourMapEntries: |
421 } | 521 throw new Exception("Can't handle SetColourMapEntries message"); |
422 | 522 |
423 rfb.readFramebufferUpdate(); | 523 case RfbProto.Bell: |
424 statNumUpdates++; | 524 Toolkit.getDefaultToolkit().beep(); |
425 | 525 break; |
426 boolean cursorPosReceived = false; | 526 |
427 | 527 case RfbProto.ServerCutText: |
428 for (int i = 0; i < rfb.updateNRects; i++) { | 528 String s = rfb.readServerCutText(); |
429 | 529 viewer.clipboard.setCutText(s); |
430 rfb.readFramebufferUpdateRectHdr(); | 530 break; |
431 statNumTotalRects++; | 531 |
432 int rx = rfb.updateRectX, ry = rfb.updateRectY; | 532 default: |
433 int rw = rfb.updateRectW, rh = rfb.updateRectH; | 533 throw new Exception("Unknown RFB message type " + msgType); |
434 | 534 } |
435 if (rfb.updateRectEncoding == rfb.EncodingLastRect) | 535 bufSize = (int)rfb.getNumBytesRead() - bufSize; |
436 break; | 536 rfb.bufResetSend(bufSize); |
437 | 537 } |
438 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) { | 538 } |
439 rfb.setFramebufferSize(rw, rh); | 539 |
440 updateFramebufferSize(); | 540 // |
441 break; | 541 // Handle a raw rectangle. The second form with paint==false is used |
442 } | 542 // by the Hextile decoder for raw-encoded tiles. |
443 | 543 // |
444 if (rfb.updateRectEncoding == rfb.EncodingXCursor | 544 |
445 || rfb.updateRectEncoding == rfb.EncodingRichCursor) { | 545 void handleRawRect(int x, int y, int w, int h) throws IOException { |
446 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, | 546 handleRawRect(x, y, w, h, true); |
447 rw, rh); | 547 } |
548 | |
549 void handleRawRect(int x, int y, int w, int h, boolean paint) | |
550 throws IOException { | |
551 | |
552 if (bytesPixel == 1) { | |
553 for (int dy = y; dy < y + h; dy++) { | |
554 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w); | |
555 if (rfb.rec != null) { | |
556 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); | |
557 } | |
558 } | |
559 } else { | |
560 byte[] buf = new byte[w * 4]; | |
561 int i, offset; | |
562 for (int dy = y; dy < y + h; dy++) { | |
563 rfb.readFully(buf); | |
564 if (rfb.rec != null) { | |
565 rfb.rec.write(buf); | |
566 } | |
567 /* | |
568 * offset = dy * rfb.framebufferWidth + x; for (i = 0; i < w; | |
569 * i++) { pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16 | | |
570 * (buf[i * 4 + 1] & 0xFF) << 8 | (buf[i * 4] & 0xFF); } | |
571 */ | |
572 } | |
573 } | |
574 /* | |
575 * handleUpdatedPixels(x, y, w, h); if (paint) scheduleRepaint(x, y, w, | |
576 * h); | |
577 */ | |
578 } | |
579 | |
580 // | |
581 // Handle a CopyRect rectangle. | |
582 // | |
583 | |
584 void handleCopyRect(int x, int y, int w, int h) throws IOException { | |
585 | |
586 rfb.readCopyRect(); | |
587 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h, x | |
588 - rfb.copyRectSrcX, y - rfb.copyRectSrcY); | |
589 | |
590 scheduleRepaint(x, y, w, h); | |
591 } | |
592 | |
593 // | |
594 // Handle an RRE-encoded rectangle. | |
595 // | |
596 | |
597 void handleRRERect(int x, int y, int w, int h) throws IOException { | |
598 | |
599 int nSubrects = rfb.readU32(); | |
600 | |
601 byte[] bg_buf = new byte[bytesPixel]; | |
602 rfb.readFully(bg_buf); | |
603 Color pixel; | |
604 if (bytesPixel == 1) { | |
605 pixel = colors[bg_buf[0] & 0xFF]; | |
606 } else { | |
607 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, | |
608 bg_buf[0] & 0xFF); | |
609 } | |
610 memGraphics.setColor(pixel); | |
611 memGraphics.fillRect(x, y, w, h); | |
612 | |
613 byte[] buf = new byte[nSubrects * (bytesPixel + 8)]; | |
614 rfb.readFully(buf); | |
615 DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf)); | |
616 | |
617 if (rfb.rec != null) { | |
618 rfb.rec.writeIntBE(nSubrects); | |
619 rfb.rec.write(bg_buf); | |
620 rfb.rec.write(buf); | |
621 } | |
622 | |
623 int sx, sy, sw, sh; | |
624 | |
625 for (int j = 0; j < nSubrects; j++) { | |
626 if (bytesPixel == 1) { | |
627 pixel = colors[ds.readUnsignedByte()]; | |
628 } else { | |
629 ds.skip(4); | |
630 pixel = new Color(buf[j * 12 + 2] & 0xFF, | |
631 buf[j * 12 + 1] & 0xFF, buf[j * 12] & 0xFF); | |
632 } | |
633 sx = x + ds.readUnsignedShort(); | |
634 sy = y + ds.readUnsignedShort(); | |
635 sw = ds.readUnsignedShort(); | |
636 sh = ds.readUnsignedShort(); | |
637 | |
638 memGraphics.setColor(pixel); | |
639 memGraphics.fillRect(sx, sy, sw, sh); | |
640 } | |
641 | |
642 scheduleRepaint(x, y, w, h); | |
643 } | |
644 | |
645 // | |
646 // Handle a CoRRE-encoded rectangle. | |
647 // | |
648 | |
649 void handleCoRRERect(int x, int y, int w, int h) throws IOException { | |
650 int nSubrects = rfb.readU32(); | |
651 | |
652 byte[] bg_buf = new byte[bytesPixel]; | |
653 rfb.readFully(bg_buf); | |
654 Color pixel; | |
655 if (bytesPixel == 1) { | |
656 pixel = colors[bg_buf[0] & 0xFF]; | |
657 } else { | |
658 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, | |
659 bg_buf[0] & 0xFF); | |
660 } | |
661 memGraphics.setColor(pixel); | |
662 memGraphics.fillRect(x, y, w, h); | |
663 | |
664 byte[] buf = new byte[nSubrects * (bytesPixel + 4)]; | |
665 rfb.readFully(buf); | |
666 | |
667 if (rfb.rec != null) { | |
668 rfb.rec.writeIntBE(nSubrects); | |
669 rfb.rec.write(bg_buf); | |
670 rfb.rec.write(buf); | |
671 } | |
672 | |
673 int sx, sy, sw, sh; | |
674 int i = 0; | |
675 | |
676 for (int j = 0; j < nSubrects; j++) { | |
677 if (bytesPixel == 1) { | |
678 pixel = colors[buf[i++] & 0xFF]; | |
679 } else { | |
680 pixel = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF, | |
681 buf[i] & 0xFF); | |
682 i += 4; | |
683 } | |
684 sx = x + (buf[i++] & 0xFF); | |
685 sy = y + (buf[i++] & 0xFF); | |
686 sw = buf[i++] & 0xFF; | |
687 sh = buf[i++] & 0xFF; | |
688 | |
689 memGraphics.setColor(pixel); | |
690 memGraphics.fillRect(sx, sy, sw, sh); | |
691 } | |
692 | |
693 scheduleRepaint(x, y, w, h); | |
694 } | |
695 | |
696 // | |
697 // Handle a Hextile-encoded rectangle. | |
698 // | |
699 | |
700 // These colors should be kept between handleHextileSubrect() calls. | |
701 private Color hextile_bg, hextile_fg; | |
702 | |
703 void handleHextileRect(int x, int y, int w, int h) throws IOException { | |
704 | |
705 hextile_bg = new Color(0); | |
706 hextile_fg = new Color(0); | |
707 | |
708 for (int ty = y; ty < y + h; ty += 16) { | |
709 int th = 16; | |
710 if (y + h - ty < 16) | |
711 th = y + h - ty; | |
712 | |
713 for (int tx = x; tx < x + w; tx += 16) { | |
714 int tw = 16; | |
715 if (x + w - tx < 16) | |
716 tw = x + w - tx; | |
717 | |
718 handleHextileSubrect(tx, ty, tw, th); | |
719 } | |
720 | |
721 // Finished with a row of tiles, now let's show it. | |
722 scheduleRepaint(x, y, w, h); | |
723 } | |
724 } | |
725 | |
726 // | |
727 // Handle one tile in the Hextile-encoded data. | |
728 // | |
729 | |
730 void handleHextileSubrect(int tx, int ty, int tw, int th) | |
731 throws IOException { | |
732 | |
733 int subencoding = rfb.readU8(); | |
734 if (rfb.rec != null) { | |
735 rfb.rec.writeByte(subencoding); | |
736 } | |
737 | |
738 // Is it a raw-encoded sub-rectangle? | |
739 if ((subencoding & rfb.HextileRaw) != 0) { | |
740 handleRawRect(tx, ty, tw, th, false); | |
741 return; | |
742 } | |
743 | |
744 // Read and draw the background if specified. | |
745 byte[] cbuf = new byte[bytesPixel]; | |
746 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) { | |
747 rfb.readFully(cbuf); | |
748 if (bytesPixel == 1) { | |
749 hextile_bg = colors[cbuf[0] & 0xFF]; | |
750 } else { | |
751 hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, | |
752 cbuf[0] & 0xFF); | |
753 } | |
754 if (rfb.rec != null) { | |
755 rfb.rec.write(cbuf); | |
756 } | |
757 } | |
758 memGraphics.setColor(hextile_bg); | |
759 memGraphics.fillRect(tx, ty, tw, th); | |
760 | |
761 // Read the foreground color if specified. | |
762 if ((subencoding & rfb.HextileForegroundSpecified) != 0) { | |
763 rfb.readFully(cbuf); | |
764 if (bytesPixel == 1) { | |
765 hextile_fg = colors[cbuf[0] & 0xFF]; | |
766 } else { | |
767 hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, | |
768 cbuf[0] & 0xFF); | |
769 } | |
770 if (rfb.rec != null) { | |
771 rfb.rec.write(cbuf); | |
772 } | |
773 } | |
774 | |
775 // Done with this tile if there is no sub-rectangles. | |
776 if ((subencoding & rfb.HextileAnySubrects) == 0) | |
777 return; | |
778 | |
779 int nSubrects = rfb.readU8(); | |
780 int bufsize = nSubrects * 2; | |
781 if ((subencoding & rfb.HextileSubrectsColoured) != 0) { | |
782 bufsize += nSubrects * bytesPixel; | |
783 } | |
784 byte[] buf = new byte[bufsize]; | |
785 rfb.readFully(buf); | |
786 if (rfb.rec != null) { | |
787 rfb.rec.writeByte(nSubrects); | |
788 rfb.rec.write(buf); | |
789 } | |
790 | |
791 int b1, b2, sx, sy, sw, sh; | |
792 int i = 0; | |
793 | |
794 if ((subencoding & rfb.HextileSubrectsColoured) == 0) { | |
795 | |
796 // Sub-rectangles are all of the same color. | |
797 memGraphics.setColor(hextile_fg); | |
798 for (int j = 0; j < nSubrects; j++) { | |
799 b1 = buf[i++] & 0xFF; | |
800 b2 = buf[i++] & 0xFF; | |
801 sx = tx + (b1 >> 4); | |
802 sy = ty + (b1 & 0xf); | |
803 sw = (b2 >> 4) + 1; | |
804 sh = (b2 & 0xf) + 1; | |
805 memGraphics.fillRect(sx, sy, sw, sh); | |
806 } | |
807 } else if (bytesPixel == 1) { | |
808 | |
809 // BGR233 (8-bit color) version for colored sub-rectangles. | |
810 for (int j = 0; j < nSubrects; j++) { | |
811 hextile_fg = colors[buf[i++] & 0xFF]; | |
812 b1 = buf[i++] & 0xFF; | |
813 b2 = buf[i++] & 0xFF; | |
814 sx = tx + (b1 >> 4); | |
815 sy = ty + (b1 & 0xf); | |
816 sw = (b2 >> 4) + 1; | |
817 sh = (b2 & 0xf) + 1; | |
818 memGraphics.setColor(hextile_fg); | |
819 memGraphics.fillRect(sx, sy, sw, sh); | |
820 } | |
821 | |
822 } else { | |
823 | |
824 // Full-color (24-bit) version for colored sub-rectangles. | |
825 for (int j = 0; j < nSubrects; j++) { | |
826 hextile_fg = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF, | |
827 buf[i] & 0xFF); | |
828 i += 4; | |
829 b1 = buf[i++] & 0xFF; | |
830 b2 = buf[i++] & 0xFF; | |
831 sx = tx + (b1 >> 4); | |
832 sy = ty + (b1 & 0xf); | |
833 sw = (b2 >> 4) + 1; | |
834 sh = (b2 & 0xf) + 1; | |
835 memGraphics.setColor(hextile_fg); | |
836 memGraphics.fillRect(sx, sy, sw, sh); | |
837 } | |
838 | |
839 } | |
840 } | |
841 | |
842 // | |
843 // Handle a ZRLE-encoded rectangle. | |
844 // | |
845 // FIXME: Currently, session recording is not fully supported for ZRLE. | |
846 // | |
847 | |
848 void handleZRLERect(int x, int y, int w, int h) throws Exception { | |
849 | |
850 if (zrleInStream == null) | |
851 zrleInStream = new ZlibInStream(); | |
852 | |
853 int nBytes = rfb.readU32(); | |
854 if (nBytes > 64 * 1024 * 1024) | |
855 throw new Exception("ZRLE decoder: illegal compressed data size"); | |
856 | |
857 if (zrleBuf == null || zrleBufLen < nBytes) { | |
858 zrleBufLen = nBytes + 4096; | |
859 zrleBuf = new byte[zrleBufLen]; | |
860 } | |
861 | |
862 // FIXME: Do not wait for all the data before decompression. | |
863 rfb.readFully(zrleBuf, 0, nBytes); | |
864 | |
865 if (rfb.rec != null) { | |
866 if (rfb.recordFromBeginning) { | |
867 rfb.rec.writeIntBE(nBytes); | |
868 rfb.rec.write(zrleBuf, 0, nBytes); | |
869 } else if (!zrleRecWarningShown) { | |
870 System.out.println("Warning: ZRLE session can be recorded" | |
871 + " only from the beginning"); | |
872 System.out.println("Warning: Recorded file may be corrupted"); | |
873 zrleRecWarningShown = true; | |
874 } | |
875 | |
876 } | |
877 | |
878 zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes); | |
879 | |
880 for (int ty = y; ty < y + h; ty += 64) { | |
881 | |
882 int th = Math.min(y + h - ty, 64); | |
883 | |
884 for (int tx = x; tx < x + w; tx += 64) { | |
885 | |
886 int tw = Math.min(x + w - tx, 64); | |
887 | |
888 int mode = zrleInStream.readU8(); | |
889 boolean rle = (mode & 128) != 0; | |
890 int palSize = mode & 127; | |
891 int[] palette = new int[128]; | |
892 | |
893 readZrlePalette(palette, palSize); | |
894 | |
895 if (palSize == 1) { | |
896 int pix = palette[0]; | |
897 Color c = (bytesPixel == 1) ? colors[pix] : new Color( | |
898 0xFF000000 | pix); | |
899 memGraphics.setColor(c); | |
900 memGraphics.fillRect(tx, ty, tw, th); | |
448 continue; | 901 continue; |
449 } | 902 } |
450 | 903 |
451 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) { | 904 if (!rle) { |
452 softCursorMove(rx, ry); | 905 if (palSize == 0) { |
453 cursorPosReceived = true; | 906 readZrleRawPixels(tw, th); |
454 continue; | 907 } else { |
455 } | 908 readZrlePackedPixels(tw, th, palette, palSize); |
456 | 909 } |
457 long numBytesReadBefore = rfb.getNumBytesRead(); | 910 } else { |
458 | 911 if (palSize == 0) { |
459 rfb.startTiming(); | 912 readZrlePlainRLEPixels(tw, th); |
460 | 913 } else { |
461 switch (rfb.updateRectEncoding) { | 914 readZrlePackedRLEPixels(tw, th, palette); |
462 case RfbProto.EncodingRaw: | 915 } |
463 statNumRectsRaw++; | 916 } |
464 handleRawRect(rx, ry, rw, rh); | 917 handleUpdatedZrleTile(tx, ty, tw, th); |
465 break; | 918 } |
466 case RfbProto.EncodingCopyRect: | 919 } |
467 statNumRectsCopy++; | 920 |
468 handleCopyRect(rx, ry, rw, rh); | 921 zrleInStream.reset(); |
469 break; | 922 |
470 case RfbProto.EncodingRRE: | |
471 handleRRERect(rx, ry, rw, rh); | |
472 break; | |
473 case RfbProto.EncodingCoRRE: | |
474 handleCoRRERect(rx, ry, rw, rh); | |
475 break; | |
476 case RfbProto.EncodingHextile: | |
477 statNumRectsHextile++; | |
478 handleHextileRect(rx, ry, rw, rh); | |
479 break; | |
480 case RfbProto.EncodingZRLE: | |
481 statNumRectsZRLE++; | |
482 handleZRLERect(rx, ry, rw, rh); | |
483 break; | |
484 case RfbProto.EncodingZlib: | |
485 handleZlibRect(rx, ry, rw, rh); | |
486 break; | |
487 case RfbProto.EncodingTight: | |
488 statNumRectsTight++; | |
489 handleTightRect(rx, ry, rw, rh); | |
490 break; | |
491 default: | |
492 throw new Exception("Unknown RFB rectangle encoding " | |
493 + rfb.updateRectEncoding); | |
494 } | |
495 | |
496 rfb.stopTiming(); | |
497 | |
498 statNumPixelRects++; | |
499 statNumBytesDecoded += rw * rh * bytesPixel; | |
500 statNumBytesEncoded += (int) (rfb.getNumBytesRead() - numBytesReadBefore); | |
501 } | |
502 | |
503 boolean fullUpdateNeeded = false; | |
504 | |
505 // Start/stop session recording if necessary. Request full | |
506 // update if a new session file was opened. | |
507 if (viewer.checkRecordingStatus()) | |
508 fullUpdateNeeded = true; | |
509 | |
510 // Defer framebuffer update request if necessary. But wake up | |
511 // immediately on keyboard or mouse event. Also, don't sleep | |
512 // if there is some data to receive, or if the last update | |
513 // included a PointerPos message. | |
514 if (viewer.deferUpdateRequests > 0 && rfb.available() == 0 | |
515 && !cursorPosReceived) { | |
516 synchronized (rfb) { | |
517 try { | |
518 rfb.wait(viewer.deferUpdateRequests); | |
519 } catch (InterruptedException e) { | |
520 } | |
521 } | |
522 } | |
523 | |
524 viewer.autoSelectEncodings(); | |
525 | |
526 // Before requesting framebuffer update, check if the pixel | |
527 // format should be changed. | |
528 | |
529 if (viewer.options.eightBitColors != (bytesPixel == 1)) { | |
530 // Pixel format should be changed. | |
531 setPixelFormat(); | |
532 fullUpdateNeeded = true; | |
533 } | |
534 | |
535 | |
536 // Request framebuffer update if needed. | |
537 int w = rfb.framebufferWidth; | |
538 int h = rfb.framebufferHeight; | |
539 rfb.writeFramebufferUpdateRequest(0, 0, w, h, !fullUpdateNeeded); | |
540 | |
541 break; | |
542 | |
543 case RfbProto.SetColourMapEntries: | |
544 throw new Exception("Can't handle SetColourMapEntries message"); | |
545 | |
546 case RfbProto.Bell: | |
547 Toolkit.getDefaultToolkit().beep(); | |
548 break; | |
549 | |
550 case RfbProto.ServerCutText: | |
551 String s = rfb.readServerCutText(); | |
552 viewer.clipboard.setCutText(s); | |
553 break; | |
554 | |
555 default: | |
556 throw new Exception("Unknown RFB message type " + msgType); | |
557 } | |
558 */ | |
559 } | |
560 } | |
561 | |
562 // | |
563 // Handle a raw rectangle. The second form with paint==false is used | |
564 // by the Hextile decoder for raw-encoded tiles. | |
565 // | |
566 | |
567 void handleRawRect(int x, int y, int w, int h) throws IOException { | |
568 handleRawRect(x, y, w, h, true); | |
569 } | |
570 | |
571 void handleRawRect(int x, int y, int w, int h, boolean paint) | |
572 throws IOException { | |
573 | |
574 if (bytesPixel == 1) { | |
575 for (int dy = y; dy < y + h; dy++) { | |
576 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w); | |
577 if (rfb.rec != null) { | |
578 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); | |
579 } | |
580 } | |
581 } else { | |
582 byte[] buf = new byte[w * 4]; | |
583 int i, offset; | |
584 for (int dy = y; dy < y + h; dy++) { | |
585 rfb.readFully(buf); | |
586 if (rfb.rec != null) { | |
587 rfb.rec.write(buf); | |
588 } | |
589 /* | |
590 offset = dy * rfb.framebufferWidth + x; | |
591 for (i = 0; i < w; i++) { | |
592 pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16 | |
593 | (buf[i * 4 + 1] & 0xFF) << 8 | |
594 | (buf[i * 4] & 0xFF); | |
595 } | |
596 */ | |
597 } | |
598 } | |
599 /* | |
600 handleUpdatedPixels(x, y, w, h); | |
601 if (paint) | |
602 scheduleRepaint(x, y, w, h); | 923 scheduleRepaint(x, y, w, h); |
603 */ | 924 } |
604 } | 925 |
605 | 926 int readPixel(InStream is) throws Exception { |
606 // | 927 int pix; |
607 // Handle a CopyRect rectangle. | 928 |
608 // | |
609 | |
610 void handleCopyRect(int x, int y, int w, int h) throws IOException { | |
611 | |
612 rfb.readCopyRect(); | |
613 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h, x | |
614 - rfb.copyRectSrcX, y - rfb.copyRectSrcY); | |
615 | |
616 scheduleRepaint(x, y, w, h); | |
617 } | |
618 | |
619 // | |
620 // Handle an RRE-encoded rectangle. | |
621 // | |
622 | |
623 void handleRRERect(int x, int y, int w, int h) throws IOException { | |
624 | |
625 int nSubrects = rfb.readU32(); | |
626 | |
627 byte[] bg_buf = new byte[bytesPixel]; | |
628 rfb.readFully(bg_buf); | |
629 Color pixel; | |
630 if (bytesPixel == 1) { | |
631 pixel = colors[bg_buf[0] & 0xFF]; | |
632 } else { | |
633 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, | |
634 bg_buf[0] & 0xFF); | |
635 } | |
636 memGraphics.setColor(pixel); | |
637 memGraphics.fillRect(x, y, w, h); | |
638 | |
639 byte[] buf = new byte[nSubrects * (bytesPixel + 8)]; | |
640 rfb.readFully(buf); | |
641 DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf)); | |
642 | |
643 if (rfb.rec != null) { | |
644 rfb.rec.writeIntBE(nSubrects); | |
645 rfb.rec.write(bg_buf); | |
646 rfb.rec.write(buf); | |
647 } | |
648 | |
649 int sx, sy, sw, sh; | |
650 | |
651 for (int j = 0; j < nSubrects; j++) { | |
652 if (bytesPixel == 1) { | 929 if (bytesPixel == 1) { |
653 pixel = colors[ds.readUnsignedByte()]; | 930 |
931 pix = is.readU8(); | |
654 } else { | 932 } else { |
655 ds.skip(4); | 933 int p1 = is.readU8(); |
656 pixel = new Color(buf[j * 12 + 2] & 0xFF, | 934 int p2 = is.readU8(); |
657 buf[j * 12 + 1] & 0xFF, buf[j * 12] & 0xFF); | 935 int p3 = is.readU8(); |
658 } | 936 pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF); |
659 sx = x + ds.readUnsignedShort(); | 937 } |
660 sy = y + ds.readUnsignedShort(); | 938 return pix; |
661 sw = ds.readUnsignedShort(); | 939 } |
662 sh = ds.readUnsignedShort(); | 940 |
663 | 941 void readPixels(InStream is, int[] dst, int count) throws Exception { |
664 memGraphics.setColor(pixel); | 942 int pix; |
665 memGraphics.fillRect(sx, sy, sw, sh); | |
666 } | |
667 | |
668 scheduleRepaint(x, y, w, h); | |
669 } | |
670 | |
671 // | |
672 // Handle a CoRRE-encoded rectangle. | |
673 // | |
674 | |
675 void handleCoRRERect(int x, int y, int w, int h) throws IOException { | |
676 int nSubrects = rfb.readU32(); | |
677 | |
678 byte[] bg_buf = new byte[bytesPixel]; | |
679 rfb.readFully(bg_buf); | |
680 Color pixel; | |
681 if (bytesPixel == 1) { | |
682 pixel = colors[bg_buf[0] & 0xFF]; | |
683 } else { | |
684 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, | |
685 bg_buf[0] & 0xFF); | |
686 } | |
687 memGraphics.setColor(pixel); | |
688 memGraphics.fillRect(x, y, w, h); | |
689 | |
690 byte[] buf = new byte[nSubrects * (bytesPixel + 4)]; | |
691 rfb.readFully(buf); | |
692 | |
693 if (rfb.rec != null) { | |
694 rfb.rec.writeIntBE(nSubrects); | |
695 rfb.rec.write(bg_buf); | |
696 rfb.rec.write(buf); | |
697 } | |
698 | |
699 int sx, sy, sw, sh; | |
700 int i = 0; | |
701 | |
702 for (int j = 0; j < nSubrects; j++) { | |
703 if (bytesPixel == 1) { | 943 if (bytesPixel == 1) { |
704 pixel = colors[buf[i++] & 0xFF]; | 944 byte[] buf = new byte[count]; |
945 is.readBytes(buf, 0, count); | |
946 for (int i = 0; i < count; i++) { | |
947 dst[i] = (int) buf[i] & 0xFF; | |
948 } | |
705 } else { | 949 } else { |
706 pixel = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF, | 950 byte[] buf = new byte[count * 3]; |
707 buf[i] & 0xFF); | 951 is.readBytes(buf, 0, count * 3); |
708 i += 4; | 952 for (int i = 0; i < count; i++) { |
709 } | 953 dst[i] = ((buf[i * 3 + 2] & 0xFF) << 16 |
710 sx = x + (buf[i++] & 0xFF); | 954 | (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3] & 0xFF)); |
711 sy = y + (buf[i++] & 0xFF); | 955 } |
712 sw = buf[i++] & 0xFF; | 956 } |
713 sh = buf[i++] & 0xFF; | 957 } |
714 | 958 |
715 memGraphics.setColor(pixel); | 959 void readZrlePalette(int[] palette, int palSize) throws Exception { |
716 memGraphics.fillRect(sx, sy, sw, sh); | 960 readPixels(zrleInStream, palette, palSize); |
717 } | 961 } |
718 | 962 |
719 scheduleRepaint(x, y, w, h); | 963 void readZrleRawPixels(int tw, int th) throws Exception { |
720 } | |
721 | |
722 // | |
723 // Handle a Hextile-encoded rectangle. | |
724 // | |
725 | |
726 // These colors should be kept between handleHextileSubrect() calls. | |
727 private Color hextile_bg, hextile_fg; | |
728 | |
729 void handleHextileRect(int x, int y, int w, int h) throws IOException { | |
730 | |
731 hextile_bg = new Color(0); | |
732 hextile_fg = new Color(0); | |
733 | |
734 for (int ty = y; ty < y + h; ty += 16) { | |
735 int th = 16; | |
736 if (y + h - ty < 16) | |
737 th = y + h - ty; | |
738 | |
739 for (int tx = x; tx < x + w; tx += 16) { | |
740 int tw = 16; | |
741 if (x + w - tx < 16) | |
742 tw = x + w - tx; | |
743 | |
744 handleHextileSubrect(tx, ty, tw, th); | |
745 } | |
746 | |
747 // Finished with a row of tiles, now let's show it. | |
748 scheduleRepaint(x, y, w, h); | |
749 } | |
750 } | |
751 | |
752 // | |
753 // Handle one tile in the Hextile-encoded data. | |
754 // | |
755 | |
756 void handleHextileSubrect(int tx, int ty, int tw, int th) | |
757 throws IOException { | |
758 | |
759 int subencoding = rfb.readU8(); | |
760 if (rfb.rec != null) { | |
761 rfb.rec.writeByte(subencoding); | |
762 } | |
763 | |
764 // Is it a raw-encoded sub-rectangle? | |
765 if ((subencoding & rfb.HextileRaw) != 0) { | |
766 handleRawRect(tx, ty, tw, th, false); | |
767 return; | |
768 } | |
769 | |
770 // Read and draw the background if specified. | |
771 byte[] cbuf = new byte[bytesPixel]; | |
772 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) { | |
773 rfb.readFully(cbuf); | |
774 if (bytesPixel == 1) { | 964 if (bytesPixel == 1) { |
775 hextile_bg = colors[cbuf[0] & 0xFF]; | 965 zrleInStream.readBytes(zrleTilePixels8, 0, tw * th); |
776 } else { | 966 } else { |
777 hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, | 967 readPixels(zrleInStream, zrleTilePixels24, tw * th); // / |
778 cbuf[0] & 0xFF); | 968 } |
779 } | 969 } |
780 if (rfb.rec != null) { | 970 |
781 rfb.rec.write(cbuf); | 971 void readZrlePackedPixels(int tw, int th, int[] palette, int palSize) |
782 } | 972 throws Exception { |
783 } | 973 |
784 memGraphics.setColor(hextile_bg); | 974 int bppp = ((palSize > 16) ? 8 : ((palSize > 4) ? 4 |
785 memGraphics.fillRect(tx, ty, tw, th); | 975 : ((palSize > 2) ? 2 : 1))); |
786 | 976 int ptr = 0; |
787 // Read the foreground color if specified. | 977 |
788 if ((subencoding & rfb.HextileForegroundSpecified) != 0) { | 978 for (int i = 0; i < th; i++) { |
789 rfb.readFully(cbuf); | 979 int eol = ptr + tw; |
790 if (bytesPixel == 1) { | 980 int b = 0; |
791 hextile_fg = colors[cbuf[0] & 0xFF]; | 981 int nbits = 0; |
792 } else { | 982 |
793 hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, | 983 while (ptr < eol) { |
794 cbuf[0] & 0xFF); | 984 if (nbits == 0) { |
795 } | 985 b = zrleInStream.readU8(); |
796 if (rfb.rec != null) { | 986 nbits = 8; |
797 rfb.rec.write(cbuf); | 987 } |
798 } | 988 nbits -= bppp; |
799 } | 989 int index = (b >> nbits) & ((1 << bppp) - 1) & 127; |
800 | 990 if (bytesPixel == 1) { |
801 // Done with this tile if there is no sub-rectangles. | 991 zrleTilePixels8[ptr++] = (byte) palette[index]; |
802 if ((subencoding & rfb.HextileAnySubrects) == 0) | |
803 return; | |
804 | |
805 int nSubrects = rfb.readU8(); | |
806 int bufsize = nSubrects * 2; | |
807 if ((subencoding & rfb.HextileSubrectsColoured) != 0) { | |
808 bufsize += nSubrects * bytesPixel; | |
809 } | |
810 byte[] buf = new byte[bufsize]; | |
811 rfb.readFully(buf); | |
812 if (rfb.rec != null) { | |
813 rfb.rec.writeByte(nSubrects); | |
814 rfb.rec.write(buf); | |
815 } | |
816 | |
817 int b1, b2, sx, sy, sw, sh; | |
818 int i = 0; | |
819 | |
820 if ((subencoding & rfb.HextileSubrectsColoured) == 0) { | |
821 | |
822 // Sub-rectangles are all of the same color. | |
823 memGraphics.setColor(hextile_fg); | |
824 for (int j = 0; j < nSubrects; j++) { | |
825 b1 = buf[i++] & 0xFF; | |
826 b2 = buf[i++] & 0xFF; | |
827 sx = tx + (b1 >> 4); | |
828 sy = ty + (b1 & 0xf); | |
829 sw = (b2 >> 4) + 1; | |
830 sh = (b2 & 0xf) + 1; | |
831 memGraphics.fillRect(sx, sy, sw, sh); | |
832 } | |
833 } else if (bytesPixel == 1) { | |
834 | |
835 // BGR233 (8-bit color) version for colored sub-rectangles. | |
836 for (int j = 0; j < nSubrects; j++) { | |
837 hextile_fg = colors[buf[i++] & 0xFF]; | |
838 b1 = buf[i++] & 0xFF; | |
839 b2 = buf[i++] & 0xFF; | |
840 sx = tx + (b1 >> 4); | |
841 sy = ty + (b1 & 0xf); | |
842 sw = (b2 >> 4) + 1; | |
843 sh = (b2 & 0xf) + 1; | |
844 memGraphics.setColor(hextile_fg); | |
845 memGraphics.fillRect(sx, sy, sw, sh); | |
846 } | |
847 | |
848 } else { | |
849 | |
850 // Full-color (24-bit) version for colored sub-rectangles. | |
851 for (int j = 0; j < nSubrects; j++) { | |
852 hextile_fg = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF, | |
853 buf[i] & 0xFF); | |
854 i += 4; | |
855 b1 = buf[i++] & 0xFF; | |
856 b2 = buf[i++] & 0xFF; | |
857 sx = tx + (b1 >> 4); | |
858 sy = ty + (b1 & 0xf); | |
859 sw = (b2 >> 4) + 1; | |
860 sh = (b2 & 0xf) + 1; | |
861 memGraphics.setColor(hextile_fg); | |
862 memGraphics.fillRect(sx, sy, sw, sh); | |
863 } | |
864 | |
865 } | |
866 } | |
867 | |
868 // | |
869 // Handle a ZRLE-encoded rectangle. | |
870 // | |
871 // FIXME: Currently, session recording is not fully supported for ZRLE. | |
872 // | |
873 | |
874 void handleZRLERect(int x, int y, int w, int h) throws Exception { | |
875 | |
876 if (zrleInStream == null) | |
877 zrleInStream = new ZlibInStream(); | |
878 | |
879 int nBytes = rfb.readU32(); | |
880 if (nBytes > 64 * 1024 * 1024) | |
881 throw new Exception("ZRLE decoder: illegal compressed data size"); | |
882 | |
883 if (zrleBuf == null || zrleBufLen < nBytes) { | |
884 zrleBufLen = nBytes + 4096; | |
885 zrleBuf = new byte[zrleBufLen]; | |
886 } | |
887 | |
888 // FIXME: Do not wait for all the data before decompression. | |
889 rfb.readFully(zrleBuf, 0, nBytes); | |
890 | |
891 if (rfb.rec != null) { | |
892 if (rfb.recordFromBeginning) { | |
893 rfb.rec.writeIntBE(nBytes); | |
894 rfb.rec.write(zrleBuf, 0, nBytes); | |
895 } else if (!zrleRecWarningShown) { | |
896 System.out.println("Warning: ZRLE session can be recorded" | |
897 + " only from the beginning"); | |
898 System.out.println("Warning: Recorded file may be corrupted"); | |
899 zrleRecWarningShown = true; | |
900 } | |
901 | |
902 } | |
903 | |
904 zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes); | |
905 | |
906 for (int ty = y; ty < y + h; ty += 64) { | |
907 | |
908 int th = Math.min(y + h - ty, 64); | |
909 | |
910 for (int tx = x; tx < x + w; tx += 64) { | |
911 | |
912 int tw = Math.min(x + w - tx, 64); | |
913 | |
914 int mode = zrleInStream.readU8(); | |
915 boolean rle = (mode & 128) != 0; | |
916 int palSize = mode & 127; | |
917 int[] palette = new int[128]; | |
918 | |
919 readZrlePalette(palette, palSize); | |
920 | |
921 if (palSize == 1) { | |
922 int pix = palette[0]; | |
923 Color c = (bytesPixel == 1) ? colors[pix] : new Color( | |
924 0xFF000000 | pix); | |
925 memGraphics.setColor(c); | |
926 memGraphics.fillRect(tx, ty, tw, th); | |
927 continue; | |
928 } | |
929 | |
930 if (!rle) { | |
931 if (palSize == 0) { | |
932 readZrleRawPixels(tw, th); | |
933 } else { | 992 } else { |
934 readZrlePackedPixels(tw, th, palette, palSize); | 993 zrleTilePixels24[ptr++] = palette[index]; |
935 } | 994 } |
936 } else { | 995 } |
937 if (palSize == 0) { | 996 } |
938 readZrlePlainRLEPixels(tw, th); | 997 } |
939 } else { | 998 |
940 readZrlePackedRLEPixels(tw, th, palette); | 999 void readZrlePlainRLEPixels(int tw, int th) throws Exception { |
941 } | 1000 int ptr = 0; |
942 } | 1001 int end = ptr + tw * th; |
943 handleUpdatedZrleTile(tx, ty, tw, th); | 1002 while (ptr < end) { |
944 } | 1003 int pix = readPixel(zrleInStream); |
945 } | 1004 int len = 1; |
946 | |
947 zrleInStream.reset(); | |
948 | |
949 scheduleRepaint(x, y, w, h); | |
950 } | |
951 | |
952 int readPixel(InStream is) throws Exception { | |
953 int pix; | |
954 | |
955 if (bytesPixel == 1) { | |
956 | |
957 pix = is.readU8(); | |
958 } else { | |
959 int p1 = is.readU8(); | |
960 int p2 = is.readU8(); | |
961 int p3 = is.readU8(); | |
962 pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF); | |
963 } | |
964 return pix; | |
965 } | |
966 | |
967 void readPixels(InStream is, int[] dst, int count) throws Exception { | |
968 int pix; | |
969 if (bytesPixel == 1) { | |
970 byte[] buf = new byte[count]; | |
971 is.readBytes(buf, 0, count); | |
972 for (int i = 0; i < count; i++) { | |
973 dst[i] = (int) buf[i] & 0xFF; | |
974 } | |
975 } else { | |
976 byte[] buf = new byte[count * 3]; | |
977 is.readBytes(buf, 0, count * 3); | |
978 for (int i = 0; i < count; i++) { | |
979 dst[i] = ((buf[i * 3 + 2] & 0xFF) << 16 | |
980 | (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3] & 0xFF)); | |
981 } | |
982 } | |
983 } | |
984 | |
985 void readZrlePalette(int[] palette, int palSize) throws Exception { | |
986 readPixels(zrleInStream, palette, palSize); | |
987 } | |
988 | |
989 void readZrleRawPixels(int tw, int th) throws Exception { | |
990 if (bytesPixel == 1) { | |
991 zrleInStream.readBytes(zrleTilePixels8, 0, tw * th); | |
992 } else { | |
993 readPixels(zrleInStream, zrleTilePixels24, tw * th); // / | |
994 } | |
995 } | |
996 | |
997 void readZrlePackedPixels(int tw, int th, int[] palette, int palSize) | |
998 throws Exception { | |
999 | |
1000 int bppp = ((palSize > 16) ? 8 : ((palSize > 4) ? 4 | |
1001 : ((palSize > 2) ? 2 : 1))); | |
1002 int ptr = 0; | |
1003 | |
1004 for (int i = 0; i < th; i++) { | |
1005 int eol = ptr + tw; | |
1006 int b = 0; | |
1007 int nbits = 0; | |
1008 | |
1009 while (ptr < eol) { | |
1010 if (nbits == 0) { | |
1011 b = zrleInStream.readU8(); | |
1012 nbits = 8; | |
1013 } | |
1014 nbits -= bppp; | |
1015 int index = (b >> nbits) & ((1 << bppp) - 1) & 127; | |
1016 if (bytesPixel == 1) { | |
1017 zrleTilePixels8[ptr++] = (byte) palette[index]; | |
1018 } else { | |
1019 zrleTilePixels24[ptr++] = palette[index]; | |
1020 } | |
1021 } | |
1022 } | |
1023 } | |
1024 | |
1025 void readZrlePlainRLEPixels(int tw, int th) throws Exception { | |
1026 int ptr = 0; | |
1027 int end = ptr + tw * th; | |
1028 while (ptr < end) { | |
1029 int pix = readPixel(zrleInStream); | |
1030 int len = 1; | |
1031 int b; | |
1032 do { | |
1033 b = zrleInStream.readU8(); | |
1034 len += b; | |
1035 } while (b == 255); | |
1036 | |
1037 if (!(len <= end - ptr)) | |
1038 throw new Exception("ZRLE decoder: assertion failed" | |
1039 + " (len <= end-ptr)"); | |
1040 | |
1041 if (bytesPixel == 1) { | |
1042 while (len-- > 0) | |
1043 zrleTilePixels8[ptr++] = (byte) pix; | |
1044 } else { | |
1045 while (len-- > 0) | |
1046 zrleTilePixels24[ptr++] = pix; | |
1047 } | |
1048 } | |
1049 } | |
1050 | |
1051 void readZrlePackedRLEPixels(int tw, int th, int[] palette) | |
1052 throws Exception { | |
1053 | |
1054 int ptr = 0; | |
1055 int end = ptr + tw * th; | |
1056 while (ptr < end) { | |
1057 int index = zrleInStream.readU8(); | |
1058 int len = 1; | |
1059 if ((index & 128) != 0) { | |
1060 int b; | 1005 int b; |
1061 do { | 1006 do { |
1062 b = zrleInStream.readU8(); | 1007 b = zrleInStream.readU8(); |
1063 len += b; | 1008 len += b; |
1064 } while (b == 255); | 1009 } while (b == 255); |
1065 | 1010 |
1066 if (!(len <= end - ptr)) | 1011 if (!(len <= end - ptr)) |
1067 throw new Exception("ZRLE decoder: assertion failed" | 1012 throw new Exception("ZRLE decoder: assertion failed" |
1068 + " (len <= end - ptr)"); | 1013 + " (len <= end-ptr)"); |
1069 } | 1014 |
1070 | 1015 if (bytesPixel == 1) { |
1071 index &= 127; | 1016 while (len-- > 0) |
1072 int pix = palette[index]; | 1017 zrleTilePixels8[ptr++] = (byte) pix; |
1073 | 1018 } else { |
1019 while (len-- > 0) | |
1020 zrleTilePixels24[ptr++] = pix; | |
1021 } | |
1022 } | |
1023 } | |
1024 | |
1025 void readZrlePackedRLEPixels(int tw, int th, int[] palette) | |
1026 throws Exception { | |
1027 | |
1028 int ptr = 0; | |
1029 int end = ptr + tw * th; | |
1030 while (ptr < end) { | |
1031 int index = zrleInStream.readU8(); | |
1032 int len = 1; | |
1033 if ((index & 128) != 0) { | |
1034 int b; | |
1035 do { | |
1036 b = zrleInStream.readU8(); | |
1037 len += b; | |
1038 } while (b == 255); | |
1039 | |
1040 if (!(len <= end - ptr)) | |
1041 throw new Exception("ZRLE decoder: assertion failed" | |
1042 + " (len <= end - ptr)"); | |
1043 } | |
1044 | |
1045 index &= 127; | |
1046 int pix = palette[index]; | |
1047 | |
1048 if (bytesPixel == 1) { | |
1049 while (len-- > 0) | |
1050 zrleTilePixels8[ptr++] = (byte) pix; | |
1051 } else { | |
1052 while (len-- > 0) | |
1053 zrleTilePixels24[ptr++] = pix; | |
1054 } | |
1055 } | |
1056 } | |
1057 | |
1058 // | |
1059 // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update. | |
1060 // | |
1061 | |
1062 void handleUpdatedZrleTile(int x, int y, int w, int h) { | |
1063 Object src, dst; | |
1074 if (bytesPixel == 1) { | 1064 if (bytesPixel == 1) { |
1075 while (len-- > 0) | 1065 src = zrleTilePixels8; |
1076 zrleTilePixels8[ptr++] = (byte) pix; | 1066 dst = pixels8; |
1077 } else { | 1067 } else { |
1078 while (len-- > 0) | 1068 src = zrleTilePixels24; |
1079 zrleTilePixels24[ptr++] = pix; | 1069 dst = pixels24; |
1080 } | 1070 } |
1081 } | 1071 int offsetSrc = 0; |
1082 } | 1072 int offsetDst = (y * rfb.framebufferWidth + x); |
1083 | 1073 for (int j = 0; j < h; j++) { |
1084 // | 1074 System.arraycopy(src, offsetSrc, dst, offsetDst, w); |
1085 // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update. | 1075 offsetSrc += w; |
1086 // | 1076 offsetDst += rfb.framebufferWidth; |
1087 | 1077 } |
1088 void handleUpdatedZrleTile(int x, int y, int w, int h) { | 1078 handleUpdatedPixels(x, y, w, h); |
1089 Object src, dst; | 1079 } |
1090 if (bytesPixel == 1) { | 1080 |
1091 src = zrleTilePixels8; | 1081 // |
1092 dst = pixels8; | 1082 // Handle a Zlib-encoded rectangle. |
1093 } else { | 1083 // |
1094 src = zrleTilePixels24; | 1084 |
1095 dst = pixels24; | 1085 void handleZlibRect(int x, int y, int w, int h) throws Exception { |
1096 } | 1086 |
1097 int offsetSrc = 0; | 1087 int nBytes = rfb.readU32(); |
1098 int offsetDst = (y * rfb.framebufferWidth + x); | 1088 |
1099 for (int j = 0; j < h; j++) { | 1089 if (zlibBuf == null || zlibBufLen < nBytes) { |
1100 System.arraycopy(src, offsetSrc, dst, offsetDst, w); | 1090 zlibBufLen = nBytes * 2; |
1101 offsetSrc += w; | 1091 zlibBuf = new byte[zlibBufLen]; |
1102 offsetDst += rfb.framebufferWidth; | 1092 } |
1103 } | 1093 |
1104 handleUpdatedPixels(x, y, w, h); | 1094 rfb.readFully(zlibBuf, 0, nBytes); |
1105 } | 1095 |
1106 | 1096 if (rfb.rec != null && rfb.recordFromBeginning) { |
1107 // | 1097 rfb.rec.writeIntBE(nBytes); |
1108 // Handle a Zlib-encoded rectangle. | 1098 rfb.rec.write(zlibBuf, 0, nBytes); |
1109 // | 1099 } |
1110 | 1100 |
1111 void handleZlibRect(int x, int y, int w, int h) throws Exception { | 1101 if (zlibInflater == null) { |
1112 | 1102 zlibInflater = new Inflater(); |
1113 int nBytes = rfb.readU32(); | 1103 } |
1114 | 1104 zlibInflater.setInput(zlibBuf, 0, nBytes); |
1115 if (zlibBuf == null || zlibBufLen < nBytes) { | 1105 |
1116 zlibBufLen = nBytes * 2; | 1106 if (bytesPixel == 1) { |
1117 zlibBuf = new byte[zlibBufLen]; | 1107 for (int dy = y; dy < y + h; dy++) { |
1118 } | 1108 zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w); |
1119 | 1109 if (rfb.rec != null && !rfb.recordFromBeginning) |
1120 rfb.readFully(zlibBuf, 0, nBytes); | 1110 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); |
1121 | 1111 } |
1122 if (rfb.rec != null && rfb.recordFromBeginning) { | |
1123 rfb.rec.writeIntBE(nBytes); | |
1124 rfb.rec.write(zlibBuf, 0, nBytes); | |
1125 } | |
1126 | |
1127 if (zlibInflater == null) { | |
1128 zlibInflater = new Inflater(); | |
1129 } | |
1130 zlibInflater.setInput(zlibBuf, 0, nBytes); | |
1131 | |
1132 if (bytesPixel == 1) { | |
1133 for (int dy = y; dy < y + h; dy++) { | |
1134 zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w); | |
1135 if (rfb.rec != null && !rfb.recordFromBeginning) | |
1136 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); | |
1137 } | |
1138 } else { | |
1139 byte[] buf = new byte[w * 4]; | |
1140 int i, offset; | |
1141 for (int dy = y; dy < y + h; dy++) { | |
1142 zlibInflater.inflate(buf); | |
1143 offset = dy * rfb.framebufferWidth + x; | |
1144 for (i = 0; i < w; i++) { | |
1145 pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16 | |
1146 | (buf[i * 4 + 1] & 0xFF) << 8 | |
1147 | (buf[i * 4] & 0xFF); | |
1148 } | |
1149 if (rfb.rec != null && !rfb.recordFromBeginning) | |
1150 rfb.rec.write(buf); | |
1151 } | |
1152 } | |
1153 | |
1154 handleUpdatedPixels(x, y, w, h); | |
1155 scheduleRepaint(x, y, w, h); | |
1156 } | |
1157 | |
1158 // | |
1159 // Handle a Tight-encoded rectangle. | |
1160 // | |
1161 | |
1162 void handleTightRect(int x, int y, int w, int h) throws Exception { | |
1163 | |
1164 int comp_ctl = rfb.readU8(); | |
1165 if (rfb.rec != null) { | |
1166 if (rfb.recordFromBeginning || comp_ctl == (rfb.TightFill << 4) | |
1167 || comp_ctl == (rfb.TightJpeg << 4)) { | |
1168 // Send data exactly as received. | |
1169 rfb.rec.writeByte(comp_ctl); | |
1170 } else { | 1112 } else { |
1171 // Tell the decoder to flush each of the four zlib streams. | 1113 byte[] buf = new byte[w * 4]; |
1172 rfb.rec.writeByte(comp_ctl | 0x0F); | 1114 int i, offset; |
1173 } | 1115 for (int dy = y; dy < y + h; dy++) { |
1174 } | 1116 zlibInflater.inflate(buf); |
1175 | 1117 offset = dy * rfb.framebufferWidth + x; |
1176 // Flush zlib streams if we are told by the server to do so. | 1118 for (i = 0; i < w; i++) { |
1177 for (int stream_id = 0; stream_id < 4; stream_id++) { | 1119 pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16 |
1178 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) { | 1120 | (buf[i * 4 + 1] & 0xFF) << 8 |
1179 tightInflaters[stream_id] = null; | 1121 | (buf[i * 4] & 0xFF); |
1180 } | 1122 } |
1181 comp_ctl >>= 1; | 1123 if (rfb.rec != null && !rfb.recordFromBeginning) |
1182 } | 1124 rfb.rec.write(buf); |
1183 | 1125 } |
1184 // Check correctness of subencoding value. | 1126 } |
1185 if (comp_ctl > rfb.TightMaxSubencoding) { | 1127 |
1186 throw new Exception("Incorrect tight subencoding: " + comp_ctl); | 1128 handleUpdatedPixels(x, y, w, h); |
1187 } | |
1188 | |
1189 // Handle solid-color rectangles. | |
1190 if (comp_ctl == rfb.TightFill) { | |
1191 | |
1192 if (bytesPixel == 1) { | |
1193 int idx = rfb.readU8(); | |
1194 memGraphics.setColor(colors[idx]); | |
1195 if (rfb.rec != null) { | |
1196 rfb.rec.writeByte(idx); | |
1197 } | |
1198 } else { | |
1199 byte[] buf = new byte[3]; | |
1200 rfb.readFully(buf); | |
1201 if (rfb.rec != null) { | |
1202 rfb.rec.write(buf); | |
1203 } | |
1204 Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 | |
1205 | (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF)); | |
1206 memGraphics.setColor(bg); | |
1207 } | |
1208 memGraphics.fillRect(x, y, w, h); | |
1209 scheduleRepaint(x, y, w, h); | 1129 scheduleRepaint(x, y, w, h); |
1210 return; | 1130 } |
1211 | 1131 |
1212 } | 1132 // |
1213 | 1133 // Handle a Tight-encoded rectangle. |
1214 if (comp_ctl == rfb.TightJpeg) { | 1134 // |
1215 | 1135 |
1216 statNumRectsTightJPEG++; | 1136 void handleTightRect(int x, int y, int w, int h) throws Exception { |
1217 | 1137 |
1218 // Read JPEG data. | 1138 int comp_ctl = rfb.readU8(); |
1219 byte[] jpegData = new byte[rfb.readCompactLen()]; | |
1220 rfb.readFully(jpegData); | |
1221 if (rfb.rec != null) { | 1139 if (rfb.rec != null) { |
1222 if (!rfb.recordFromBeginning) { | 1140 if (rfb.recordFromBeginning || comp_ctl == (rfb.TightFill << 4) |
1223 rfb.recordCompactLen(jpegData.length); | 1141 || comp_ctl == (rfb.TightJpeg << 4)) { |
1224 } | 1142 // Send data exactly as received. |
1225 rfb.rec.write(jpegData); | 1143 rfb.rec.writeByte(comp_ctl); |
1226 } | 1144 } else { |
1227 | 1145 // Tell the decoder to flush each of the four zlib streams. |
1228 // Create an Image object from the JPEG data. | 1146 rfb.rec.writeByte(comp_ctl | 0x0F); |
1229 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData); | 1147 } |
1230 | 1148 } |
1231 // Remember the rectangle where the image should be drawn. | 1149 |
1232 jpegRect = new Rectangle(x, y, w, h); | 1150 // Flush zlib streams if we are told by the server to do so. |
1233 | 1151 for (int stream_id = 0; stream_id < 4; stream_id++) { |
1234 // Let the imageUpdate() method do the actual drawing, here just | 1152 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) { |
1235 // wait until the image is fully loaded and drawn. | 1153 tightInflaters[stream_id] = null; |
1236 synchronized (jpegRect) { | 1154 } |
1237 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, | 1155 comp_ctl >>= 1; |
1238 this); | 1156 } |
1239 try { | 1157 |
1240 // Wait no longer than three seconds. | 1158 // Check correctness of subencoding value. |
1241 jpegRect.wait(3000); | 1159 if (comp_ctl > rfb.TightMaxSubencoding) { |
1242 } catch (InterruptedException e) { | 1160 throw new Exception("Incorrect tight subencoding: " + comp_ctl); |
1243 throw new Exception("Interrupted while decoding JPEG image"); | 1161 } |
1244 } | 1162 |
1245 } | 1163 // Handle solid-color rectangles. |
1246 | 1164 if (comp_ctl == rfb.TightFill) { |
1247 // Done, jpegRect is not needed any more. | 1165 |
1248 jpegRect = null; | |
1249 return; | |
1250 | |
1251 } | |
1252 | |
1253 // Read filter id and parameters. | |
1254 int numColors = 0, rowSize = w; | |
1255 byte[] palette8 = new byte[2]; | |
1256 int[] palette24 = new int[256]; | |
1257 boolean useGradient = false; | |
1258 if ((comp_ctl & rfb.TightExplicitFilter) != 0) { | |
1259 int filter_id = rfb.readU8(); | |
1260 if (rfb.rec != null) { | |
1261 rfb.rec.writeByte(filter_id); | |
1262 } | |
1263 if (filter_id == rfb.TightFilterPalette) { | |
1264 numColors = rfb.readU8() + 1; | |
1265 if (rfb.rec != null) { | |
1266 rfb.rec.writeByte(numColors - 1); | |
1267 } | |
1268 if (bytesPixel == 1) { | 1166 if (bytesPixel == 1) { |
1269 if (numColors != 2) { | 1167 int idx = rfb.readU8(); |
1270 throw new Exception("Incorrect tight palette size: " | 1168 memGraphics.setColor(colors[idx]); |
1271 + numColors); | |
1272 } | |
1273 rfb.readFully(palette8); | |
1274 if (rfb.rec != null) { | 1169 if (rfb.rec != null) { |
1275 rfb.rec.write(palette8); | 1170 rfb.rec.writeByte(idx); |
1276 } | 1171 } |
1277 } else { | 1172 } else { |
1278 byte[] buf = new byte[numColors * 3]; | 1173 byte[] buf = new byte[3]; |
1279 rfb.readFully(buf); | 1174 rfb.readFully(buf); |
1280 if (rfb.rec != null) { | 1175 if (rfb.rec != null) { |
1281 rfb.rec.write(buf); | 1176 rfb.rec.write(buf); |
1282 } | 1177 } |
1283 for (int i = 0; i < numColors; i++) { | 1178 Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 |
1284 palette24[i] = ((buf[i * 3] & 0xFF) << 16 | 1179 | (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF)); |
1285 | (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3 + 2] & 0xFF)); | 1180 memGraphics.setColor(bg); |
1286 } | 1181 } |
1287 } | 1182 memGraphics.fillRect(x, y, w, h); |
1288 if (numColors == 2) | 1183 scheduleRepaint(x, y, w, h); |
1289 rowSize = (w + 7) / 8; | 1184 return; |
1290 } else if (filter_id == rfb.TightFilterGradient) { | 1185 |
1291 useGradient = true; | 1186 } |
1292 } else if (filter_id != rfb.TightFilterCopy) { | 1187 |
1293 throw new Exception("Incorrect tight filter id: " + filter_id); | 1188 if (comp_ctl == rfb.TightJpeg) { |
1294 } | 1189 |
1295 } | 1190 statNumRectsTightJPEG++; |
1296 if (numColors == 0 && bytesPixel == 4) | 1191 |
1297 rowSize *= 3; | 1192 // Read JPEG data. |
1298 | 1193 byte[] jpegData = new byte[rfb.readCompactLen()]; |
1299 // Read, optionally uncompress and decode data. | 1194 rfb.readFully(jpegData); |
1300 int dataSize = h * rowSize; | |
1301 if (dataSize < rfb.TightMinToCompress) { | |
1302 // Data size is small - not compressed with zlib. | |
1303 if (numColors != 0) { | |
1304 // Indexed colors. | |
1305 byte[] indexedData = new byte[dataSize]; | |
1306 rfb.readFully(indexedData); | |
1307 if (rfb.rec != null) { | 1195 if (rfb.rec != null) { |
1308 rfb.rec.write(indexedData); | 1196 if (!rfb.recordFromBeginning) { |
1309 } | 1197 rfb.recordCompactLen(jpegData.length); |
1310 if (numColors == 2) { | 1198 } |
1311 // Two colors. | 1199 rfb.rec.write(jpegData); |
1200 } | |
1201 | |
1202 // Create an Image object from the JPEG data. | |
1203 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData); | |
1204 | |
1205 // Remember the rectangle where the image should be drawn. | |
1206 jpegRect = new Rectangle(x, y, w, h); | |
1207 | |
1208 // Let the imageUpdate() method do the actual drawing, here just | |
1209 // wait until the image is fully loaded and drawn. | |
1210 synchronized (jpegRect) { | |
1211 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, | |
1212 this); | |
1213 try { | |
1214 // Wait no longer than three seconds. | |
1215 jpegRect.wait(3000); | |
1216 } catch (InterruptedException e) { | |
1217 throw new Exception("Interrupted while decoding JPEG image"); | |
1218 } | |
1219 } | |
1220 | |
1221 // Done, jpegRect is not needed any more. | |
1222 jpegRect = null; | |
1223 return; | |
1224 | |
1225 } | |
1226 | |
1227 // Read filter id and parameters. | |
1228 int numColors = 0, rowSize = w; | |
1229 byte[] palette8 = new byte[2]; | |
1230 int[] palette24 = new int[256]; | |
1231 boolean useGradient = false; | |
1232 if ((comp_ctl & rfb.TightExplicitFilter) != 0) { | |
1233 int filter_id = rfb.readU8(); | |
1234 if (rfb.rec != null) { | |
1235 rfb.rec.writeByte(filter_id); | |
1236 } | |
1237 if (filter_id == rfb.TightFilterPalette) { | |
1238 numColors = rfb.readU8() + 1; | |
1239 if (rfb.rec != null) { | |
1240 rfb.rec.writeByte(numColors - 1); | |
1241 } | |
1312 if (bytesPixel == 1) { | 1242 if (bytesPixel == 1) { |
1313 decodeMonoData(x, y, w, h, indexedData, palette8); | 1243 if (numColors != 2) { |
1244 throw new Exception("Incorrect tight palette size: " | |
1245 + numColors); | |
1246 } | |
1247 rfb.readFully(palette8); | |
1248 if (rfb.rec != null) { | |
1249 rfb.rec.write(palette8); | |
1250 } | |
1314 } else { | 1251 } else { |
1315 decodeMonoData(x, y, w, h, indexedData, palette24); | 1252 byte[] buf = new byte[numColors * 3]; |
1316 } | |
1317 } else { | |
1318 // 3..255 colors (assuming bytesPixel == 4). | |
1319 int i = 0; | |
1320 for (int dy = y; dy < y + h; dy++) { | |
1321 for (int dx = x; dx < x + w; dx++) { | |
1322 pixels24[dy * rfb.framebufferWidth + dx] = palette24[indexedData[i++] & 0xFF]; | |
1323 } | |
1324 } | |
1325 } | |
1326 } else if (useGradient) { | |
1327 // "Gradient"-processed data | |
1328 byte[] buf = new byte[w * h * 3]; | |
1329 rfb.readFully(buf); | |
1330 if (rfb.rec != null) { | |
1331 rfb.rec.write(buf); | |
1332 } | |
1333 decodeGradientData(x, y, w, h, buf); | |
1334 } else { | |
1335 // Raw truecolor data. | |
1336 if (bytesPixel == 1) { | |
1337 for (int dy = y; dy < y + h; dy++) { | |
1338 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w); | |
1339 if (rfb.rec != null) { | |
1340 rfb.rec.write(pixels8, dy * rfb.framebufferWidth | |
1341 + x, w); | |
1342 } | |
1343 } | |
1344 } else { | |
1345 byte[] buf = new byte[w * 3]; | |
1346 int i, offset; | |
1347 for (int dy = y; dy < y + h; dy++) { | |
1348 rfb.readFully(buf); | 1253 rfb.readFully(buf); |
1349 if (rfb.rec != null) { | 1254 if (rfb.rec != null) { |
1350 rfb.rec.write(buf); | 1255 rfb.rec.write(buf); |
1351 } | 1256 } |
1352 offset = dy * rfb.framebufferWidth + x; | 1257 for (int i = 0; i < numColors; i++) { |
1353 for (i = 0; i < w; i++) { | 1258 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
1354 pixels24[offset + i] = (buf[i * 3] & 0xFF) << 16 | 1259 | (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3 + 2] & 0xFF)); |
1355 | (buf[i * 3 + 1] & 0xFF) << 8 | 1260 } |
1356 | (buf[i * 3 + 2] & 0xFF); | 1261 } |
1357 } | 1262 if (numColors == 2) |
1358 } | 1263 rowSize = (w + 7) / 8; |
1359 } | 1264 } else if (filter_id == rfb.TightFilterGradient) { |
1360 } | 1265 useGradient = true; |
1361 } else { | 1266 } else if (filter_id != rfb.TightFilterCopy) { |
1362 // Data was compressed with zlib. | 1267 throw new Exception("Incorrect tight filter id: " + filter_id); |
1363 int zlibDataLen = rfb.readCompactLen(); | 1268 } |
1364 byte[] zlibData = new byte[zlibDataLen]; | 1269 } |
1365 rfb.readFully(zlibData); | 1270 if (numColors == 0 && bytesPixel == 4) |
1366 if (rfb.rec != null && rfb.recordFromBeginning) { | 1271 rowSize *= 3; |
1367 rfb.rec.write(zlibData); | 1272 |
1368 } | 1273 // Read, optionally uncompress and decode data. |
1369 int stream_id = comp_ctl & 0x03; | 1274 int dataSize = h * rowSize; |
1370 if (tightInflaters[stream_id] == null) { | 1275 if (dataSize < rfb.TightMinToCompress) { |
1371 tightInflaters[stream_id] = new Inflater(); | 1276 // Data size is small - not compressed with zlib. |
1372 } | 1277 if (numColors != 0) { |
1373 Inflater myInflater = tightInflaters[stream_id]; | 1278 // Indexed colors. |
1374 myInflater.setInput(zlibData); | 1279 byte[] indexedData = new byte[dataSize]; |
1375 byte[] buf = new byte[dataSize]; | 1280 rfb.readFully(indexedData); |
1376 myInflater.inflate(buf); | 1281 if (rfb.rec != null) { |
1377 if (rfb.rec != null && !rfb.recordFromBeginning) { | 1282 rfb.rec.write(indexedData); |
1378 rfb.recordCompressedData(buf); | 1283 } |
1379 } | 1284 if (numColors == 2) { |
1380 | 1285 // Two colors. |
1381 if (numColors != 0) { | 1286 if (bytesPixel == 1) { |
1382 // Indexed colors. | 1287 decodeMonoData(x, y, w, h, indexedData, palette8); |
1383 if (numColors == 2) { | 1288 } else { |
1384 // Two colors. | 1289 decodeMonoData(x, y, w, h, indexedData, palette24); |
1290 } | |
1291 } else { | |
1292 // 3..255 colors (assuming bytesPixel == 4). | |
1293 int i = 0; | |
1294 for (int dy = y; dy < y + h; dy++) { | |
1295 for (int dx = x; dx < x + w; dx++) { | |
1296 pixels24[dy * rfb.framebufferWidth + dx] = palette24[indexedData[i++] & 0xFF]; | |
1297 } | |
1298 } | |
1299 } | |
1300 } else if (useGradient) { | |
1301 // "Gradient"-processed data | |
1302 byte[] buf = new byte[w * h * 3]; | |
1303 rfb.readFully(buf); | |
1304 if (rfb.rec != null) { | |
1305 rfb.rec.write(buf); | |
1306 } | |
1307 decodeGradientData(x, y, w, h, buf); | |
1308 } else { | |
1309 // Raw truecolor data. | |
1385 if (bytesPixel == 1) { | 1310 if (bytesPixel == 1) { |
1386 decodeMonoData(x, y, w, h, buf, palette8); | 1311 for (int dy = y; dy < y + h; dy++) { |
1312 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w); | |
1313 if (rfb.rec != null) { | |
1314 rfb.rec.write(pixels8, dy * rfb.framebufferWidth | |
1315 + x, w); | |
1316 } | |
1317 } | |
1387 } else { | 1318 } else { |
1388 decodeMonoData(x, y, w, h, buf, palette24); | 1319 byte[] buf = new byte[w * 3]; |
1389 } | 1320 int i, offset; |
1321 for (int dy = y; dy < y + h; dy++) { | |
1322 rfb.readFully(buf); | |
1323 if (rfb.rec != null) { | |
1324 rfb.rec.write(buf); | |
1325 } | |
1326 offset = dy * rfb.framebufferWidth + x; | |
1327 for (i = 0; i < w; i++) { | |
1328 pixels24[offset + i] = (buf[i * 3] & 0xFF) << 16 | |
1329 | (buf[i * 3 + 1] & 0xFF) << 8 | |
1330 | (buf[i * 3 + 2] & 0xFF); | |
1331 } | |
1332 } | |
1333 } | |
1334 } | |
1335 } else { | |
1336 // Data was compressed with zlib. | |
1337 int zlibDataLen = rfb.readCompactLen(); | |
1338 byte[] zlibData = new byte[zlibDataLen]; | |
1339 rfb.readFully(zlibData); | |
1340 if (rfb.rec != null && rfb.recordFromBeginning) { | |
1341 rfb.rec.write(zlibData); | |
1342 } | |
1343 int stream_id = comp_ctl & 0x03; | |
1344 if (tightInflaters[stream_id] == null) { | |
1345 tightInflaters[stream_id] = new Inflater(); | |
1346 } | |
1347 Inflater myInflater = tightInflaters[stream_id]; | |
1348 myInflater.setInput(zlibData); | |
1349 byte[] buf = new byte[dataSize]; | |
1350 myInflater.inflate(buf); | |
1351 if (rfb.rec != null && !rfb.recordFromBeginning) { | |
1352 rfb.recordCompressedData(buf); | |
1353 } | |
1354 | |
1355 if (numColors != 0) { | |
1356 // Indexed colors. | |
1357 if (numColors == 2) { | |
1358 // Two colors. | |
1359 if (bytesPixel == 1) { | |
1360 decodeMonoData(x, y, w, h, buf, palette8); | |
1361 } else { | |
1362 decodeMonoData(x, y, w, h, buf, palette24); | |
1363 } | |
1364 } else { | |
1365 // More than two colors (assuming bytesPixel == 4). | |
1366 int i = 0; | |
1367 for (int dy = y; dy < y + h; dy++) { | |
1368 for (int dx = x; dx < x + w; dx++) { | |
1369 pixels24[dy * rfb.framebufferWidth + dx] = palette24[buf[i++] & 0xFF]; | |
1370 } | |
1371 } | |
1372 } | |
1373 } else if (useGradient) { | |
1374 // Compressed "Gradient"-filtered data (assuming bytesPixel == | |
1375 // 4). | |
1376 decodeGradientData(x, y, w, h, buf); | |
1390 } else { | 1377 } else { |
1391 // More than two colors (assuming bytesPixel == 4). | 1378 // Compressed truecolor data. |
1392 int i = 0; | 1379 if (bytesPixel == 1) { |
1393 for (int dy = y; dy < y + h; dy++) { | 1380 int destOffset = y * rfb.framebufferWidth + x; |
1394 for (int dx = x; dx < x + w; dx++) { | 1381 for (int dy = 0; dy < h; dy++) { |
1395 pixels24[dy * rfb.framebufferWidth + dx] = palette24[buf[i++] & 0xFF]; | 1382 System.arraycopy(buf, dy * w, pixels8, destOffset, w); |
1396 } | 1383 destOffset += rfb.framebufferWidth; |
1397 } | 1384 } |
1398 } | 1385 } else { |
1399 } else if (useGradient) { | 1386 int srcOffset = 0; |
1400 // Compressed "Gradient"-filtered data (assuming bytesPixel == | 1387 int destOffset, i; |
1401 // 4). | 1388 for (int dy = 0; dy < h; dy++) { |
1402 decodeGradientData(x, y, w, h, buf); | 1389 myInflater.inflate(buf); |
1390 destOffset = (y + dy) * rfb.framebufferWidth + x; | |
1391 for (i = 0; i < w; i++) { | |
1392 pixels24[destOffset + i] = (buf[srcOffset] & 0xFF) << 16 | |
1393 | (buf[srcOffset + 1] & 0xFF) << 8 | |
1394 | (buf[srcOffset + 2] & 0xFF); | |
1395 srcOffset += 3; | |
1396 } | |
1397 } | |
1398 } | |
1399 } | |
1400 } | |
1401 | |
1402 handleUpdatedPixels(x, y, w, h); | |
1403 scheduleRepaint(x, y, w, h); | |
1404 } | |
1405 | |
1406 // | |
1407 // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions). | |
1408 // | |
1409 | |
1410 void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) { | |
1411 | |
1412 int dx, dy, n; | |
1413 int i = y * rfb.framebufferWidth + x; | |
1414 int rowBytes = (w + 7) / 8; | |
1415 byte b; | |
1416 | |
1417 for (dy = 0; dy < h; dy++) { | |
1418 for (dx = 0; dx < w / 8; dx++) { | |
1419 b = src[dy * rowBytes + dx]; | |
1420 for (n = 7; n >= 0; n--) | |
1421 pixels8[i++] = palette[b >> n & 1]; | |
1422 } | |
1423 for (n = 7; n >= 8 - w % 8; n--) { | |
1424 pixels8[i++] = palette[src[dy * rowBytes + dx] >> n & 1]; | |
1425 } | |
1426 i += (rfb.framebufferWidth - w); | |
1427 } | |
1428 } | |
1429 | |
1430 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) { | |
1431 | |
1432 int dx, dy, n; | |
1433 int i = y * rfb.framebufferWidth + x; | |
1434 int rowBytes = (w + 7) / 8; | |
1435 byte b; | |
1436 | |
1437 for (dy = 0; dy < h; dy++) { | |
1438 for (dx = 0; dx < w / 8; dx++) { | |
1439 b = src[dy * rowBytes + dx]; | |
1440 for (n = 7; n >= 0; n--) | |
1441 pixels24[i++] = palette[b >> n & 1]; | |
1442 } | |
1443 for (n = 7; n >= 8 - w % 8; n--) { | |
1444 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1]; | |
1445 } | |
1446 i += (rfb.framebufferWidth - w); | |
1447 } | |
1448 } | |
1449 | |
1450 // | |
1451 // Decode data processed with the "Gradient" filter. | |
1452 // | |
1453 | |
1454 void decodeGradientData(int x, int y, int w, int h, byte[] buf) { | |
1455 | |
1456 int dx, dy, c; | |
1457 byte[] prevRow = new byte[w * 3]; | |
1458 byte[] thisRow = new byte[w * 3]; | |
1459 byte[] pix = new byte[3]; | |
1460 int[] est = new int[3]; | |
1461 | |
1462 int offset = y * rfb.framebufferWidth + x; | |
1463 | |
1464 for (dy = 0; dy < h; dy++) { | |
1465 | |
1466 /* First pixel in a row */ | |
1467 for (c = 0; c < 3; c++) { | |
1468 pix[c] = (byte) (prevRow[c] + buf[dy * w * 3 + c]); | |
1469 thisRow[c] = pix[c]; | |
1470 } | |
1471 pixels24[offset++] = (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | |
1472 | (pix[2] & 0xFF); | |
1473 | |
1474 /* Remaining pixels of a row */ | |
1475 for (dx = 1; dx < w; dx++) { | |
1476 for (c = 0; c < 3; c++) { | |
1477 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) - (prevRow[(dx - 1) | |
1478 * 3 + c] & 0xFF)); | |
1479 if (est[c] > 0xFF) { | |
1480 est[c] = 0xFF; | |
1481 } else if (est[c] < 0x00) { | |
1482 est[c] = 0x00; | |
1483 } | |
1484 pix[c] = (byte) (est[c] + buf[(dy * w + dx) * 3 + c]); | |
1485 thisRow[dx * 3 + c] = pix[c]; | |
1486 } | |
1487 pixels24[offset++] = (pix[0] & 0xFF) << 16 | |
1488 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF); | |
1489 } | |
1490 | |
1491 System.arraycopy(thisRow, 0, prevRow, 0, w * 3); | |
1492 offset += (rfb.framebufferWidth - w); | |
1493 } | |
1494 } | |
1495 | |
1496 // | |
1497 // Display newly updated area of pixels. | |
1498 // | |
1499 | |
1500 void handleUpdatedPixels(int x, int y, int w, int h) { | |
1501 | |
1502 // Draw updated pixels of the off-screen image. | |
1503 pixelsSource.newPixels(x, y, w, h); | |
1504 memGraphics.setClip(x, y, w, h); | |
1505 memGraphics.drawImage(rawPixelsImage, 0, 0, null); | |
1506 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight); | |
1507 } | |
1508 | |
1509 // | |
1510 // Tell JVM to repaint specified desktop area. | |
1511 // | |
1512 | |
1513 void scheduleRepaint(int x, int y, int w, int h) { | |
1514 // Request repaint, deferred if necessary. | |
1515 if (rfb.framebufferWidth == scaledWidth) { | |
1516 repaint(viewer.deferScreenUpdates, x, y, w, h); | |
1403 } else { | 1517 } else { |
1404 // Compressed truecolor data. | 1518 int sx = x * scalingFactor / 100; |
1405 if (bytesPixel == 1) { | 1519 int sy = y * scalingFactor / 100; |
1406 int destOffset = y * rfb.framebufferWidth + x; | 1520 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1; |
1407 for (int dy = 0; dy < h; dy++) { | 1521 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1; |
1408 System.arraycopy(buf, dy * w, pixels8, destOffset, w); | 1522 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh); |
1409 destOffset += rfb.framebufferWidth; | 1523 } |
1524 } | |
1525 | |
1526 // | |
1527 // Handle events. | |
1528 // | |
1529 | |
1530 public void keyPressed(KeyEvent evt) { | |
1531 processLocalKeyEvent(evt); | |
1532 } | |
1533 | |
1534 public void keyReleased(KeyEvent evt) { | |
1535 processLocalKeyEvent(evt); | |
1536 } | |
1537 | |
1538 public void keyTyped(KeyEvent evt) { | |
1539 evt.consume(); | |
1540 } | |
1541 | |
1542 public void mousePressed(MouseEvent evt) { | |
1543 processLocalMouseEvent(evt, false); | |
1544 } | |
1545 | |
1546 public void mouseReleased(MouseEvent evt) { | |
1547 processLocalMouseEvent(evt, false); | |
1548 } | |
1549 | |
1550 public void mouseMoved(MouseEvent evt) { | |
1551 processLocalMouseEvent(evt, true); | |
1552 } | |
1553 | |
1554 public void mouseDragged(MouseEvent evt) { | |
1555 processLocalMouseEvent(evt, true); | |
1556 } | |
1557 | |
1558 public void processLocalKeyEvent(KeyEvent evt) { | |
1559 if (viewer.rfb != null && rfb.inNormalProtocol) { | |
1560 if (!inputEnabled) { | |
1561 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') | |
1562 && evt.getID() == KeyEvent.KEY_PRESSED) { | |
1563 // Request screen update. | |
1564 try { | |
1565 rfb.writeFramebufferUpdateRequest(0, 0, | |
1566 rfb.framebufferWidth, rfb.framebufferHeight, | |
1567 false); | |
1568 } catch (IOException e) { | |
1569 e.printStackTrace(); | |
1570 } | |
1410 } | 1571 } |
1411 } else { | 1572 } else { |
1412 int srcOffset = 0; | 1573 // Input enabled. |
1413 int destOffset, i; | 1574 synchronized (rfb) { |
1414 for (int dy = 0; dy < h; dy++) { | 1575 try { |
1415 myInflater.inflate(buf); | 1576 rfb.writeKeyEvent(evt); |
1416 destOffset = (y + dy) * rfb.framebufferWidth + x; | 1577 } catch (Exception e) { |
1417 for (i = 0; i < w; i++) { | 1578 e.printStackTrace(); |
1418 pixels24[destOffset + i] = (buf[srcOffset] & 0xFF) << 16 | 1579 } |
1419 | (buf[srcOffset + 1] & 0xFF) << 8 | 1580 rfb.notify(); |
1420 | (buf[srcOffset + 2] & 0xFF); | 1581 } |
1421 srcOffset += 3; | 1582 } |
1422 } | 1583 } |
1423 } | 1584 // Don't ever pass keyboard events to AWT for default processing. |
1424 } | 1585 // Otherwise, pressing Tab would switch focus to ButtonPanel etc. |
1425 } | 1586 evt.consume(); |
1426 } | 1587 } |
1427 | 1588 |
1428 handleUpdatedPixels(x, y, w, h); | 1589 public void processLocalMouseEvent(MouseEvent evt, boolean moved) { |
1429 scheduleRepaint(x, y, w, h); | 1590 if (viewer.rfb != null && rfb.inNormalProtocol) { |
1430 } | 1591 if (moved) { |
1431 | 1592 softCursorMove(evt.getX(), evt.getY()); |
1432 // | 1593 } |
1433 // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions). | 1594 if (rfb.framebufferWidth != scaledWidth) { |
1434 // | 1595 int sx = (evt.getX() * 100 + scalingFactor / 2) / scalingFactor; |
1435 | 1596 int sy = (evt.getY() * 100 + scalingFactor / 2) / scalingFactor; |
1436 void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) { | 1597 evt.translatePoint(sx - evt.getX(), sy - evt.getY()); |
1437 | 1598 } |
1438 int dx, dy, n; | |
1439 int i = y * rfb.framebufferWidth + x; | |
1440 int rowBytes = (w + 7) / 8; | |
1441 byte b; | |
1442 | |
1443 for (dy = 0; dy < h; dy++) { | |
1444 for (dx = 0; dx < w / 8; dx++) { | |
1445 b = src[dy * rowBytes + dx]; | |
1446 for (n = 7; n >= 0; n--) | |
1447 pixels8[i++] = palette[b >> n & 1]; | |
1448 } | |
1449 for (n = 7; n >= 8 - w % 8; n--) { | |
1450 pixels8[i++] = palette[src[dy * rowBytes + dx] >> n & 1]; | |
1451 } | |
1452 i += (rfb.framebufferWidth - w); | |
1453 } | |
1454 } | |
1455 | |
1456 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) { | |
1457 | |
1458 int dx, dy, n; | |
1459 int i = y * rfb.framebufferWidth + x; | |
1460 int rowBytes = (w + 7) / 8; | |
1461 byte b; | |
1462 | |
1463 for (dy = 0; dy < h; dy++) { | |
1464 for (dx = 0; dx < w / 8; dx++) { | |
1465 b = src[dy * rowBytes + dx]; | |
1466 for (n = 7; n >= 0; n--) | |
1467 pixels24[i++] = palette[b >> n & 1]; | |
1468 } | |
1469 for (n = 7; n >= 8 - w % 8; n--) { | |
1470 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1]; | |
1471 } | |
1472 i += (rfb.framebufferWidth - w); | |
1473 } | |
1474 } | |
1475 | |
1476 // | |
1477 // Decode data processed with the "Gradient" filter. | |
1478 // | |
1479 | |
1480 void decodeGradientData(int x, int y, int w, int h, byte[] buf) { | |
1481 | |
1482 int dx, dy, c; | |
1483 byte[] prevRow = new byte[w * 3]; | |
1484 byte[] thisRow = new byte[w * 3]; | |
1485 byte[] pix = new byte[3]; | |
1486 int[] est = new int[3]; | |
1487 | |
1488 int offset = y * rfb.framebufferWidth + x; | |
1489 | |
1490 for (dy = 0; dy < h; dy++) { | |
1491 | |
1492 /* First pixel in a row */ | |
1493 for (c = 0; c < 3; c++) { | |
1494 pix[c] = (byte) (prevRow[c] + buf[dy * w * 3 + c]); | |
1495 thisRow[c] = pix[c]; | |
1496 } | |
1497 pixels24[offset++] = (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | |
1498 | (pix[2] & 0xFF); | |
1499 | |
1500 /* Remaining pixels of a row */ | |
1501 for (dx = 1; dx < w; dx++) { | |
1502 for (c = 0; c < 3; c++) { | |
1503 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) - (prevRow[(dx - 1) | |
1504 * 3 + c] & 0xFF)); | |
1505 if (est[c] > 0xFF) { | |
1506 est[c] = 0xFF; | |
1507 } else if (est[c] < 0x00) { | |
1508 est[c] = 0x00; | |
1509 } | |
1510 pix[c] = (byte) (est[c] + buf[(dy * w + dx) * 3 + c]); | |
1511 thisRow[dx * 3 + c] = pix[c]; | |
1512 } | |
1513 pixels24[offset++] = (pix[0] & 0xFF) << 16 | |
1514 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF); | |
1515 } | |
1516 | |
1517 System.arraycopy(thisRow, 0, prevRow, 0, w * 3); | |
1518 offset += (rfb.framebufferWidth - w); | |
1519 } | |
1520 } | |
1521 | |
1522 // | |
1523 // Display newly updated area of pixels. | |
1524 // | |
1525 | |
1526 void handleUpdatedPixels(int x, int y, int w, int h) { | |
1527 | |
1528 // Draw updated pixels of the off-screen image. | |
1529 pixelsSource.newPixels(x, y, w, h); | |
1530 memGraphics.setClip(x, y, w, h); | |
1531 memGraphics.drawImage(rawPixelsImage, 0, 0, null); | |
1532 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight); | |
1533 } | |
1534 | |
1535 // | |
1536 // Tell JVM to repaint specified desktop area. | |
1537 // | |
1538 | |
1539 void scheduleRepaint(int x, int y, int w, int h) { | |
1540 // Request repaint, deferred if necessary. | |
1541 if (rfb.framebufferWidth == scaledWidth) { | |
1542 repaint(viewer.deferScreenUpdates, x, y, w, h); | |
1543 } else { | |
1544 int sx = x * scalingFactor / 100; | |
1545 int sy = y * scalingFactor / 100; | |
1546 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1; | |
1547 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1; | |
1548 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh); | |
1549 } | |
1550 } | |
1551 | |
1552 // | |
1553 // Handle events. | |
1554 // | |
1555 | |
1556 public void keyPressed(KeyEvent evt) { | |
1557 processLocalKeyEvent(evt); | |
1558 } | |
1559 | |
1560 public void keyReleased(KeyEvent evt) { | |
1561 processLocalKeyEvent(evt); | |
1562 } | |
1563 | |
1564 public void keyTyped(KeyEvent evt) { | |
1565 evt.consume(); | |
1566 } | |
1567 | |
1568 public void mousePressed(MouseEvent evt) { | |
1569 processLocalMouseEvent(evt, false); | |
1570 } | |
1571 | |
1572 public void mouseReleased(MouseEvent evt) { | |
1573 processLocalMouseEvent(evt, false); | |
1574 } | |
1575 | |
1576 public void mouseMoved(MouseEvent evt) { | |
1577 processLocalMouseEvent(evt, true); | |
1578 } | |
1579 | |
1580 public void mouseDragged(MouseEvent evt) { | |
1581 processLocalMouseEvent(evt, true); | |
1582 } | |
1583 | |
1584 public void processLocalKeyEvent(KeyEvent evt) { | |
1585 if (viewer.rfb != null && rfb.inNormalProtocol) { | |
1586 if (!inputEnabled) { | |
1587 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') | |
1588 && evt.getID() == KeyEvent.KEY_PRESSED) { | |
1589 // Request screen update. | |
1590 try { | |
1591 rfb.writeFramebufferUpdateRequest(0, 0, | |
1592 rfb.framebufferWidth, rfb.framebufferHeight, | |
1593 false); | |
1594 } catch (IOException e) { | |
1595 e.printStackTrace(); | |
1596 } | |
1597 } | |
1598 } else { | |
1599 // Input enabled. | |
1600 synchronized (rfb) { | 1599 synchronized (rfb) { |
1601 try { | 1600 try { |
1602 rfb.writeKeyEvent(evt); | 1601 rfb.writePointerEvent(evt); |
1603 } catch (Exception e) { | 1602 } catch (Exception e) { |
1604 e.printStackTrace(); | 1603 e.printStackTrace(); |
1605 } | 1604 } |
1606 rfb.notify(); | 1605 rfb.notify(); |
1607 } | 1606 } |
1608 } | 1607 } |
1609 } | 1608 } |
1610 // Don't ever pass keyboard events to AWT for default processing. | 1609 |
1611 // Otherwise, pressing Tab would switch focus to ButtonPanel etc. | 1610 // |
1612 evt.consume(); | 1611 // Ignored events. |
1613 } | 1612 // |
1614 | 1613 |
1615 public void processLocalMouseEvent(MouseEvent evt, boolean moved) { | 1614 public void mouseClicked(MouseEvent evt) { |
1616 if (viewer.rfb != null && rfb.inNormalProtocol) { | 1615 } |
1617 if (moved) { | 1616 |
1618 softCursorMove(evt.getX(), evt.getY()); | 1617 public void mouseEntered(MouseEvent evt) { |
1619 } | 1618 } |
1620 if (rfb.framebufferWidth != scaledWidth) { | 1619 |
1621 int sx = (evt.getX() * 100 + scalingFactor / 2) / scalingFactor; | 1620 public void mouseExited(MouseEvent evt) { |
1622 int sy = (evt.getY() * 100 + scalingFactor / 2) / scalingFactor; | 1621 } |
1623 evt.translatePoint(sx - evt.getX(), sy - evt.getY()); | 1622 |
1624 } | 1623 // |
1625 synchronized (rfb) { | 1624 // Reset update statistics. |
1626 try { | 1625 // |
1627 rfb.writePointerEvent(evt); | 1626 |
1628 } catch (Exception e) { | 1627 void resetStats() { |
1629 e.printStackTrace(); | 1628 statStartTime = System.currentTimeMillis(); |
1630 } | 1629 statNumUpdates = 0; |
1631 rfb.notify(); | 1630 statNumTotalRects = 0; |
1632 } | 1631 statNumPixelRects = 0; |
1633 } | 1632 statNumRectsTight = 0; |
1634 } | 1633 statNumRectsTightJPEG = 0; |
1635 | 1634 statNumRectsZRLE = 0; |
1636 // | 1635 statNumRectsHextile = 0; |
1637 // Ignored events. | 1636 statNumRectsRaw = 0; |
1638 // | 1637 statNumRectsCopy = 0; |
1639 | 1638 statNumBytesEncoded = 0; |
1640 public void mouseClicked(MouseEvent evt) { | 1639 statNumBytesDecoded = 0; |
1641 } | 1640 } |
1642 | 1641 |
1643 public void mouseEntered(MouseEvent evt) { | 1642 // //////////////////////////////////////////////////////////////// |
1644 } | 1643 // |
1645 | 1644 // Handle cursor shape updates (XCursor and RichCursor encodings). |
1646 public void mouseExited(MouseEvent evt) { | 1645 // |
1647 } | 1646 |
1648 | 1647 boolean showSoftCursor = false; |
1649 // | 1648 |
1650 // Reset update statistics. | 1649 MemoryImageSource softCursorSource; |
1651 // | 1650 Image softCursor; |
1652 | 1651 |
1653 void resetStats() { | 1652 int cursorX = 0, cursorY = 0; |
1654 statStartTime = System.currentTimeMillis(); | 1653 int cursorWidth, cursorHeight; |
1655 statNumUpdates = 0; | 1654 int origCursorWidth, origCursorHeight; |
1656 statNumTotalRects = 0; | 1655 int hotX, hotY; |
1657 statNumPixelRects = 0; | 1656 int origHotX, origHotY; |
1658 statNumRectsTight = 0; | 1657 |
1659 statNumRectsTightJPEG = 0; | 1658 // |
1660 statNumRectsZRLE = 0; | 1659 // Handle cursor shape update (XCursor and RichCursor encodings). |
1661 statNumRectsHextile = 0; | 1660 // |
1662 statNumRectsRaw = 0; | 1661 |
1663 statNumRectsCopy = 0; | 1662 synchronized void handleCursorShapeUpdate(int encodingType, int xhot, |
1664 statNumBytesEncoded = 0; | 1663 int yhot, int width, int height) throws IOException { |
1665 statNumBytesDecoded = 0; | 1664 |
1666 } | 1665 softCursorFree(); |
1667 | 1666 |
1668 // //////////////////////////////////////////////////////////////// | 1667 if (width * height == 0) |
1669 // | 1668 return; |
1670 // Handle cursor shape updates (XCursor and RichCursor encodings). | 1669 |
1671 // | 1670 // Ignore cursor shape data if requested by user. |
1672 | 1671 if (viewer.options.ignoreCursorUpdates) { |
1673 boolean showSoftCursor = false; | 1672 int bytesPerRow = (width + 7) / 8; |
1674 | 1673 int bytesMaskData = bytesPerRow * height; |
1675 MemoryImageSource softCursorSource; | 1674 |
1676 Image softCursor; | 1675 if (encodingType == rfb.EncodingXCursor) { |
1677 | 1676 rfb.skipBytes(6 + bytesMaskData * 2); |
1678 int cursorX = 0, cursorY = 0; | 1677 } else { |
1679 int cursorWidth, cursorHeight; | 1678 // rfb.EncodingRichCursor |
1680 int origCursorWidth, origCursorHeight; | 1679 rfb.skipBytes(width * height * bytesPixel + bytesMaskData); |
1681 int hotX, hotY; | 1680 } |
1682 int origHotX, origHotY; | 1681 return; |
1683 | 1682 } |
1684 // | 1683 |
1685 // Handle cursor shape update (XCursor and RichCursor encodings). | 1684 // Decode cursor pixel data. |
1686 // | 1685 softCursorSource = decodeCursorShape(encodingType, width, height); |
1687 | 1686 |
1688 synchronized void handleCursorShapeUpdate(int encodingType, int xhot, | 1687 // Set original (non-scaled) cursor dimensions. |
1689 int yhot, int width, int height) throws IOException { | 1688 origCursorWidth = width; |
1690 | 1689 origCursorHeight = height; |
1691 softCursorFree(); | 1690 origHotX = xhot; |
1692 | 1691 origHotY = yhot; |
1693 if (width * height == 0) | 1692 |
1694 return; | 1693 // Create off-screen cursor image. |
1695 | 1694 createSoftCursor(); |
1696 // Ignore cursor shape data if requested by user. | 1695 |
1697 if (viewer.options.ignoreCursorUpdates) { | 1696 // Show the cursor. |
1697 showSoftCursor = true; | |
1698 repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY, | |
1699 cursorWidth, cursorHeight); | |
1700 } | |
1701 | |
1702 // | |
1703 // decodeCursorShape(). Decode cursor pixel data and return | |
1704 // corresponding MemoryImageSource instance. | |
1705 // | |
1706 | |
1707 synchronized MemoryImageSource decodeCursorShape(int encodingType, | |
1708 int width, int height) throws IOException { | |
1709 | |
1698 int bytesPerRow = (width + 7) / 8; | 1710 int bytesPerRow = (width + 7) / 8; |
1699 int bytesMaskData = bytesPerRow * height; | 1711 int bytesMaskData = bytesPerRow * height; |
1700 | 1712 |
1713 int[] softCursorPixels = new int[width * height]; | |
1714 | |
1701 if (encodingType == rfb.EncodingXCursor) { | 1715 if (encodingType == rfb.EncodingXCursor) { |
1702 rfb.skipBytes(6 + bytesMaskData * 2); | 1716 |
1703 } else { | 1717 // Read foreground and background colors of the cursor. |
1704 // rfb.EncodingRichCursor | 1718 byte[] rgb = new byte[6]; |
1705 rfb.skipBytes(width * height * bytesPixel + bytesMaskData); | 1719 rfb.readFully(rgb); |
1706 } | 1720 int[] colors = { |
1707 return; | 1721 (0xFF000000 | (rgb[3] & 0xFF) << 16 | (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)), |
1708 } | 1722 (0xFF000000 | (rgb[0] & 0xFF) << 16 | (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) }; |
1709 | 1723 |
1710 // Decode cursor pixel data. | 1724 // Read pixel and mask data. |
1711 softCursorSource = decodeCursorShape(encodingType, width, height); | 1725 byte[] pixBuf = new byte[bytesMaskData]; |
1712 | 1726 rfb.readFully(pixBuf); |
1713 // Set original (non-scaled) cursor dimensions. | 1727 byte[] maskBuf = new byte[bytesMaskData]; |
1714 origCursorWidth = width; | 1728 rfb.readFully(maskBuf); |
1715 origCursorHeight = height; | 1729 |
1716 origHotX = xhot; | 1730 // Decode pixel data into softCursorPixels[]. |
1717 origHotY = yhot; | 1731 byte pixByte, maskByte; |
1718 | 1732 int x, y, n, result; |
1719 // Create off-screen cursor image. | 1733 int i = 0; |
1720 createSoftCursor(); | 1734 for (y = 0; y < height; y++) { |
1721 | 1735 for (x = 0; x < width / 8; x++) { |
1722 // Show the cursor. | 1736 pixByte = pixBuf[y * bytesPerRow + x]; |
1723 showSoftCursor = true; | 1737 maskByte = maskBuf[y * bytesPerRow + x]; |
1724 repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY, | 1738 for (n = 7; n >= 0; n--) { |
1725 cursorWidth, cursorHeight); | 1739 if ((maskByte >> n & 1) != 0) { |
1726 } | 1740 result = colors[pixByte >> n & 1]; |
1727 | 1741 } else { |
1728 // | 1742 result = 0; // Transparent pixel |
1729 // decodeCursorShape(). Decode cursor pixel data and return | 1743 } |
1730 // corresponding MemoryImageSource instance. | 1744 softCursorPixels[i++] = result; |
1731 // | 1745 } |
1732 | 1746 } |
1733 synchronized MemoryImageSource decodeCursorShape(int encodingType, | 1747 for (n = 7; n >= 8 - width % 8; n--) { |
1734 int width, int height) throws IOException { | 1748 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) { |
1735 | 1749 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1]; |
1736 int bytesPerRow = (width + 7) / 8; | |
1737 int bytesMaskData = bytesPerRow * height; | |
1738 | |
1739 int[] softCursorPixels = new int[width * height]; | |
1740 | |
1741 if (encodingType == rfb.EncodingXCursor) { | |
1742 | |
1743 // Read foreground and background colors of the cursor. | |
1744 byte[] rgb = new byte[6]; | |
1745 rfb.readFully(rgb); | |
1746 int[] colors = { | |
1747 (0xFF000000 | (rgb[3] & 0xFF) << 16 | (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)), | |
1748 (0xFF000000 | (rgb[0] & 0xFF) << 16 | (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) }; | |
1749 | |
1750 // Read pixel and mask data. | |
1751 byte[] pixBuf = new byte[bytesMaskData]; | |
1752 rfb.readFully(pixBuf); | |
1753 byte[] maskBuf = new byte[bytesMaskData]; | |
1754 rfb.readFully(maskBuf); | |
1755 | |
1756 // Decode pixel data into softCursorPixels[]. | |
1757 byte pixByte, maskByte; | |
1758 int x, y, n, result; | |
1759 int i = 0; | |
1760 for (y = 0; y < height; y++) { | |
1761 for (x = 0; x < width / 8; x++) { | |
1762 pixByte = pixBuf[y * bytesPerRow + x]; | |
1763 maskByte = maskBuf[y * bytesPerRow + x]; | |
1764 for (n = 7; n >= 0; n--) { | |
1765 if ((maskByte >> n & 1) != 0) { | |
1766 result = colors[pixByte >> n & 1]; | |
1767 } else { | 1750 } else { |
1768 result = 0; // Transparent pixel | 1751 result = 0; // Transparent pixel |
1769 } | 1752 } |
1770 softCursorPixels[i++] = result; | 1753 softCursorPixels[i++] = result; |
1771 } | 1754 } |
1772 } | 1755 } |
1773 for (n = 7; n >= 8 - width % 8; n--) { | 1756 |
1774 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) { | 1757 } else { |
1775 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1]; | 1758 // encodingType == rfb.EncodingRichCursor |
1776 } else { | 1759 |
1777 result = 0; // Transparent pixel | 1760 // Read pixel and mask data. |
1778 } | 1761 byte[] pixBuf = new byte[width * height * bytesPixel]; |
1779 softCursorPixels[i++] = result; | 1762 rfb.readFully(pixBuf); |
1780 } | 1763 byte[] maskBuf = new byte[bytesMaskData]; |
1781 } | 1764 rfb.readFully(maskBuf); |
1782 | 1765 |
1783 } else { | 1766 // Decode pixel data into softCursorPixels[]. |
1784 // encodingType == rfb.EncodingRichCursor | 1767 byte pixByte, maskByte; |
1785 | 1768 int x, y, n, result; |
1786 // Read pixel and mask data. | 1769 int i = 0; |
1787 byte[] pixBuf = new byte[width * height * bytesPixel]; | 1770 for (y = 0; y < height; y++) { |
1788 rfb.readFully(pixBuf); | 1771 for (x = 0; x < width / 8; x++) { |
1789 byte[] maskBuf = new byte[bytesMaskData]; | 1772 maskByte = maskBuf[y * bytesPerRow + x]; |
1790 rfb.readFully(maskBuf); | 1773 for (n = 7; n >= 0; n--) { |
1791 | 1774 if ((maskByte >> n & 1) != 0) { |
1792 // Decode pixel data into softCursorPixels[]. | 1775 if (bytesPixel == 1) { |
1793 byte pixByte, maskByte; | 1776 result = cm8.getRGB(pixBuf[i]); |
1794 int x, y, n, result; | 1777 } else { |
1795 int i = 0; | 1778 result = 0xFF000000 |
1796 for (y = 0; y < height; y++) { | 1779 | (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1797 for (x = 0; x < width / 8; x++) { | 1780 | (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1798 maskByte = maskBuf[y * bytesPerRow + x]; | 1781 | (pixBuf[i * 4] & 0xFF); |
1799 for (n = 7; n >= 0; n--) { | 1782 } |
1800 if ((maskByte >> n & 1) != 0) { | 1783 } else { |
1784 result = 0; // Transparent pixel | |
1785 } | |
1786 softCursorPixels[i++] = result; | |
1787 } | |
1788 } | |
1789 for (n = 7; n >= 8 - width % 8; n--) { | |
1790 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) { | |
1801 if (bytesPixel == 1) { | 1791 if (bytesPixel == 1) { |
1802 result = cm8.getRGB(pixBuf[i]); | 1792 result = cm8.getRGB(pixBuf[i]); |
1803 } else { | 1793 } else { |
1804 result = 0xFF000000 | 1794 result = 0xFF000000 |
1805 | (pixBuf[i * 4 + 2] & 0xFF) << 16 | 1795 | (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1810 result = 0; // Transparent pixel | 1800 result = 0; // Transparent pixel |
1811 } | 1801 } |
1812 softCursorPixels[i++] = result; | 1802 softCursorPixels[i++] = result; |
1813 } | 1803 } |
1814 } | 1804 } |
1815 for (n = 7; n >= 8 - width % 8; n--) { | 1805 |
1816 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) { | 1806 } |
1817 if (bytesPixel == 1) { | 1807 |
1818 result = cm8.getRGB(pixBuf[i]); | 1808 return new MemoryImageSource(width, height, softCursorPixels, 0, width); |
1819 } else { | 1809 } |
1820 result = 0xFF000000 | 1810 |
1821 | (pixBuf[i * 4 + 2] & 0xFF) << 16 | 1811 // |
1822 | (pixBuf[i * 4 + 1] & 0xFF) << 8 | 1812 // createSoftCursor(). Assign softCursor new Image (scaled if necessary). |
1823 | (pixBuf[i * 4] & 0xFF); | 1813 // Uses softCursorSource as a source for new cursor image. |
1824 } | 1814 // |
1825 } else { | 1815 |
1826 result = 0; // Transparent pixel | 1816 synchronized void createSoftCursor() { |
1827 } | 1817 |
1828 softCursorPixels[i++] = result; | 1818 if (softCursorSource == null) |
1829 } | 1819 return; |
1830 } | 1820 |
1831 | 1821 int scaleCursor = viewer.options.scaleCursor; |
1832 } | 1822 if (scaleCursor == 0 || !inputEnabled) |
1833 | 1823 scaleCursor = 100; |
1834 return new MemoryImageSource(width, height, softCursorPixels, 0, width); | 1824 |
1825 // Save original cursor coordinates. | |
1826 int x = cursorX - hotX; | |
1827 int y = cursorY - hotY; | |
1828 int w = cursorWidth; | |
1829 int h = cursorHeight; | |
1830 | |
1831 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100; | |
1832 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100; | |
1833 hotX = (origHotX * scaleCursor + 50) / 100; | |
1834 hotY = (origHotY * scaleCursor + 50) / 100; | |
1835 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource); | |
1836 | |
1837 if (scaleCursor != 100) { | |
1838 softCursor = softCursor.getScaledInstance(cursorWidth, | |
1839 cursorHeight, Image.SCALE_SMOOTH); | |
1840 } | |
1841 | |
1842 if (showSoftCursor) { | |
1843 // Compute screen area to update. | |
1844 x = Math.min(x, cursorX - hotX); | |
1845 y = Math.min(y, cursorY - hotY); | |
1846 w = Math.max(w, cursorWidth); | |
1847 h = Math.max(h, cursorHeight); | |
1848 | |
1849 repaint(viewer.deferCursorUpdates, x, y, w, h); | |
1850 } | |
1851 } | |
1852 | |
1853 // | |
1854 // softCursorMove(). Moves soft cursor into a particular location. | |
1855 // | |
1856 | |
1857 synchronized void softCursorMove(int x, int y) { | |
1858 int oldX = cursorX; | |
1859 int oldY = cursorY; | |
1860 cursorX = x; | |
1861 cursorY = y; | |
1862 if (showSoftCursor) { | |
1863 repaint(viewer.deferCursorUpdates, oldX - hotX, oldY - hotY, | |
1864 cursorWidth, cursorHeight); | |
1865 repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY, | |
1866 cursorWidth, cursorHeight); | |
1867 } | |
1868 } | |
1869 | |
1870 // | |
1871 // softCursorFree(). Remove soft cursor, dispose resources. | |
1872 // | |
1873 | |
1874 synchronized void softCursorFree() { | |
1875 if (showSoftCursor) { | |
1876 showSoftCursor = false; | |
1877 softCursor = null; | |
1878 softCursorSource = null; | |
1879 | |
1880 repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY, | |
1881 cursorWidth, cursorHeight); | |
1882 } | |
1883 } | |
1835 } | 1884 } |
1836 | |
1837 // | |
1838 // createSoftCursor(). Assign softCursor new Image (scaled if necessary). | |
1839 // Uses softCursorSource as a source for new cursor image. | |
1840 // | |
1841 | |
1842 synchronized void createSoftCursor() { | |
1843 | |
1844 if (softCursorSource == null) | |
1845 return; | |
1846 | |
1847 int scaleCursor = viewer.options.scaleCursor; | |
1848 if (scaleCursor == 0 || !inputEnabled) | |
1849 scaleCursor = 100; | |
1850 | |
1851 // Save original cursor coordinates. | |
1852 int x = cursorX - hotX; | |
1853 int y = cursorY - hotY; | |
1854 int w = cursorWidth; | |
1855 int h = cursorHeight; | |
1856 | |
1857 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100; | |
1858 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100; | |
1859 hotX = (origHotX * scaleCursor + 50) / 100; | |
1860 hotY = (origHotY * scaleCursor + 50) / 100; | |
1861 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource); | |
1862 | |
1863 if (scaleCursor != 100) { | |
1864 softCursor = softCursor.getScaledInstance(cursorWidth, | |
1865 cursorHeight, Image.SCALE_SMOOTH); | |
1866 } | |
1867 | |
1868 if (showSoftCursor) { | |
1869 // Compute screen area to update. | |
1870 x = Math.min(x, cursorX - hotX); | |
1871 y = Math.min(y, cursorY - hotY); | |
1872 w = Math.max(w, cursorWidth); | |
1873 h = Math.max(h, cursorHeight); | |
1874 | |
1875 repaint(viewer.deferCursorUpdates, x, y, w, h); | |
1876 } | |
1877 } | |
1878 | |
1879 // | |
1880 // softCursorMove(). Moves soft cursor into a particular location. | |
1881 // | |
1882 | |
1883 synchronized void softCursorMove(int x, int y) { | |
1884 int oldX = cursorX; | |
1885 int oldY = cursorY; | |
1886 cursorX = x; | |
1887 cursorY = y; | |
1888 if (showSoftCursor) { | |
1889 repaint(viewer.deferCursorUpdates, oldX - hotX, oldY - hotY, | |
1890 cursorWidth, cursorHeight); | |
1891 repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY, | |
1892 cursorWidth, cursorHeight); | |
1893 } | |
1894 } | |
1895 | |
1896 // | |
1897 // softCursorFree(). Remove soft cursor, dispose resources. | |
1898 // | |
1899 | |
1900 synchronized void softCursorFree() { | |
1901 if (showSoftCursor) { | |
1902 showSoftCursor = false; | |
1903 softCursor = null; | |
1904 softCursorSource = null; | |
1905 | |
1906 repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY, | |
1907 cursorWidth, cursorHeight); | |
1908 } | |
1909 } | |
1910 } |