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.
82 int extraModifiers = 0;
88 public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
93 maxHeight = maxHeight_;
96 scalingFactor = viewer.options.scalingFactor;
98 tightInflaters = new Inflater[4];
100 cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
101 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
103 colors = new Color[256];
104 for (int i = 0; i < 256; i++)
105 colors[i] = new Color(cm8.getRGB(i));
109 inputEnabled = false;
110 if (!viewer.options.viewOnly)
113 // Keyboard listener is enabled even in view-only mode, to catch
114 // 'r' or 'R' key presses used to request screen update.
115 addKeyListener(this);
118 public VncCanvas(VncViewer v) throws IOException {
123 // Callback methods to determine geometry of our Component.
126 public Dimension getPreferredSize() {
127 return new Dimension(scaledWidth, scaledHeight);
130 public Dimension getMinimumSize() {
131 return new Dimension(scaledWidth, scaledHeight);
134 public Dimension getMaximumSize() {
135 return new Dimension(scaledWidth, scaledHeight);
139 // All painting is performed here.
142 public void update(Graphics g) {
146 public void paint(Graphics g) {
147 synchronized(memImage) {
148 if (rfb.framebufferWidth == scaledWidth) {
149 g.drawImage(memImage, 0, 0, null);
151 paintScaledFrameBuffer(g);
154 if (showSoftCursor) {
155 int x0 = cursorX - hotX, y0 = cursorY - hotY;
156 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
157 if (r.intersects(g.getClipBounds())) {
158 g.drawImage(softCursor, x0, y0, null);
163 public void paintScaledFrameBuffer(Graphics g) {
164 g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
168 // Override the ImageObserver interface method to handle drawing of
169 // JPEG-encoded data.
172 public boolean imageUpdate(Image img, int infoflags,
173 int x, int y, int width, int height) {
174 if ((infoflags & (ALLBITS | ABORT)) == 0) {
175 return true; // We need more image data.
177 // If the whole image is available, draw it now.
178 if ((infoflags & ALLBITS) != 0) {
179 if (jpegRect != null) {
180 synchronized(jpegRect) {
181 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
182 scheduleRepaint(jpegRect.x, jpegRect.y,
183 jpegRect.width, jpegRect.height);
188 return false; // All image data was processed.
193 // Start/stop receiving mouse events. Keyboard events are received
194 // even in view-only mode, because we want to map the 'r' key to the
195 // screen refreshing function.
198 public synchronized void enableInput(boolean enable) {
199 if (enable && !inputEnabled) {
201 addMouseListener(this);
202 addMouseMotionListener(this);
203 if (viewer.showControls) {
204 viewer.buttonPanel.enableRemoteAccessControls(true);
206 createSoftCursor(); // scaled cursor
207 } else if (!enable && inputEnabled) {
208 inputEnabled = false;
209 removeMouseListener(this);
210 removeMouseMotionListener(this);
211 if (viewer.showControls) {
212 viewer.buttonPanel.enableRemoteAccessControls(false);
214 createSoftCursor(); // non-scaled cursor
218 public void setPixelFormat() throws IOException {
219 if (viewer.options.eightBitColors) {
220 rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
223 rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
226 updateFramebufferSize();
229 void updateFramebufferSize() {
232 int fbWidth = rfb.framebufferWidth;
233 int fbHeight = rfb.framebufferHeight;
235 // Calculate scaling factor for auto scaling.
236 if (maxWidth > 0 && maxHeight > 0) {
237 int f1 = maxWidth * 100 / fbWidth;
238 int f2 = maxHeight * 100 / fbHeight;
239 scalingFactor = Math.min(f1, f2);
240 if (scalingFactor > 100)
242 System.out.println("Scaling desktop at " + scalingFactor + "%");
245 // Update scaled framebuffer geometry.
246 scaledWidth = (fbWidth * scalingFactor + 50) / 100;
247 scaledHeight = (fbHeight * scalingFactor + 50) / 100;
249 // Create new off-screen image either if it does not exist, or if
250 // its geometry should be changed. It's not necessary to replace
251 // existing image if only pixel format should be changed.
252 if (memImage == null) {
253 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
254 memGraphics = memImage.getGraphics();
255 } else if (memImage.getWidth(null) != fbWidth ||
256 memImage.getHeight(null) != fbHeight) {
257 synchronized(memImage) {
258 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
259 memGraphics = memImage.getGraphics();
263 // Images with raw pixels should be re-allocated on every change
264 // of geometry or pixel format.
265 if (bytesPixel == 1) {
268 pixels8 = new byte[fbWidth * fbHeight];
271 new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth);
273 zrleTilePixels24 = null;
274 zrleTilePixels8 = new byte[64 * 64];
279 pixels24 = new int[fbWidth * fbHeight];
282 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
284 zrleTilePixels8 = null;
285 zrleTilePixels24 = new int[64 * 64];
288 pixelsSource.setAnimated(true);
289 rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
291 // Update the size of desktop containers.
292 if (viewer.inSeparateFrame) {
293 if (viewer.desktopScrollPane != null)
294 resizeDesktopFrame();
296 setSize(scaledWidth, scaledHeight);
298 viewer.moveFocusToDesktop();
301 void resizeDesktopFrame() {
302 setSize(scaledWidth, scaledHeight);
304 // FIXME: Find a better way to determine correct size of a
305 // ScrollPane. -- const
306 Insets insets = viewer.desktopScrollPane.getInsets();
307 viewer.desktopScrollPane.setSize(scaledWidth +
308 2 * Math.min(insets.left, insets.right),
310 2 * Math.min(insets.top, insets.bottom));
312 viewer.vncFrame.pack();
314 // Try to limit the frame size to the screen size.
316 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
317 Dimension frameSize = viewer.vncFrame.getSize();
318 Dimension newSize = frameSize;
320 // Reduce Screen Size by 30 pixels in each direction;
321 // This is a (poor) attempt to account for
322 // 1) Menu bar on Macintosh (should really also account for
323 // Dock on OSX). Usually 22px on top of screen.
324 // 2) Taxkbar on Windows (usually about 28 px on bottom)
325 // 3) Other obstructions.
327 screenSize.height -= 30;
328 screenSize.width -= 30;
330 boolean needToResizeFrame = false;
331 if (frameSize.height > screenSize.height) {
332 newSize.height = screenSize.height;
333 needToResizeFrame = true;
335 if (frameSize.width > screenSize.width) {
336 newSize.width = screenSize.width;
337 needToResizeFrame = true;
339 if (needToResizeFrame) {
340 viewer.vncFrame.setSize(newSize);
343 viewer.desktopScrollPane.doLayout();
347 // processNormalProtocol() - executed by the rfbThread to deal with the
351 public void processNormalProtocol() throws Exception {
353 // Start/stop session recording if necessary.
354 viewer.checkRecordingStatus();
356 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
357 rfb.framebufferHeight, false);
360 // main dispatch loop
365 // Read message type from the server.
366 int msgType = rfb.readServerMessageType();
368 // Process the message depending on its type.
370 case RfbProto.FramebufferUpdate:
371 rfb.readFramebufferUpdate();
373 boolean cursorPosReceived = false;
375 for (int i = 0; i < rfb.updateNRects; i++) {
376 rfb.readFramebufferUpdateRectHdr();
377 int rx = rfb.updateRectX, ry = rfb.updateRectY;
378 int rw = rfb.updateRectW, rh = rfb.updateRectH;
380 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
383 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
384 rfb.setFramebufferSize(rw, rh);
385 updateFramebufferSize();
389 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
390 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
391 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
395 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
396 softCursorMove(rx, ry);
397 cursorPosReceived = true;
403 switch (rfb.updateRectEncoding) {
404 case RfbProto.EncodingRaw:
405 handleRawRect(rx, ry, rw, rh);
407 case RfbProto.EncodingCopyRect:
408 handleCopyRect(rx, ry, rw, rh);
410 case RfbProto.EncodingRRE:
411 handleRRERect(rx, ry, rw, rh);
413 case RfbProto.EncodingCoRRE:
414 handleCoRRERect(rx, ry, rw, rh);
416 case RfbProto.EncodingHextile:
417 handleHextileRect(rx, ry, rw, rh);
419 case RfbProto.EncodingZRLE:
420 handleZRLERect(rx, ry, rw, rh);
422 case RfbProto.EncodingZlib:
423 handleZlibRect(rx, ry, rw, rh);
425 case RfbProto.EncodingTight:
426 handleTightRect(rx, ry, rw, rh);
429 throw new Exception("Unknown RFB rectangle encoding " +
430 rfb.updateRectEncoding);
436 boolean fullUpdateNeeded = false;
438 // Start/stop session recording if necessary. Request full
439 // update if a new session file was opened.
440 if (viewer.checkRecordingStatus())
441 fullUpdateNeeded = true;
443 // Defer framebuffer update request if necessary. But wake up
444 // immediately on keyboard or mouse event. Also, don't sleep
445 // if there is some data to receive, or if the last update
446 // included a PointerPos message.
447 if (viewer.deferUpdateRequests > 0 &&
448 rfb.is.available() == 0 && !cursorPosReceived) {
451 rfb.wait(viewer.deferUpdateRequests);
452 } catch (InterruptedException e) {
457 // Before requesting framebuffer update, check if the pixel
458 // format should be changed. If it should, request full update
459 // instead of an incremental one.
460 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
462 fullUpdateNeeded = true;
465 viewer.autoSelectEncodings();
467 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
468 rfb.framebufferHeight,
473 case RfbProto.SetColourMapEntries:
474 throw new Exception("Can't handle SetColourMapEntries message");
477 Toolkit.getDefaultToolkit().beep();
480 case RfbProto.ServerCutText:
481 String s = rfb.readServerCutText();
482 viewer.clipboard.setCutText(s);
486 throw new Exception("Unknown RFB message type " + msgType);
493 // Handle a raw rectangle. The second form with paint==false is used
494 // by the Hextile decoder for raw-encoded tiles.
497 void handleRawRect(int x, int y, int w, int h) throws IOException {
498 handleRawRect(x, y, w, h, true);
501 void handleRawRect(int x, int y, int w, int h, boolean paint)
504 if (bytesPixel == 1) {
505 for (int dy = y; dy < y + h; dy++) {
506 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
507 if (rfb.rec != null) {
508 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
512 byte[] buf = new byte[w * 4];
514 for (int dy = y; dy < y + h; dy++) {
516 if (rfb.rec != null) {
519 offset = dy * rfb.framebufferWidth + x;
520 for (i = 0; i < w; i++) {
521 pixels24[offset + i] =
522 (buf[i * 4 + 2] & 0xFF) << 16 |
523 (buf[i * 4 + 1] & 0xFF) << 8 |
529 handleUpdatedPixels(x, y, w, h);
531 scheduleRepaint(x, y, w, h);
535 // Handle a CopyRect rectangle.
538 void handleCopyRect(int x, int y, int w, int h) throws IOException {
541 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
542 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
544 scheduleRepaint(x, y, w, h);
548 // Handle an RRE-encoded rectangle.
551 void handleRRERect(int x, int y, int w, int h) throws IOException {
553 int nSubrects = rfb.is.readInt();
555 byte[] bg_buf = new byte[bytesPixel];
556 rfb.readFully(bg_buf);
558 if (bytesPixel == 1) {
559 pixel = colors[bg_buf[0] & 0xFF];
561 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
563 memGraphics.setColor(pixel);
564 memGraphics.fillRect(x, y, w, h);
566 byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
568 DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));
570 if (rfb.rec != null) {
571 rfb.rec.writeIntBE(nSubrects);
572 rfb.rec.write(bg_buf);
578 for (int j = 0; j < nSubrects; j++) {
579 if (bytesPixel == 1) {
580 pixel = colors[ds.readUnsignedByte()];
583 pixel = new Color(buf[j*12+2] & 0xFF,
587 sx = x + ds.readUnsignedShort();
588 sy = y + ds.readUnsignedShort();
589 sw = ds.readUnsignedShort();
590 sh = ds.readUnsignedShort();
592 memGraphics.setColor(pixel);
593 memGraphics.fillRect(sx, sy, sw, sh);
596 scheduleRepaint(x, y, w, h);
600 // Handle a CoRRE-encoded rectangle.
603 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
604 int nSubrects = rfb.is.readInt();
606 byte[] bg_buf = new byte[bytesPixel];
607 rfb.readFully(bg_buf);
609 if (bytesPixel == 1) {
610 pixel = colors[bg_buf[0] & 0xFF];
612 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
614 memGraphics.setColor(pixel);
615 memGraphics.fillRect(x, y, w, h);
617 byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
620 if (rfb.rec != null) {
621 rfb.rec.writeIntBE(nSubrects);
622 rfb.rec.write(bg_buf);
629 for (int j = 0; j < nSubrects; j++) {
630 if (bytesPixel == 1) {
631 pixel = colors[buf[i++] & 0xFF];
633 pixel = new Color(buf[i+2] & 0xFF, buf[i+1] & 0xFF, buf[i] & 0xFF);
636 sx = x + (buf[i++] & 0xFF);
637 sy = y + (buf[i++] & 0xFF);
638 sw = buf[i++] & 0xFF;
639 sh = buf[i++] & 0xFF;
641 memGraphics.setColor(pixel);
642 memGraphics.fillRect(sx, sy, sw, sh);
645 scheduleRepaint(x, y, w, h);
649 // Handle a Hextile-encoded rectangle.
652 // These colors should be kept between handleHextileSubrect() calls.
653 private Color hextile_bg, hextile_fg;
655 void handleHextileRect(int x, int y, int w, int h) throws IOException {
657 hextile_bg = new Color(0);
658 hextile_fg = new Color(0);
660 for (int ty = y; ty < y + h; ty += 16) {
665 for (int tx = x; tx < x + w; tx += 16) {
670 handleHextileSubrect(tx, ty, tw, th);
673 // Finished with a row of tiles, now let's show it.
674 scheduleRepaint(x, y, w, h);
679 // Handle one tile in the Hextile-encoded data.
682 void handleHextileSubrect(int tx, int ty, int tw, int th)
685 int subencoding = rfb.is.readUnsignedByte();
686 if (rfb.rec != null) {
687 rfb.rec.writeByte(subencoding);
690 // Is it a raw-encoded sub-rectangle?
691 if ((subencoding & rfb.HextileRaw) != 0) {
692 handleRawRect(tx, ty, tw, th, false);
696 // Read and draw the background if specified.
697 byte[] cbuf = new byte[bytesPixel];
698 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
700 if (bytesPixel == 1) {
701 hextile_bg = colors[cbuf[0] & 0xFF];
703 hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
705 if (rfb.rec != null) {
709 memGraphics.setColor(hextile_bg);
710 memGraphics.fillRect(tx, ty, tw, th);
712 // Read the foreground color if specified.
713 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
715 if (bytesPixel == 1) {
716 hextile_fg = colors[cbuf[0] & 0xFF];
718 hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
720 if (rfb.rec != null) {
725 // Done with this tile if there is no sub-rectangles.
726 if ((subencoding & rfb.HextileAnySubrects) == 0)
729 int nSubrects = rfb.is.readUnsignedByte();
730 int bufsize = nSubrects * 2;
731 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
732 bufsize += nSubrects * bytesPixel;
734 byte[] buf = new byte[bufsize];
736 if (rfb.rec != null) {
737 rfb.rec.writeByte(nSubrects);
741 int b1, b2, sx, sy, sw, sh;
744 if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
746 // Sub-rectangles are all of the same color.
747 memGraphics.setColor(hextile_fg);
748 for (int j = 0; j < nSubrects; j++) {
749 b1 = buf[i++] & 0xFF;
750 b2 = buf[i++] & 0xFF;
752 sy = ty + (b1 & 0xf);
755 memGraphics.fillRect(sx, sy, sw, sh);
757 } else if (bytesPixel == 1) {
759 // BGR233 (8-bit color) version for colored sub-rectangles.
760 for (int j = 0; j < nSubrects; j++) {
761 hextile_fg = colors[buf[i++] & 0xFF];
762 b1 = buf[i++] & 0xFF;
763 b2 = buf[i++] & 0xFF;
765 sy = ty + (b1 & 0xf);
768 memGraphics.setColor(hextile_fg);
769 memGraphics.fillRect(sx, sy, sw, sh);
774 // Full-color (24-bit) version for colored sub-rectangles.
775 for (int j = 0; j < nSubrects; j++) {
776 hextile_fg = new Color(buf[i+2] & 0xFF,
780 b1 = buf[i++] & 0xFF;
781 b2 = buf[i++] & 0xFF;
783 sy = ty + (b1 & 0xf);
786 memGraphics.setColor(hextile_fg);
787 memGraphics.fillRect(sx, sy, sw, sh);
794 // Handle a ZRLE-encoded rectangle.
796 // FIXME: Currently, session recording is not fully supported for ZRLE.
799 void handleZRLERect(int x, int y, int w, int h) throws Exception {
801 if (zrleInStream == null)
802 zrleInStream = new ZlibInStream();
804 int nBytes = rfb.is.readInt();
805 if (nBytes > 64 * 1024 * 1024)
806 throw new Exception("ZRLE decoder: illegal compressed data size");
808 if (zrleBuf == null || zrleBufLen < nBytes) {
809 zrleBufLen = nBytes + 4096;
810 zrleBuf = new byte[zrleBufLen];
813 // FIXME: Do not wait for all the data before decompression.
814 rfb.readFully(zrleBuf, 0, nBytes);
816 if (rfb.rec != null) {
817 if (rfb.recordFromBeginning) {
818 rfb.rec.writeIntBE(nBytes);
819 rfb.rec.write(zrleBuf, 0, nBytes);
820 } else if (!zrleRecWarningShown) {
821 System.out.println("Warning: ZRLE session can be recorded" +
822 " only from the beginning");
823 System.out.println("Warning: Recorded file may be corrupted");
824 zrleRecWarningShown = true;
828 zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
830 for (int ty = y; ty < y+h; ty += 64) {
832 int th = Math.min(y+h-ty, 64);
834 for (int tx = x; tx < x+w; tx += 64) {
836 int tw = Math.min(x+w-tx, 64);
838 int mode = zrleInStream.readU8();
839 boolean rle = (mode & 128) != 0;
840 int palSize = mode & 127;
841 int[] palette = new int[128];
843 readZrlePalette(palette, palSize);
846 int pix = palette[0];
847 Color c = (bytesPixel == 1) ?
848 colors[pix] : new Color(0xFF000000 | pix);
849 memGraphics.setColor(c);
850 memGraphics.fillRect(tx, ty, tw, th);
856 readZrleRawPixels(tw, th);
858 readZrlePackedPixels(tw, th, palette, palSize);
862 readZrlePlainRLEPixels(tw, th);
864 readZrlePackedRLEPixels(tw, th, palette);
867 handleUpdatedZrleTile(tx, ty, tw, th);
871 zrleInStream.reset();
873 scheduleRepaint(x, y, w, h);
876 int readPixel(InStream is) throws Exception {
878 if (bytesPixel == 1) {
881 int p1 = is.readU8();
882 int p2 = is.readU8();
883 int p3 = is.readU8();
884 pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
889 void readPixels(InStream is, int[] dst, int count) throws Exception {
891 if (bytesPixel == 1) {
892 byte[] buf = new byte[count];
893 is.readBytes(buf, 0, count);
894 for (int i = 0; i < count; i++) {
895 dst[i] = (int)buf[i] & 0xFF;
898 byte[] buf = new byte[count * 3];
899 is.readBytes(buf, 0, count * 3);
900 for (int i = 0; i < count; i++) {
901 dst[i] = ((buf[i*3+2] & 0xFF) << 16 |
902 (buf[i*3+1] & 0xFF) << 8 |
908 void readZrlePalette(int[] palette, int palSize) throws Exception {
909 readPixels(zrleInStream, palette, palSize);
912 void readZrleRawPixels(int tw, int th) throws Exception {
913 if (bytesPixel == 1) {
914 zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
916 readPixels(zrleInStream, zrleTilePixels24, tw * th); ///
920 void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
923 int bppp = ((palSize > 16) ? 8 :
924 ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
927 for (int i = 0; i < th; i++) {
934 b = zrleInStream.readU8();
938 int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
939 if (bytesPixel == 1) {
940 zrleTilePixels8[ptr++] = (byte)palette[index];
942 zrleTilePixels24[ptr++] = palette[index];
948 void readZrlePlainRLEPixels(int tw, int th) throws Exception {
950 int end = ptr + tw * th;
952 int pix = readPixel(zrleInStream);
956 b = zrleInStream.readU8();
960 if (!(len <= end - ptr))
961 throw new Exception("ZRLE decoder: assertion failed" +
962 " (len <= end-ptr)");
964 if (bytesPixel == 1) {
965 while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
967 while (len-- > 0) zrleTilePixels24[ptr++] = pix;
972 void readZrlePackedRLEPixels(int tw, int th, int[] palette)
976 int end = ptr + tw * th;
978 int index = zrleInStream.readU8();
980 if ((index & 128) != 0) {
983 b = zrleInStream.readU8();
987 if (!(len <= end - ptr))
988 throw new Exception("ZRLE decoder: assertion failed" +
989 " (len <= end - ptr)");
993 int pix = palette[index];
995 if (bytesPixel == 1) {
996 while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
998 while (len-- > 0) zrleTilePixels24[ptr++] = pix;
1004 // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
1007 void handleUpdatedZrleTile(int x, int y, int w, int h) {
1009 if (bytesPixel == 1) {
1010 src = zrleTilePixels8; dst = pixels8;
1012 src = zrleTilePixels24; dst = pixels24;
1015 int offsetDst = (y * rfb.framebufferWidth + x);
1016 for (int j = 0; j < h; j++) {
1017 System.arraycopy(src, offsetSrc, dst, offsetDst, w);
1019 offsetDst += rfb.framebufferWidth;
1021 handleUpdatedPixels(x, y, w, h);
1025 // Handle a Zlib-encoded rectangle.
1028 void handleZlibRect(int x, int y, int w, int h) throws Exception {
1030 int nBytes = rfb.is.readInt();
1032 if (zlibBuf == null || zlibBufLen < nBytes) {
1033 zlibBufLen = nBytes * 2;
1034 zlibBuf = new byte[zlibBufLen];
1037 rfb.readFully(zlibBuf, 0, nBytes);
1039 if (rfb.rec != null && rfb.recordFromBeginning) {
1040 rfb.rec.writeIntBE(nBytes);
1041 rfb.rec.write(zlibBuf, 0, nBytes);
1044 if (zlibInflater == null) {
1045 zlibInflater = new Inflater();
1047 zlibInflater.setInput(zlibBuf, 0, nBytes);
1049 if (bytesPixel == 1) {
1050 for (int dy = y; dy < y + h; dy++) {
1051 zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
1052 if (rfb.rec != null && !rfb.recordFromBeginning)
1053 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
1056 byte[] buf = new byte[w * 4];
1058 for (int dy = y; dy < y + h; dy++) {
1059 zlibInflater.inflate(buf);
1060 offset = dy * rfb.framebufferWidth + x;
1061 for (i = 0; i < w; i++) {
1062 pixels24[offset + i] =
1063 (buf[i * 4 + 2] & 0xFF) << 16 |
1064 (buf[i * 4 + 1] & 0xFF) << 8 |
1065 (buf[i * 4] & 0xFF);
1067 if (rfb.rec != null && !rfb.recordFromBeginning)
1072 handleUpdatedPixels(x, y, w, h);
1073 scheduleRepaint(x, y, w, h);
1077 // Handle a Tight-encoded rectangle.
1080 void handleTightRect(int x, int y, int w, int h) throws Exception {
1082 int comp_ctl = rfb.is.readUnsignedByte();
1083 if (rfb.rec != null) {
1084 if (rfb.recordFromBeginning ||
1085 comp_ctl == (rfb.TightFill << 4) ||
1086 comp_ctl == (rfb.TightJpeg << 4)) {
1087 // Send data exactly as received.
1088 rfb.rec.writeByte(comp_ctl);
1090 // Tell the decoder to flush each of the four zlib streams.
1091 rfb.rec.writeByte(comp_ctl | 0x0F);
1095 // Flush zlib streams if we are told by the server to do so.
1096 for (int stream_id = 0; stream_id < 4; stream_id++) {
1097 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
1098 tightInflaters[stream_id] = null;
1103 // Check correctness of subencoding value.
1104 if (comp_ctl > rfb.TightMaxSubencoding) {
1105 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
1108 // Handle solid-color rectangles.
1109 if (comp_ctl == rfb.TightFill) {
1111 if (bytesPixel == 1) {
1112 int idx = rfb.is.readUnsignedByte();
1113 memGraphics.setColor(colors[idx]);
1114 if (rfb.rec != null) {
1115 rfb.rec.writeByte(idx);
1118 byte[] buf = new byte[3];
1120 if (rfb.rec != null) {
1123 Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 |
1124 (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
1125 memGraphics.setColor(bg);
1127 memGraphics.fillRect(x, y, w, h);
1128 scheduleRepaint(x, y, w, h);
1133 if (comp_ctl == rfb.TightJpeg) {
1136 byte[] jpegData = new byte[rfb.readCompactLen()];
1137 rfb.readFully(jpegData);
1138 if (rfb.rec != null) {
1139 if (!rfb.recordFromBeginning) {
1140 rfb.recordCompactLen(jpegData.length);
1142 rfb.rec.write(jpegData);
1145 // Create an Image object from the JPEG data.
1146 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
1148 // Remember the rectangle where the image should be drawn.
1149 jpegRect = new Rectangle(x, y, w, h);
1151 // Let the imageUpdate() method do the actual drawing, here just
1152 // wait until the image is fully loaded and drawn.
1153 synchronized(jpegRect) {
1154 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
1156 // Wait no longer than three seconds.
1157 jpegRect.wait(3000);
1158 } catch (InterruptedException e) {
1159 throw new Exception("Interrupted while decoding JPEG image");
1163 // Done, jpegRect is not needed any more.
1169 // Read filter id and parameters.
1170 int numColors = 0, rowSize = w;
1171 byte[] palette8 = new byte[2];
1172 int[] palette24 = new int[256];
1173 boolean useGradient = false;
1174 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
1175 int filter_id = rfb.is.readUnsignedByte();
1176 if (rfb.rec != null) {
1177 rfb.rec.writeByte(filter_id);
1179 if (filter_id == rfb.TightFilterPalette) {
1180 numColors = rfb.is.readUnsignedByte() + 1;
1181 if (rfb.rec != null) {
1182 rfb.rec.writeByte(numColors - 1);
1184 if (bytesPixel == 1) {
1185 if (numColors != 2) {
1186 throw new Exception("Incorrect tight palette size: " + numColors);
1188 rfb.readFully(palette8);
1189 if (rfb.rec != null) {
1190 rfb.rec.write(palette8);
1193 byte[] buf = new byte[numColors * 3];
1195 if (rfb.rec != null) {
1198 for (int i = 0; i < numColors; i++) {
1199 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
1200 (buf[i * 3 + 1] & 0xFF) << 8 |
1201 (buf[i * 3 + 2] & 0xFF));
1205 rowSize = (w + 7) / 8;
1206 } else if (filter_id == rfb.TightFilterGradient) {
1208 } else if (filter_id != rfb.TightFilterCopy) {
1209 throw new Exception("Incorrect tight filter id: " + filter_id);
1212 if (numColors == 0 && bytesPixel == 4)
1215 // Read, optionally uncompress and decode data.
1216 int dataSize = h * rowSize;
1217 if (dataSize < rfb.TightMinToCompress) {
1218 // Data size is small - not compressed with zlib.
1219 if (numColors != 0) {
1221 byte[] indexedData = new byte[dataSize];
1222 rfb.readFully(indexedData);
1223 if (rfb.rec != null) {
1224 rfb.rec.write(indexedData);
1226 if (numColors == 2) {
1228 if (bytesPixel == 1) {
1229 decodeMonoData(x, y, w, h, indexedData, palette8);
1231 decodeMonoData(x, y, w, h, indexedData, palette24);
1234 // 3..255 colors (assuming bytesPixel == 4).
1236 for (int dy = y; dy < y + h; dy++) {
1237 for (int dx = x; dx < x + w; dx++) {
1238 pixels24[dy * rfb.framebufferWidth + dx] =
1239 palette24[indexedData[i++] & 0xFF];
1243 } else if (useGradient) {
1244 // "Gradient"-processed data
1245 byte[] buf = new byte[w * h * 3];
1247 if (rfb.rec != null) {
1250 decodeGradientData(x, y, w, h, buf);
1252 // Raw truecolor data.
1253 if (bytesPixel == 1) {
1254 for (int dy = y; dy < y + h; dy++) {
1255 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
1256 if (rfb.rec != null) {
1257 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
1261 byte[] buf = new byte[w * 3];
1263 for (int dy = y; dy < y + h; dy++) {
1265 if (rfb.rec != null) {
1268 offset = dy * rfb.framebufferWidth + x;
1269 for (i = 0; i < w; i++) {
1270 pixels24[offset + i] =
1271 (buf[i * 3] & 0xFF) << 16 |
1272 (buf[i * 3 + 1] & 0xFF) << 8 |
1273 (buf[i * 3 + 2] & 0xFF);
1279 // Data was compressed with zlib.
1280 int zlibDataLen = rfb.readCompactLen();
1281 byte[] zlibData = new byte[zlibDataLen];
1282 rfb.readFully(zlibData);
1283 if (rfb.rec != null && rfb.recordFromBeginning) {
1284 rfb.rec.write(zlibData);
1286 int stream_id = comp_ctl & 0x03;
1287 if (tightInflaters[stream_id] == null) {
1288 tightInflaters[stream_id] = new Inflater();
1290 Inflater myInflater = tightInflaters[stream_id];
1291 myInflater.setInput(zlibData);
1292 byte[] buf = new byte[dataSize];
1293 myInflater.inflate(buf);
1294 if (rfb.rec != null && !rfb.recordFromBeginning) {
1295 rfb.recordCompressedData(buf);
1298 if (numColors != 0) {
1300 if (numColors == 2) {
1302 if (bytesPixel == 1) {
1303 decodeMonoData(x, y, w, h, buf, palette8);
1305 decodeMonoData(x, y, w, h, buf, palette24);
1308 // More than two colors (assuming bytesPixel == 4).
1310 for (int dy = y; dy < y + h; dy++) {
1311 for (int dx = x; dx < x + w; dx++) {
1312 pixels24[dy * rfb.framebufferWidth + dx] =
1313 palette24[buf[i++] & 0xFF];
1317 } else if (useGradient) {
1318 // Compressed "Gradient"-filtered data (assuming bytesPixel == 4).
1319 decodeGradientData(x, y, w, h, buf);
1321 // Compressed truecolor data.
1322 if (bytesPixel == 1) {
1323 int destOffset = y * rfb.framebufferWidth + x;
1324 for (int dy = 0; dy < h; dy++) {
1325 System.arraycopy(buf, dy * w, pixels8, destOffset, w);
1326 destOffset += rfb.framebufferWidth;
1331 for (int dy = 0; dy < h; dy++) {
1332 myInflater.inflate(buf);
1333 destOffset = (y + dy) * rfb.framebufferWidth + x;
1334 for (i = 0; i < w; i++) {
1335 pixels24[destOffset + i] =
1336 (buf[srcOffset] & 0xFF) << 16 |
1337 (buf[srcOffset + 1] & 0xFF) << 8 |
1338 (buf[srcOffset + 2] & 0xFF);
1346 handleUpdatedPixels(x, y, w, h);
1347 scheduleRepaint(x, y, w, h);
1351 // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
1354 void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
1357 int i = y * rfb.framebufferWidth + x;
1358 int rowBytes = (w + 7) / 8;
1361 for (dy = 0; dy < h; dy++) {
1362 for (dx = 0; dx < w / 8; dx++) {
1363 b = src[dy*rowBytes+dx];
1364 for (n = 7; n >= 0; n--)
1365 pixels8[i++] = palette[b >> n & 1];
1367 for (n = 7; n >= 8 - w % 8; n--) {
1368 pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
1370 i += (rfb.framebufferWidth - w);
1374 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
1377 int i = y * rfb.framebufferWidth + x;
1378 int rowBytes = (w + 7) / 8;
1381 for (dy = 0; dy < h; dy++) {
1382 for (dx = 0; dx < w / 8; dx++) {
1383 b = src[dy*rowBytes+dx];
1384 for (n = 7; n >= 0; n--)
1385 pixels24[i++] = palette[b >> n & 1];
1387 for (n = 7; n >= 8 - w % 8; n--) {
1388 pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
1390 i += (rfb.framebufferWidth - w);
1395 // Decode data processed with the "Gradient" filter.
1398 void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
1401 byte[] prevRow = new byte[w * 3];
1402 byte[] thisRow = new byte[w * 3];
1403 byte[] pix = new byte[3];
1404 int[] est = new int[3];
1406 int offset = y * rfb.framebufferWidth + x;
1408 for (dy = 0; dy < h; dy++) {
1410 /* First pixel in a row */
1411 for (c = 0; c < 3; c++) {
1412 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
1413 thisRow[c] = pix[c];
1415 pixels24[offset++] =
1416 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
1418 /* Remaining pixels of a row */
1419 for (dx = 1; dx < w; dx++) {
1420 for (c = 0; c < 3; c++) {
1421 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
1422 (prevRow[(dx-1) * 3 + c] & 0xFF));
1423 if (est[c] > 0xFF) {
1425 } else if (est[c] < 0x00) {
1428 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
1429 thisRow[dx * 3 + c] = pix[c];
1431 pixels24[offset++] =
1432 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
1435 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
1436 offset += (rfb.framebufferWidth - w);
1441 // Display newly updated area of pixels.
1444 void handleUpdatedPixels(int x, int y, int w, int h) {
1446 // Draw updated pixels of the off-screen image.
1447 pixelsSource.newPixels(x, y, w, h);
1448 memGraphics.setClip(x, y, w, h);
1449 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
1450 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
1454 // Tell JVM to repaint specified desktop area.
1457 void scheduleRepaint(int x, int y, int w, int h) {
1458 // Request repaint, deferred if necessary.
1459 if (rfb.framebufferWidth == scaledWidth) {
1460 repaint(viewer.deferScreenUpdates, x, y, w, h);
1462 int sx = x * scalingFactor / 100;
1463 int sy = y * scalingFactor / 100;
1464 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
1465 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
1466 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
1474 public void keyPressed(KeyEvent evt) {
1475 processLocalKeyEvent(evt);
1477 public void keyReleased(KeyEvent evt) {
1478 processLocalKeyEvent(evt);
1480 public void keyTyped(KeyEvent evt) {
1484 public void mousePressed(MouseEvent evt) {
1485 processLocalMouseEvent(evt, false);
1487 public void mouseReleased(MouseEvent evt) {
1488 processLocalMouseEvent(evt, false);
1490 public void mouseMoved(MouseEvent evt) {
1491 processLocalMouseEvent(evt, true);
1493 public void mouseDragged(MouseEvent evt) {
1494 processLocalMouseEvent(evt, true);
1497 public void processLocalKeyEvent(KeyEvent evt) {
1498 if (viewer.rfb != null && rfb.inNormalProtocol) {
1499 if (!inputEnabled) {
1500 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
1501 evt.getID() == KeyEvent.KEY_PRESSED ) {
1502 // Request screen update.
1504 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
1505 rfb.framebufferHeight, false);
1506 } catch (IOException e) {
1507 e.printStackTrace();
1514 if (extraModifiers != 0) {
1515 evt.setModifiers(evt.getModifiers() | extraModifiers);
1517 rfb.writeKeyEvent(evt);
1518 } catch (Exception e) {
1519 e.printStackTrace();
1525 // Don't ever pass keyboard events to AWT for default processing.
1526 // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
1530 public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
1531 if (viewer.rfb != null && rfb.inNormalProtocol) {
1533 softCursorMove(evt.getX(), evt.getY());
1535 if (rfb.framebufferWidth != scaledWidth) {
1536 int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
1537 int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
1538 evt.translatePoint(sx - evt.getX(), sy - evt.getY());
1542 rfb.writePointerEvent(evt);
1543 } catch (Exception e) {
1544 e.printStackTrace();
1555 public void mouseClicked(MouseEvent evt) {}
1556 public void mouseEntered(MouseEvent evt) {}
1557 public void mouseExited(MouseEvent evt) {}
1560 //////////////////////////////////////////////////////////////////
1562 // Handle cursor shape updates (XCursor and RichCursor encodings).
1565 boolean showSoftCursor = false;
1567 MemoryImageSource softCursorSource;
1570 int cursorX = 0, cursorY = 0;
1571 int cursorWidth, cursorHeight;
1572 int origCursorWidth, origCursorHeight;
1574 int origHotX, origHotY;
1577 // Handle cursor shape update (XCursor and RichCursor encodings).
1581 handleCursorShapeUpdate(int encodingType,
1582 int xhot, int yhot, int width, int height)
1583 throws IOException {
1587 if (width * height == 0)
1590 // Ignore cursor shape data if requested by user.
1591 if (viewer.options.ignoreCursorUpdates) {
1592 int bytesPerRow = (width + 7) / 8;
1593 int bytesMaskData = bytesPerRow * height;
1595 if (encodingType == rfb.EncodingXCursor) {
1596 rfb.is.skipBytes(6 + bytesMaskData * 2);
1598 // rfb.EncodingRichCursor
1599 rfb.is.skipBytes(width * height + bytesMaskData);
1604 // Decode cursor pixel data.
1605 softCursorSource = decodeCursorShape(encodingType, width, height);
1607 // Set original (non-scaled) cursor dimensions.
1608 origCursorWidth = width;
1609 origCursorHeight = height;
1613 // Create off-screen cursor image.
1617 showSoftCursor = true;
1618 repaint(viewer.deferCursorUpdates,
1619 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1623 // decodeCursorShape(). Decode cursor pixel data and return
1624 // corresponding MemoryImageSource instance.
1627 synchronized MemoryImageSource
1628 decodeCursorShape(int encodingType, int width, int height)
1629 throws IOException {
1631 int bytesPerRow = (width + 7) / 8;
1632 int bytesMaskData = bytesPerRow * height;
1634 int[] softCursorPixels = new int[width * height];
1636 if (encodingType == rfb.EncodingXCursor) {
1638 // Read foreground and background colors of the cursor.
1639 byte[] rgb = new byte[6];
1641 int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
1642 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
1643 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
1644 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
1646 // Read pixel and mask data.
1647 byte[] pixBuf = new byte[bytesMaskData];
1648 rfb.readFully(pixBuf);
1649 byte[] maskBuf = new byte[bytesMaskData];
1650 rfb.readFully(maskBuf);
1652 // Decode pixel data into softCursorPixels[].
1653 byte pixByte, maskByte;
1654 int x, y, n, result;
1656 for (y = 0; y < height; y++) {
1657 for (x = 0; x < width / 8; x++) {
1658 pixByte = pixBuf[y * bytesPerRow + x];
1659 maskByte = maskBuf[y * bytesPerRow + x];
1660 for (n = 7; n >= 0; n--) {
1661 if ((maskByte >> n & 1) != 0) {
1662 result = colors[pixByte >> n & 1];
1664 result = 0; // Transparent pixel
1666 softCursorPixels[i++] = result;
1669 for (n = 7; n >= 8 - width % 8; n--) {
1670 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1671 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
1673 result = 0; // Transparent pixel
1675 softCursorPixels[i++] = result;
1680 // encodingType == rfb.EncodingRichCursor
1682 // Read pixel and mask data.
1683 byte[] pixBuf = new byte[width * height * bytesPixel];
1684 rfb.readFully(pixBuf);
1685 byte[] maskBuf = new byte[bytesMaskData];
1686 rfb.readFully(maskBuf);
1688 // Decode pixel data into softCursorPixels[].
1689 byte pixByte, maskByte;
1690 int x, y, n, result;
1692 for (y = 0; y < height; y++) {
1693 for (x = 0; x < width / 8; x++) {
1694 maskByte = maskBuf[y * bytesPerRow + x];
1695 for (n = 7; n >= 0; n--) {
1696 if ((maskByte >> n & 1) != 0) {
1697 if (bytesPixel == 1) {
1698 result = cm8.getRGB(pixBuf[i]);
1700 result = 0xFF000000 |
1701 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1702 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1703 (pixBuf[i * 4] & 0xFF);
1706 result = 0; // Transparent pixel
1708 softCursorPixels[i++] = result;
1711 for (n = 7; n >= 8 - width % 8; n--) {
1712 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1713 if (bytesPixel == 1) {
1714 result = cm8.getRGB(pixBuf[i]);
1716 result = 0xFF000000 |
1717 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1718 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1719 (pixBuf[i * 4] & 0xFF);
1722 result = 0; // Transparent pixel
1724 softCursorPixels[i++] = result;
1730 return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1734 // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1735 // Uses softCursorSource as a source for new cursor image.
1739 createSoftCursor() {
1741 if (softCursorSource == null)
1744 int scaleCursor = viewer.options.scaleCursor;
1745 if (scaleCursor == 0 || !inputEnabled)
1748 // Save original cursor coordinates.
1749 int x = cursorX - hotX;
1750 int y = cursorY - hotY;
1751 int w = cursorWidth;
1752 int h = cursorHeight;
1754 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1755 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1756 hotX = (origHotX * scaleCursor + 50) / 100;
1757 hotY = (origHotY * scaleCursor + 50) / 100;
1758 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
1760 if (scaleCursor != 100) {
1761 softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1762 Image.SCALE_SMOOTH);
1765 if (showSoftCursor) {
1766 // Compute screen area to update.
1767 x = Math.min(x, cursorX - hotX);
1768 y = Math.min(y, cursorY - hotY);
1769 w = Math.max(w, cursorWidth);
1770 h = Math.max(h, cursorHeight);
1772 repaint(viewer.deferCursorUpdates, x, y, w, h);
1777 // softCursorMove(). Moves soft cursor into a particular location.
1780 synchronized void softCursorMove(int x, int y) {
1785 if (showSoftCursor) {
1786 repaint(viewer.deferCursorUpdates,
1787 oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
1788 repaint(viewer.deferCursorUpdates,
1789 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1794 // softCursorFree(). Remove soft cursor, dispose resources.
1797 synchronized void softCursorFree() {
1798 if (showSoftCursor) {
1799 showSoftCursor = false;
1801 softCursorSource = null;
1803 repaint(viewer.deferCursorUpdates,
1804 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);