2 // Copyright (C) 2004 Horizon Wimba. All Rights Reserved.
3 // Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved.
4 // Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved.
5 // Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
6 // Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
8 // This is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
13 // This software is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this software; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25 import java.awt.event.*;
26 import java.awt.image.*;
29 import java.util.zip.*;
33 // VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
36 class VncCanvas extends Canvas
37 implements KeyListener, MouseListener, MouseMotionListener {
45 int maxWidth = 0, maxHeight = 0;
47 int scaledWidth, scaledHeight;
53 MemoryImageSource pixelsSource;
57 // ZRLE encoder's data.
60 byte[] zrleTilePixels8;
61 int[] zrleTilePixels24;
62 ZlibInStream zrleInStream;
63 boolean zrleRecWarningShown = false;
65 // Zlib encoder's data.
68 Inflater zlibInflater;
70 // Tight encoder's data.
71 final static int tightZlibBufferSize = 512;
72 Inflater[] tightInflaters;
74 // Since JPEG images are loaded asynchronously, we have to remember
75 // their position in the framebuffer. Also, this jpegRect object is
76 // used for synchronization between the rfbThread and a JVM's thread
77 // which decodes and loads JPEG images.
80 // True if we process keyboard and mouse events.
87 public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
92 maxHeight = maxHeight_;
95 scalingFactor = viewer.options.scalingFactor;
97 tightInflaters = new Inflater[4];
99 cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
100 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
102 colors = new Color[256];
103 for (int i = 0; i < 256; i++)
104 colors[i] = new Color(cm8.getRGB(i));
108 inputEnabled = false;
109 if (!viewer.options.viewOnly)
112 // Keyboard listener is enabled even in view-only mode, to catch
113 // 'r' or 'R' key presses used to request screen update.
114 addKeyListener(this);
117 public VncCanvas(VncViewer v) throws IOException {
122 // Callback methods to determine geometry of our Component.
125 public Dimension getPreferredSize() {
126 return new Dimension(scaledWidth, scaledHeight);
129 public Dimension getMinimumSize() {
130 return new Dimension(scaledWidth, scaledHeight);
133 public Dimension getMaximumSize() {
134 return new Dimension(scaledWidth, scaledHeight);
138 // All painting is performed here.
141 public void update(Graphics g) {
145 public void paint(Graphics g) {
146 synchronized(memImage) {
147 if (rfb.framebufferWidth == scaledWidth) {
148 g.drawImage(memImage, 0, 0, null);
150 paintScaledFrameBuffer(g);
153 if (showSoftCursor) {
154 int x0 = cursorX - hotX, y0 = cursorY - hotY;
155 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
156 if (r.intersects(g.getClipBounds())) {
157 g.drawImage(softCursor, x0, y0, null);
162 public void paintScaledFrameBuffer(Graphics g) {
163 g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
167 // Override the ImageObserver interface method to handle drawing of
168 // JPEG-encoded data.
171 public boolean imageUpdate(Image img, int infoflags,
172 int x, int y, int width, int height) {
173 if ((infoflags & (ALLBITS | ABORT)) == 0) {
174 return true; // We need more image data.
176 // If the whole image is available, draw it now.
177 if ((infoflags & ALLBITS) != 0) {
178 if (jpegRect != null) {
179 synchronized(jpegRect) {
180 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
181 scheduleRepaint(jpegRect.x, jpegRect.y,
182 jpegRect.width, jpegRect.height);
187 return false; // All image data was processed.
192 // Start/stop receiving mouse events. Keyboard events are received
193 // even in view-only mode, because we want to map the 'r' key to the
194 // screen refreshing function.
197 public synchronized void enableInput(boolean enable) {
198 if (enable && !inputEnabled) {
200 addMouseListener(this);
201 addMouseMotionListener(this);
202 if (viewer.showControls) {
203 viewer.buttonPanel.enableRemoteAccessControls(true);
205 createSoftCursor(); // scaled cursor
206 } else if (!enable && inputEnabled) {
207 inputEnabled = false;
208 removeMouseListener(this);
209 removeMouseMotionListener(this);
210 if (viewer.showControls) {
211 viewer.buttonPanel.enableRemoteAccessControls(false);
213 createSoftCursor(); // non-scaled cursor
217 public void setPixelFormat() throws IOException {
218 if (viewer.options.eightBitColors) {
219 rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
222 rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
225 updateFramebufferSize();
228 void updateFramebufferSize() {
231 int fbWidth = rfb.framebufferWidth;
232 int fbHeight = rfb.framebufferHeight;
234 // Calculate scaling factor for auto scaling.
235 if (maxWidth > 0 && maxHeight > 0) {
236 int f1 = maxWidth * 100 / fbWidth;
237 int f2 = maxHeight * 100 / fbHeight;
238 scalingFactor = Math.min(f1, f2);
239 if (scalingFactor > 100)
241 System.out.println("Scaling desktop at " + scalingFactor + "%");
244 // Update scaled framebuffer geometry.
245 scaledWidth = (fbWidth * scalingFactor + 50) / 100;
246 scaledHeight = (fbHeight * scalingFactor + 50) / 100;
248 // Create new off-screen image either if it does not exist, or if
249 // its geometry should be changed. It's not necessary to replace
250 // existing image if only pixel format should be changed.
251 if (memImage == null) {
252 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
253 memGraphics = memImage.getGraphics();
254 } else if (memImage.getWidth(null) != fbWidth ||
255 memImage.getHeight(null) != fbHeight) {
256 synchronized(memImage) {
257 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
258 memGraphics = memImage.getGraphics();
262 // Images with raw pixels should be re-allocated on every change
263 // of geometry or pixel format.
264 if (bytesPixel == 1) {
267 pixels8 = new byte[fbWidth * fbHeight];
270 new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth);
272 zrleTilePixels24 = null;
273 zrleTilePixels8 = new byte[64 * 64];
278 pixels24 = new int[fbWidth * fbHeight];
281 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
283 zrleTilePixels8 = null;
284 zrleTilePixels24 = new int[64 * 64];
287 pixelsSource.setAnimated(true);
288 rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
290 // Update the size of desktop containers.
291 if (viewer.inSeparateFrame) {
292 if (viewer.desktopScrollPane != null)
293 resizeDesktopFrame();
295 setSize(scaledWidth, scaledHeight);
297 viewer.moveFocusToDesktop();
300 void resizeDesktopFrame() {
301 setSize(scaledWidth, scaledHeight);
303 // FIXME: Find a better way to determine correct size of a
304 // ScrollPane. -- const
305 Insets insets = viewer.desktopScrollPane.getInsets();
306 viewer.desktopScrollPane.setSize(scaledWidth +
307 2 * Math.min(insets.left, insets.right),
309 2 * Math.min(insets.top, insets.bottom));
311 viewer.vncFrame.pack();
313 // Try to limit the frame size to the screen size.
315 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
316 Dimension frameSize = viewer.vncFrame.getSize();
317 Dimension newSize = frameSize;
319 // Reduce Screen Size by 30 pixels in each direction;
320 // This is a (poor) attempt to account for
321 // 1) Menu bar on Macintosh (should really also account for
322 // Dock on OSX). Usually 22px on top of screen.
323 // 2) Taxkbar on Windows (usually about 28 px on bottom)
324 // 3) Other obstructions.
326 screenSize.height -= 30;
327 screenSize.width -= 30;
329 boolean needToResizeFrame = false;
330 if (frameSize.height > screenSize.height) {
331 newSize.height = screenSize.height;
332 needToResizeFrame = true;
334 if (frameSize.width > screenSize.width) {
335 newSize.width = screenSize.width;
336 needToResizeFrame = true;
338 if (needToResizeFrame) {
339 viewer.vncFrame.setSize(newSize);
342 viewer.desktopScrollPane.doLayout();
346 // processNormalProtocol() - executed by the rfbThread to deal with the
350 public void processNormalProtocol() throws Exception {
352 // Start/stop session recording if necessary.
353 viewer.checkRecordingStatus();
355 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
356 rfb.framebufferHeight, false);
359 // main dispatch loop
364 // Read message type from the server.
365 int msgType = rfb.readServerMessageType();
367 // Process the message depending on its type.
369 case RfbProto.FramebufferUpdate:
370 rfb.readFramebufferUpdate();
372 boolean cursorPosReceived = false;
374 for (int i = 0; i < rfb.updateNRects; i++) {
375 rfb.readFramebufferUpdateRectHdr();
376 int rx = rfb.updateRectX, ry = rfb.updateRectY;
377 int rw = rfb.updateRectW, rh = rfb.updateRectH;
379 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
382 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
383 rfb.setFramebufferSize(rw, rh);
384 updateFramebufferSize();
388 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
389 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
390 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
394 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
395 softCursorMove(rx, ry);
396 cursorPosReceived = true;
402 switch (rfb.updateRectEncoding) {
403 case RfbProto.EncodingRaw:
404 handleRawRect(rx, ry, rw, rh);
406 case RfbProto.EncodingCopyRect:
407 handleCopyRect(rx, ry, rw, rh);
409 case RfbProto.EncodingRRE:
410 handleRRERect(rx, ry, rw, rh);
412 case RfbProto.EncodingCoRRE:
413 handleCoRRERect(rx, ry, rw, rh);
415 case RfbProto.EncodingHextile:
416 handleHextileRect(rx, ry, rw, rh);
418 case RfbProto.EncodingZRLE:
419 handleZRLERect(rx, ry, rw, rh);
421 case RfbProto.EncodingZlib:
422 handleZlibRect(rx, ry, rw, rh);
424 case RfbProto.EncodingTight:
425 handleTightRect(rx, ry, rw, rh);
428 throw new Exception("Unknown RFB rectangle encoding " +
429 rfb.updateRectEncoding);
435 boolean fullUpdateNeeded = false;
437 // Start/stop session recording if necessary. Request full
438 // update if a new session file was opened.
439 if (viewer.checkRecordingStatus())
440 fullUpdateNeeded = true;
442 // Defer framebuffer update request if necessary. But wake up
443 // immediately on keyboard or mouse event. Also, don't sleep
444 // if there is some data to receive, or if the last update
445 // included a PointerPos message.
446 if (viewer.deferUpdateRequests > 0 &&
447 rfb.is.available() == 0 && !cursorPosReceived) {
450 rfb.wait(viewer.deferUpdateRequests);
451 } catch (InterruptedException e) {
456 // Before requesting framebuffer update, check if the pixel
457 // format should be changed. If it should, request full update
458 // instead of an incremental one.
459 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
461 fullUpdateNeeded = true;
464 viewer.autoSelectEncodings();
466 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
467 rfb.framebufferHeight,
472 case RfbProto.SetColourMapEntries:
473 throw new Exception("Can't handle SetColourMapEntries message");
476 Toolkit.getDefaultToolkit().beep();
479 case RfbProto.ServerCutText:
480 String s = rfb.readServerCutText();
481 viewer.clipboard.setCutText(s);
485 throw new Exception("Unknown RFB message type " + msgType);
492 // Handle a raw rectangle. The second form with paint==false is used
493 // by the Hextile decoder for raw-encoded tiles.
496 void handleRawRect(int x, int y, int w, int h) throws IOException {
497 handleRawRect(x, y, w, h, true);
500 void handleRawRect(int x, int y, int w, int h, boolean paint)
503 if (bytesPixel == 1) {
504 for (int dy = y; dy < y + h; dy++) {
505 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
506 if (rfb.rec != null) {
507 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
511 byte[] buf = new byte[w * 4];
513 for (int dy = y; dy < y + h; dy++) {
515 if (rfb.rec != null) {
518 offset = dy * rfb.framebufferWidth + x;
519 for (i = 0; i < w; i++) {
520 pixels24[offset + i] =
521 (buf[i * 4 + 2] & 0xFF) << 16 |
522 (buf[i * 4 + 1] & 0xFF) << 8 |
528 handleUpdatedPixels(x, y, w, h);
530 scheduleRepaint(x, y, w, h);
534 // Handle a CopyRect rectangle.
537 void handleCopyRect(int x, int y, int w, int h) throws IOException {
540 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
541 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
543 scheduleRepaint(x, y, w, h);
547 // Handle an RRE-encoded rectangle.
550 void handleRRERect(int x, int y, int w, int h) throws IOException {
552 int nSubrects = rfb.is.readInt();
554 byte[] bg_buf = new byte[bytesPixel];
555 rfb.readFully(bg_buf);
557 if (bytesPixel == 1) {
558 pixel = colors[bg_buf[0] & 0xFF];
560 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
562 memGraphics.setColor(pixel);
563 memGraphics.fillRect(x, y, w, h);
565 byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
567 DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));
569 if (rfb.rec != null) {
570 rfb.rec.writeIntBE(nSubrects);
571 rfb.rec.write(bg_buf);
577 for (int j = 0; j < nSubrects; j++) {
578 if (bytesPixel == 1) {
579 pixel = colors[ds.readUnsignedByte()];
582 pixel = new Color(buf[j*12+2] & 0xFF,
586 sx = x + ds.readUnsignedShort();
587 sy = y + ds.readUnsignedShort();
588 sw = ds.readUnsignedShort();
589 sh = ds.readUnsignedShort();
591 memGraphics.setColor(pixel);
592 memGraphics.fillRect(sx, sy, sw, sh);
595 scheduleRepaint(x, y, w, h);
599 // Handle a CoRRE-encoded rectangle.
602 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
603 int nSubrects = rfb.is.readInt();
605 byte[] bg_buf = new byte[bytesPixel];
606 rfb.readFully(bg_buf);
608 if (bytesPixel == 1) {
609 pixel = colors[bg_buf[0] & 0xFF];
611 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
613 memGraphics.setColor(pixel);
614 memGraphics.fillRect(x, y, w, h);
616 byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
619 if (rfb.rec != null) {
620 rfb.rec.writeIntBE(nSubrects);
621 rfb.rec.write(bg_buf);
628 for (int j = 0; j < nSubrects; j++) {
629 if (bytesPixel == 1) {
630 pixel = colors[buf[i++] & 0xFF];
632 pixel = new Color(buf[i+2] & 0xFF, buf[i+1] & 0xFF, buf[i] & 0xFF);
635 sx = x + (buf[i++] & 0xFF);
636 sy = y + (buf[i++] & 0xFF);
637 sw = buf[i++] & 0xFF;
638 sh = buf[i++] & 0xFF;
640 memGraphics.setColor(pixel);
641 memGraphics.fillRect(sx, sy, sw, sh);
644 scheduleRepaint(x, y, w, h);
648 // Handle a Hextile-encoded rectangle.
651 // These colors should be kept between handleHextileSubrect() calls.
652 private Color hextile_bg, hextile_fg;
654 void handleHextileRect(int x, int y, int w, int h) throws IOException {
656 hextile_bg = new Color(0);
657 hextile_fg = new Color(0);
659 for (int ty = y; ty < y + h; ty += 16) {
664 for (int tx = x; tx < x + w; tx += 16) {
669 handleHextileSubrect(tx, ty, tw, th);
672 // Finished with a row of tiles, now let's show it.
673 scheduleRepaint(x, y, w, h);
678 // Handle one tile in the Hextile-encoded data.
681 void handleHextileSubrect(int tx, int ty, int tw, int th)
684 int subencoding = rfb.is.readUnsignedByte();
685 if (rfb.rec != null) {
686 rfb.rec.writeByte(subencoding);
689 // Is it a raw-encoded sub-rectangle?
690 if ((subencoding & rfb.HextileRaw) != 0) {
691 handleRawRect(tx, ty, tw, th, false);
695 // Read and draw the background if specified.
696 byte[] cbuf = new byte[bytesPixel];
697 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
699 if (bytesPixel == 1) {
700 hextile_bg = colors[cbuf[0] & 0xFF];
702 hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
704 if (rfb.rec != null) {
708 memGraphics.setColor(hextile_bg);
709 memGraphics.fillRect(tx, ty, tw, th);
711 // Read the foreground color if specified.
712 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
714 if (bytesPixel == 1) {
715 hextile_fg = colors[cbuf[0] & 0xFF];
717 hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
719 if (rfb.rec != null) {
724 // Done with this tile if there is no sub-rectangles.
725 if ((subencoding & rfb.HextileAnySubrects) == 0)
728 int nSubrects = rfb.is.readUnsignedByte();
729 int bufsize = nSubrects * 2;
730 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
731 bufsize += nSubrects * bytesPixel;
733 byte[] buf = new byte[bufsize];
735 if (rfb.rec != null) {
736 rfb.rec.writeByte(nSubrects);
740 int b1, b2, sx, sy, sw, sh;
743 if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
745 // Sub-rectangles are all of the same color.
746 memGraphics.setColor(hextile_fg);
747 for (int j = 0; j < nSubrects; j++) {
748 b1 = buf[i++] & 0xFF;
749 b2 = buf[i++] & 0xFF;
751 sy = ty + (b1 & 0xf);
754 memGraphics.fillRect(sx, sy, sw, sh);
756 } else if (bytesPixel == 1) {
758 // BGR233 (8-bit color) version for colored sub-rectangles.
759 for (int j = 0; j < nSubrects; j++) {
760 hextile_fg = colors[buf[i++] & 0xFF];
761 b1 = buf[i++] & 0xFF;
762 b2 = buf[i++] & 0xFF;
764 sy = ty + (b1 & 0xf);
767 memGraphics.setColor(hextile_fg);
768 memGraphics.fillRect(sx, sy, sw, sh);
773 // Full-color (24-bit) version for colored sub-rectangles.
774 for (int j = 0; j < nSubrects; j++) {
775 hextile_fg = new Color(buf[i+2] & 0xFF,
779 b1 = buf[i++] & 0xFF;
780 b2 = buf[i++] & 0xFF;
782 sy = ty + (b1 & 0xf);
785 memGraphics.setColor(hextile_fg);
786 memGraphics.fillRect(sx, sy, sw, sh);
793 // Handle a ZRLE-encoded rectangle.
795 // FIXME: Currently, session recording is not fully supported for ZRLE.
798 void handleZRLERect(int x, int y, int w, int h) throws Exception {
800 if (zrleInStream == null)
801 zrleInStream = new ZlibInStream();
803 int nBytes = rfb.is.readInt();
804 if (nBytes > 64 * 1024 * 1024)
805 throw new Exception("ZRLE decoder: illegal compressed data size");
807 if (zrleBuf == null || zrleBufLen < nBytes) {
808 zrleBufLen = nBytes + 4096;
809 zrleBuf = new byte[zrleBufLen];
812 // FIXME: Do not wait for all the data before decompression.
813 rfb.readFully(zrleBuf, 0, nBytes);
815 if (rfb.rec != null) {
816 if (rfb.recordFromBeginning) {
817 rfb.rec.writeIntBE(nBytes);
818 rfb.rec.write(zrleBuf, 0, nBytes);
819 } else if (!zrleRecWarningShown) {
820 System.out.println("Warning: ZRLE session can be recorded" +
821 " only from the beginning");
822 System.out.println("Warning: Recorded file may be corrupted");
823 zrleRecWarningShown = true;
827 zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
829 for (int ty = y; ty < y+h; ty += 64) {
831 int th = Math.min(y+h-ty, 64);
833 for (int tx = x; tx < x+w; tx += 64) {
835 int tw = Math.min(x+w-tx, 64);
837 int mode = zrleInStream.readU8();
838 boolean rle = (mode & 128) != 0;
839 int palSize = mode & 127;
840 int[] palette = new int[128];
842 readZrlePalette(palette, palSize);
845 int pix = palette[0];
846 Color c = (bytesPixel == 1) ?
847 colors[pix] : new Color(0xFF000000 | pix);
848 memGraphics.setColor(c);
849 memGraphics.fillRect(tx, ty, tw, th);
855 readZrleRawPixels(tw, th);
857 readZrlePackedPixels(tw, th, palette, palSize);
861 readZrlePlainRLEPixels(tw, th);
863 readZrlePackedRLEPixels(tw, th, palette);
866 handleUpdatedZrleTile(tx, ty, tw, th);
870 zrleInStream.reset();
872 scheduleRepaint(x, y, w, h);
875 int readPixel(InStream is) throws Exception {
877 if (bytesPixel == 1) {
880 int p1 = is.readU8();
881 int p2 = is.readU8();
882 int p3 = is.readU8();
883 pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
888 void readPixels(InStream is, int[] dst, int count) throws Exception {
890 if (bytesPixel == 1) {
891 byte[] buf = new byte[count];
892 is.readBytes(buf, 0, count);
893 for (int i = 0; i < count; i++) {
894 dst[i] = (int)buf[i] & 0xFF;
897 byte[] buf = new byte[count * 3];
898 is.readBytes(buf, 0, count * 3);
899 for (int i = 0; i < count; i++) {
900 dst[i] = ((buf[i*3+2] & 0xFF) << 16 |
901 (buf[i*3+1] & 0xFF) << 8 |
907 void readZrlePalette(int[] palette, int palSize) throws Exception {
908 readPixels(zrleInStream, palette, palSize);
911 void readZrleRawPixels(int tw, int th) throws Exception {
912 if (bytesPixel == 1) {
913 zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
915 readPixels(zrleInStream, zrleTilePixels24, tw * th); ///
919 void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
922 int bppp = ((palSize > 16) ? 8 :
923 ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
926 for (int i = 0; i < th; i++) {
933 b = zrleInStream.readU8();
937 int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
938 if (bytesPixel == 1) {
939 zrleTilePixels8[ptr++] = (byte)palette[index];
941 zrleTilePixels24[ptr++] = palette[index];
947 void readZrlePlainRLEPixels(int tw, int th) throws Exception {
949 int end = ptr + tw * th;
951 int pix = readPixel(zrleInStream);
955 b = zrleInStream.readU8();
959 if (!(len <= end - ptr))
960 throw new Exception("ZRLE decoder: assertion failed" +
961 " (len <= end-ptr)");
963 if (bytesPixel == 1) {
964 while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
966 while (len-- > 0) zrleTilePixels24[ptr++] = pix;
971 void readZrlePackedRLEPixels(int tw, int th, int[] palette)
975 int end = ptr + tw * th;
977 int index = zrleInStream.readU8();
979 if ((index & 128) != 0) {
982 b = zrleInStream.readU8();
986 if (!(len <= end - ptr))
987 throw new Exception("ZRLE decoder: assertion failed" +
988 " (len <= end - ptr)");
992 int pix = palette[index];
994 if (bytesPixel == 1) {
995 while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
997 while (len-- > 0) zrleTilePixels24[ptr++] = pix;
1003 // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
1006 void handleUpdatedZrleTile(int x, int y, int w, int h) {
1008 if (bytesPixel == 1) {
1009 src = zrleTilePixels8; dst = pixels8;
1011 src = zrleTilePixels24; dst = pixels24;
1014 int offsetDst = (y * rfb.framebufferWidth + x);
1015 for (int j = 0; j < h; j++) {
1016 System.arraycopy(src, offsetSrc, dst, offsetDst, w);
1018 offsetDst += rfb.framebufferWidth;
1020 handleUpdatedPixels(x, y, w, h);
1024 // Handle a Zlib-encoded rectangle.
1027 void handleZlibRect(int x, int y, int w, int h) throws Exception {
1029 int nBytes = rfb.is.readInt();
1031 if (zlibBuf == null || zlibBufLen < nBytes) {
1032 zlibBufLen = nBytes * 2;
1033 zlibBuf = new byte[zlibBufLen];
1036 rfb.readFully(zlibBuf, 0, nBytes);
1038 if (rfb.rec != null && rfb.recordFromBeginning) {
1039 rfb.rec.writeIntBE(nBytes);
1040 rfb.rec.write(zlibBuf, 0, nBytes);
1043 if (zlibInflater == null) {
1044 zlibInflater = new Inflater();
1046 zlibInflater.setInput(zlibBuf, 0, nBytes);
1048 if (bytesPixel == 1) {
1049 for (int dy = y; dy < y + h; dy++) {
1050 zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
1051 if (rfb.rec != null && !rfb.recordFromBeginning)
1052 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
1055 byte[] buf = new byte[w * 4];
1057 for (int dy = y; dy < y + h; dy++) {
1058 zlibInflater.inflate(buf);
1059 offset = dy * rfb.framebufferWidth + x;
1060 for (i = 0; i < w; i++) {
1061 pixels24[offset + i] =
1062 (buf[i * 4 + 2] & 0xFF) << 16 |
1063 (buf[i * 4 + 1] & 0xFF) << 8 |
1064 (buf[i * 4] & 0xFF);
1066 if (rfb.rec != null && !rfb.recordFromBeginning)
1071 handleUpdatedPixels(x, y, w, h);
1072 scheduleRepaint(x, y, w, h);
1076 // Handle a Tight-encoded rectangle.
1079 void handleTightRect(int x, int y, int w, int h) throws Exception {
1081 int comp_ctl = rfb.is.readUnsignedByte();
1082 if (rfb.rec != null) {
1083 if (rfb.recordFromBeginning ||
1084 comp_ctl == (rfb.TightFill << 4) ||
1085 comp_ctl == (rfb.TightJpeg << 4)) {
1086 // Send data exactly as received.
1087 rfb.rec.writeByte(comp_ctl);
1089 // Tell the decoder to flush each of the four zlib streams.
1090 rfb.rec.writeByte(comp_ctl | 0x0F);
1094 // Flush zlib streams if we are told by the server to do so.
1095 for (int stream_id = 0; stream_id < 4; stream_id++) {
1096 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
1097 tightInflaters[stream_id] = null;
1102 // Check correctness of subencoding value.
1103 if (comp_ctl > rfb.TightMaxSubencoding) {
1104 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
1107 // Handle solid-color rectangles.
1108 if (comp_ctl == rfb.TightFill) {
1110 if (bytesPixel == 1) {
1111 int idx = rfb.is.readUnsignedByte();
1112 memGraphics.setColor(colors[idx]);
1113 if (rfb.rec != null) {
1114 rfb.rec.writeByte(idx);
1117 byte[] buf = new byte[3];
1119 if (rfb.rec != null) {
1122 Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 |
1123 (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
1124 memGraphics.setColor(bg);
1126 memGraphics.fillRect(x, y, w, h);
1127 scheduleRepaint(x, y, w, h);
1132 if (comp_ctl == rfb.TightJpeg) {
1135 byte[] jpegData = new byte[rfb.readCompactLen()];
1136 rfb.readFully(jpegData);
1137 if (rfb.rec != null) {
1138 if (!rfb.recordFromBeginning) {
1139 rfb.recordCompactLen(jpegData.length);
1141 rfb.rec.write(jpegData);
1144 // Create an Image object from the JPEG data.
1145 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
1147 // Remember the rectangle where the image should be drawn.
1148 jpegRect = new Rectangle(x, y, w, h);
1150 // Let the imageUpdate() method do the actual drawing, here just
1151 // wait until the image is fully loaded and drawn.
1152 synchronized(jpegRect) {
1153 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
1155 // Wait no longer than three seconds.
1156 jpegRect.wait(3000);
1157 } catch (InterruptedException e) {
1158 throw new Exception("Interrupted while decoding JPEG image");
1162 // Done, jpegRect is not needed any more.
1168 // Read filter id and parameters.
1169 int numColors = 0, rowSize = w;
1170 byte[] palette8 = new byte[2];
1171 int[] palette24 = new int[256];
1172 boolean useGradient = false;
1173 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
1174 int filter_id = rfb.is.readUnsignedByte();
1175 if (rfb.rec != null) {
1176 rfb.rec.writeByte(filter_id);
1178 if (filter_id == rfb.TightFilterPalette) {
1179 numColors = rfb.is.readUnsignedByte() + 1;
1180 if (rfb.rec != null) {
1181 rfb.rec.writeByte(numColors - 1);
1183 if (bytesPixel == 1) {
1184 if (numColors != 2) {
1185 throw new Exception("Incorrect tight palette size: " + numColors);
1187 rfb.readFully(palette8);
1188 if (rfb.rec != null) {
1189 rfb.rec.write(palette8);
1192 byte[] buf = new byte[numColors * 3];
1194 if (rfb.rec != null) {
1197 for (int i = 0; i < numColors; i++) {
1198 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
1199 (buf[i * 3 + 1] & 0xFF) << 8 |
1200 (buf[i * 3 + 2] & 0xFF));
1204 rowSize = (w + 7) / 8;
1205 } else if (filter_id == rfb.TightFilterGradient) {
1207 } else if (filter_id != rfb.TightFilterCopy) {
1208 throw new Exception("Incorrect tight filter id: " + filter_id);
1211 if (numColors == 0 && bytesPixel == 4)
1214 // Read, optionally uncompress and decode data.
1215 int dataSize = h * rowSize;
1216 if (dataSize < rfb.TightMinToCompress) {
1217 // Data size is small - not compressed with zlib.
1218 if (numColors != 0) {
1220 byte[] indexedData = new byte[dataSize];
1221 rfb.readFully(indexedData);
1222 if (rfb.rec != null) {
1223 rfb.rec.write(indexedData);
1225 if (numColors == 2) {
1227 if (bytesPixel == 1) {
1228 decodeMonoData(x, y, w, h, indexedData, palette8);
1230 decodeMonoData(x, y, w, h, indexedData, palette24);
1233 // 3..255 colors (assuming bytesPixel == 4).
1235 for (int dy = y; dy < y + h; dy++) {
1236 for (int dx = x; dx < x + w; dx++) {
1237 pixels24[dy * rfb.framebufferWidth + dx] =
1238 palette24[indexedData[i++] & 0xFF];
1242 } else if (useGradient) {
1243 // "Gradient"-processed data
1244 byte[] buf = new byte[w * h * 3];
1246 if (rfb.rec != null) {
1249 decodeGradientData(x, y, w, h, buf);
1251 // Raw truecolor data.
1252 if (bytesPixel == 1) {
1253 for (int dy = y; dy < y + h; dy++) {
1254 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
1255 if (rfb.rec != null) {
1256 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
1260 byte[] buf = new byte[w * 3];
1262 for (int dy = y; dy < y + h; dy++) {
1264 if (rfb.rec != null) {
1267 offset = dy * rfb.framebufferWidth + x;
1268 for (i = 0; i < w; i++) {
1269 pixels24[offset + i] =
1270 (buf[i * 3] & 0xFF) << 16 |
1271 (buf[i * 3 + 1] & 0xFF) << 8 |
1272 (buf[i * 3 + 2] & 0xFF);
1278 // Data was compressed with zlib.
1279 int zlibDataLen = rfb.readCompactLen();
1280 byte[] zlibData = new byte[zlibDataLen];
1281 rfb.readFully(zlibData);
1282 if (rfb.rec != null && rfb.recordFromBeginning) {
1283 rfb.rec.write(zlibData);
1285 int stream_id = comp_ctl & 0x03;
1286 if (tightInflaters[stream_id] == null) {
1287 tightInflaters[stream_id] = new Inflater();
1289 Inflater myInflater = tightInflaters[stream_id];
1290 myInflater.setInput(zlibData);
1291 byte[] buf = new byte[dataSize];
1292 myInflater.inflate(buf);
1293 if (rfb.rec != null && !rfb.recordFromBeginning) {
1294 rfb.recordCompressedData(buf);
1297 if (numColors != 0) {
1299 if (numColors == 2) {
1301 if (bytesPixel == 1) {
1302 decodeMonoData(x, y, w, h, buf, palette8);
1304 decodeMonoData(x, y, w, h, buf, palette24);
1307 // More than two colors (assuming bytesPixel == 4).
1309 for (int dy = y; dy < y + h; dy++) {
1310 for (int dx = x; dx < x + w; dx++) {
1311 pixels24[dy * rfb.framebufferWidth + dx] =
1312 palette24[buf[i++] & 0xFF];
1316 } else if (useGradient) {
1317 // Compressed "Gradient"-filtered data (assuming bytesPixel == 4).
1318 decodeGradientData(x, y, w, h, buf);
1320 // Compressed truecolor data.
1321 if (bytesPixel == 1) {
1322 int destOffset = y * rfb.framebufferWidth + x;
1323 for (int dy = 0; dy < h; dy++) {
1324 System.arraycopy(buf, dy * w, pixels8, destOffset, w);
1325 destOffset += rfb.framebufferWidth;
1330 for (int dy = 0; dy < h; dy++) {
1331 myInflater.inflate(buf);
1332 destOffset = (y + dy) * rfb.framebufferWidth + x;
1333 for (i = 0; i < w; i++) {
1334 pixels24[destOffset + i] =
1335 (buf[srcOffset] & 0xFF) << 16 |
1336 (buf[srcOffset + 1] & 0xFF) << 8 |
1337 (buf[srcOffset + 2] & 0xFF);
1345 handleUpdatedPixels(x, y, w, h);
1346 scheduleRepaint(x, y, w, h);
1350 // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
1353 void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
1356 int i = y * rfb.framebufferWidth + x;
1357 int rowBytes = (w + 7) / 8;
1360 for (dy = 0; dy < h; dy++) {
1361 for (dx = 0; dx < w / 8; dx++) {
1362 b = src[dy*rowBytes+dx];
1363 for (n = 7; n >= 0; n--)
1364 pixels8[i++] = palette[b >> n & 1];
1366 for (n = 7; n >= 8 - w % 8; n--) {
1367 pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
1369 i += (rfb.framebufferWidth - w);
1373 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
1376 int i = y * rfb.framebufferWidth + x;
1377 int rowBytes = (w + 7) / 8;
1380 for (dy = 0; dy < h; dy++) {
1381 for (dx = 0; dx < w / 8; dx++) {
1382 b = src[dy*rowBytes+dx];
1383 for (n = 7; n >= 0; n--)
1384 pixels24[i++] = palette[b >> n & 1];
1386 for (n = 7; n >= 8 - w % 8; n--) {
1387 pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
1389 i += (rfb.framebufferWidth - w);
1394 // Decode data processed with the "Gradient" filter.
1397 void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
1400 byte[] prevRow = new byte[w * 3];
1401 byte[] thisRow = new byte[w * 3];
1402 byte[] pix = new byte[3];
1403 int[] est = new int[3];
1405 int offset = y * rfb.framebufferWidth + x;
1407 for (dy = 0; dy < h; dy++) {
1409 /* First pixel in a row */
1410 for (c = 0; c < 3; c++) {
1411 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
1412 thisRow[c] = pix[c];
1414 pixels24[offset++] =
1415 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
1417 /* Remaining pixels of a row */
1418 for (dx = 1; dx < w; dx++) {
1419 for (c = 0; c < 3; c++) {
1420 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
1421 (prevRow[(dx-1) * 3 + c] & 0xFF));
1422 if (est[c] > 0xFF) {
1424 } else if (est[c] < 0x00) {
1427 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
1428 thisRow[dx * 3 + c] = pix[c];
1430 pixels24[offset++] =
1431 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
1434 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
1435 offset += (rfb.framebufferWidth - w);
1440 // Display newly updated area of pixels.
1443 void handleUpdatedPixels(int x, int y, int w, int h) {
1445 // Draw updated pixels of the off-screen image.
1446 pixelsSource.newPixels(x, y, w, h);
1447 memGraphics.setClip(x, y, w, h);
1448 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
1449 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
1453 // Tell JVM to repaint specified desktop area.
1456 void scheduleRepaint(int x, int y, int w, int h) {
1457 // Request repaint, deferred if necessary.
1458 if (rfb.framebufferWidth == scaledWidth) {
1459 repaint(viewer.deferScreenUpdates, x, y, w, h);
1461 int sx = x * scalingFactor / 100;
1462 int sy = y * scalingFactor / 100;
1463 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
1464 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
1465 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
1473 public void keyPressed(KeyEvent evt) {
1474 processLocalKeyEvent(evt);
1476 public void keyReleased(KeyEvent evt) {
1477 processLocalKeyEvent(evt);
1479 public void keyTyped(KeyEvent evt) {
1483 public void mousePressed(MouseEvent evt) {
1484 processLocalMouseEvent(evt, false);
1486 public void mouseReleased(MouseEvent evt) {
1487 processLocalMouseEvent(evt, false);
1489 public void mouseMoved(MouseEvent evt) {
1490 processLocalMouseEvent(evt, true);
1492 public void mouseDragged(MouseEvent evt) {
1493 processLocalMouseEvent(evt, true);
1496 public void processLocalKeyEvent(KeyEvent evt) {
1497 if (viewer.rfb != null && rfb.inNormalProtocol) {
1498 if (!inputEnabled) {
1499 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
1500 evt.getID() == KeyEvent.KEY_PRESSED ) {
1501 // Request screen update.
1503 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
1504 rfb.framebufferHeight, false);
1505 } catch (IOException e) {
1506 e.printStackTrace();
1513 rfb.writeKeyEvent(evt);
1514 } catch (Exception e) {
1515 e.printStackTrace();
1521 // Don't ever pass keyboard events to AWT for default processing.
1522 // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
1526 public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
1527 if (viewer.rfb != null && rfb.inNormalProtocol) {
1529 softCursorMove(evt.getX(), evt.getY());
1531 if (rfb.framebufferWidth != scaledWidth) {
1532 int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
1533 int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
1534 evt.translatePoint(sx - evt.getX(), sy - evt.getY());
1538 rfb.writePointerEvent(evt);
1539 } catch (Exception e) {
1540 e.printStackTrace();
1551 public void mouseClicked(MouseEvent evt) {}
1552 public void mouseEntered(MouseEvent evt) {}
1553 public void mouseExited(MouseEvent evt) {}
1556 //////////////////////////////////////////////////////////////////
1558 // Handle cursor shape updates (XCursor and RichCursor encodings).
1561 boolean showSoftCursor = false;
1563 MemoryImageSource softCursorSource;
1566 int cursorX = 0, cursorY = 0;
1567 int cursorWidth, cursorHeight;
1568 int origCursorWidth, origCursorHeight;
1570 int origHotX, origHotY;
1573 // Handle cursor shape update (XCursor and RichCursor encodings).
1577 handleCursorShapeUpdate(int encodingType,
1578 int xhot, int yhot, int width, int height)
1579 throws IOException {
1583 if (width * height == 0)
1586 // Ignore cursor shape data if requested by user.
1587 if (viewer.options.ignoreCursorUpdates) {
1588 int bytesPerRow = (width + 7) / 8;
1589 int bytesMaskData = bytesPerRow * height;
1591 if (encodingType == rfb.EncodingXCursor) {
1592 rfb.is.skipBytes(6 + bytesMaskData * 2);
1594 // rfb.EncodingRichCursor
1595 rfb.is.skipBytes(width * height + bytesMaskData);
1600 // Decode cursor pixel data.
1601 softCursorSource = decodeCursorShape(encodingType, width, height);
1603 // Set original (non-scaled) cursor dimensions.
1604 origCursorWidth = width;
1605 origCursorHeight = height;
1609 // Create off-screen cursor image.
1613 showSoftCursor = true;
1614 repaint(viewer.deferCursorUpdates,
1615 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1619 // decodeCursorShape(). Decode cursor pixel data and return
1620 // corresponding MemoryImageSource instance.
1623 synchronized MemoryImageSource
1624 decodeCursorShape(int encodingType, int width, int height)
1625 throws IOException {
1627 int bytesPerRow = (width + 7) / 8;
1628 int bytesMaskData = bytesPerRow * height;
1630 int[] softCursorPixels = new int[width * height];
1632 if (encodingType == rfb.EncodingXCursor) {
1634 // Read foreground and background colors of the cursor.
1635 byte[] rgb = new byte[6];
1637 int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
1638 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
1639 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
1640 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
1642 // Read pixel and mask data.
1643 byte[] pixBuf = new byte[bytesMaskData];
1644 rfb.readFully(pixBuf);
1645 byte[] maskBuf = new byte[bytesMaskData];
1646 rfb.readFully(maskBuf);
1648 // Decode pixel data into softCursorPixels[].
1649 byte pixByte, maskByte;
1650 int x, y, n, result;
1652 for (y = 0; y < height; y++) {
1653 for (x = 0; x < width / 8; x++) {
1654 pixByte = pixBuf[y * bytesPerRow + x];
1655 maskByte = maskBuf[y * bytesPerRow + x];
1656 for (n = 7; n >= 0; n--) {
1657 if ((maskByte >> n & 1) != 0) {
1658 result = colors[pixByte >> n & 1];
1660 result = 0; // Transparent pixel
1662 softCursorPixels[i++] = result;
1665 for (n = 7; n >= 8 - width % 8; n--) {
1666 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1667 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
1669 result = 0; // Transparent pixel
1671 softCursorPixels[i++] = result;
1676 // encodingType == rfb.EncodingRichCursor
1678 // Read pixel and mask data.
1679 byte[] pixBuf = new byte[width * height * bytesPixel];
1680 rfb.readFully(pixBuf);
1681 byte[] maskBuf = new byte[bytesMaskData];
1682 rfb.readFully(maskBuf);
1684 // Decode pixel data into softCursorPixels[].
1685 byte pixByte, maskByte;
1686 int x, y, n, result;
1688 for (y = 0; y < height; y++) {
1689 for (x = 0; x < width / 8; x++) {
1690 maskByte = maskBuf[y * bytesPerRow + x];
1691 for (n = 7; n >= 0; n--) {
1692 if ((maskByte >> n & 1) != 0) {
1693 if (bytesPixel == 1) {
1694 result = cm8.getRGB(pixBuf[i]);
1696 result = 0xFF000000 |
1697 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1698 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1699 (pixBuf[i * 4] & 0xFF);
1702 result = 0; // Transparent pixel
1704 softCursorPixels[i++] = result;
1707 for (n = 7; n >= 8 - width % 8; n--) {
1708 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1709 if (bytesPixel == 1) {
1710 result = cm8.getRGB(pixBuf[i]);
1712 result = 0xFF000000 |
1713 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1714 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1715 (pixBuf[i * 4] & 0xFF);
1718 result = 0; // Transparent pixel
1720 softCursorPixels[i++] = result;
1726 return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1730 // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1731 // Uses softCursorSource as a source for new cursor image.
1735 createSoftCursor() {
1737 if (softCursorSource == null)
1740 int scaleCursor = viewer.options.scaleCursor;
1741 if (scaleCursor == 0 || !inputEnabled)
1744 // Save original cursor coordinates.
1745 int x = cursorX - hotX;
1746 int y = cursorY - hotY;
1747 int w = cursorWidth;
1748 int h = cursorHeight;
1750 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1751 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1752 hotX = (origHotX * scaleCursor + 50) / 100;
1753 hotY = (origHotY * scaleCursor + 50) / 100;
1754 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
1756 if (scaleCursor != 100) {
1757 softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1758 Image.SCALE_SMOOTH);
1761 if (showSoftCursor) {
1762 // Compute screen area to update.
1763 x = Math.min(x, cursorX - hotX);
1764 y = Math.min(y, cursorY - hotY);
1765 w = Math.max(w, cursorWidth);
1766 h = Math.max(h, cursorHeight);
1768 repaint(viewer.deferCursorUpdates, x, y, w, h);
1773 // softCursorMove(). Moves soft cursor into a particular location.
1776 synchronized void softCursorMove(int x, int y) {
1781 if (showSoftCursor) {
1782 repaint(viewer.deferCursorUpdates,
1783 oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
1784 repaint(viewer.deferCursorUpdates,
1785 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1790 // softCursorFree(). Remove soft cursor, dispose resources.
1793 synchronized void softCursorFree() {
1794 if (showSoftCursor) {
1795 showSoftCursor = false;
1797 softCursorSource = null;
1799 repaint(viewer.deferCursorUpdates,
1800 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);