diff options
author | DRC <dcommander@users.sourceforge.net> | 2011-10-07 05:38:00 +0000 |
---|---|---|
committer | DRC <dcommander@users.sourceforge.net> | 2011-10-07 05:38:00 +0000 |
commit | c19ab9ec7f3ac4823802388ac953e9494c613575 (patch) | |
tree | 63513ffd7ce0b3ab3de2d9b619cc4e5b892eea31 /java/com/tigervnc/vncviewer/DesktopWindow.java | |
parent | bba54b0b14fded1d457f426cdc8843a34d6c9dc5 (diff) | |
download | tigervnc-c19ab9ec7f3ac4823802388ac953e9494c613575.tar.gz tigervnc-c19ab9ec7f3ac4823802388ac953e9494c613575.zip |
Move Java source up one level and allow Java viewer to be built as a standalone project (per community request)
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4715 3789f03b-4d11-0410-bbf8-ca57d06f2519
Diffstat (limited to 'java/com/tigervnc/vncviewer/DesktopWindow.java')
-rw-r--r-- | java/com/tigervnc/vncviewer/DesktopWindow.java | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/java/com/tigervnc/vncviewer/DesktopWindow.java b/java/com/tigervnc/vncviewer/DesktopWindow.java new file mode 100644 index 00000000..f5acfff1 --- /dev/null +++ b/java/com/tigervnc/vncviewer/DesktopWindow.java @@ -0,0 +1,553 @@ +/* Copyright (C) 2010 D. R. Commander. All Rights Reserved. + * Copyright (C) 2009 Paul Donohue. All Rights Reserved. + * Copyright (C) 2006 Constantin Kaplinsky. All Rights Reserved. + * Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// +// DesktopWindow is an AWT Canvas representing a VNC desktop. +// +// Methods on DesktopWindow are called from both the GUI thread and the thread +// which processes incoming RFB messages ("the RFB thread"). This means we +// need to be careful with synchronization here. +// + +package com.tigervnc.vncviewer; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.Clipboard; +import javax.swing.*; + +import com.tigervnc.rfb.*; +import com.tigervnc.rfb.Cursor; +import com.tigervnc.rfb.Exception; +import com.tigervnc.rfb.Point; + +class DesktopWindow extends JPanel implements + Runnable, + MouseListener, + MouseMotionListener, + MouseWheelListener, + KeyListener +{ + + //////////////////////////////////////////////////////////////////// + // The following methods are all called from the RFB thread + + public DesktopWindow(int width, int height, PixelFormat serverPF, CConn cc_) { + cc = cc_; + setSize(width, height); + im = new PixelBufferImage(width, height, cc, this); + + cursor = new Cursor(); + cursorBacking = new ManagedPixelBuffer(); + addMouseListener(this); + addMouseWheelListener(this); + addMouseMotionListener(this); + addKeyListener(this); + addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent e) { + checkClipboard(); + } + }); + setFocusTraversalKeysEnabled(false); + setFocusable(true); + setDoubleBuffered(true); + } + + public int width() { + return getWidth(); + } + + public int height() { + return getHeight(); + } + + // initGraphics() is needed because for some reason you can't call + // getGraphics() on a newly-created awt Component. It is called when the + // DesktopWindow has actually been made visible so that getGraphics() ought + // to work. + + public void initGraphics() { + cc.viewport.g = cc.viewport.getGraphics(); + graphics = getComponentGraphics(cc.viewport.g); + prepareImage(im.image, scaledWidth, scaledHeight, this); + } + + final public PixelFormat getPF() { return im.getPF(); } + + synchronized public void setPF(PixelFormat pf) { + im.setPF(pf); + } + + public void setViewport(ViewportFrame viewport) + { + viewport.setChild(this); + } + + // Methods called from the RFB thread - these need to be synchronized + // wherever they access data shared with the GUI thread. + + public void setCursor(int w, int h, Point hotspot, + int[] data, byte[] mask) { + // strictly we should use a mutex around this test since useLocalCursor + // might be being altered by the GUI thread. However it's only a single + // boolean and it doesn't matter if we get the wrong value anyway. + + synchronized(this) { + if (!cc.viewer.useLocalCursor.getValue()) return; + + hideLocalCursor(); + + cursor.hotspot = hotspot; + + Dimension bsc = tk.getBestCursorSize(w, h); + + cursor.setSize(((int)bsc.getWidth() > w ? (int)bsc.getWidth() : w), + ((int)bsc.getHeight() > h ? (int)bsc.getHeight() : h)); + cursor.setPF(getPF()); + + cursorBacking.setSize(cursor.width(), cursor.height()); + cursorBacking.setPF(getPF()); + + cursor.data = new int[cursor.width() * cursor.height()]; + cursor.mask = new byte[cursor.maskLen()]; + + // set the masked pixels of the cursor transparent by using an extra bit in + // the colormap. We'll OR this into the data based on the values in the mask. + if (cursor.getPF().bpp == 8) { + cursor.cm = new DirectColorModel(9, 7, (7 << 3), (3 << 6), (1 << 8)); + } + + int maskBytesPerRow = (w + 7) / 8; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int byte_ = y * maskBytesPerRow + x / 8; + int bit = 7 - x % 8; + if ((mask[byte_] & (1 << bit)) > 0) { + cursor.data[y * cursor.width() + x] = (cursor.getPF().bpp == 8) ? + data[y * w + x] | (1 << 8) : data[y * w + x]; + } + } + System.arraycopy(mask, y * maskBytesPerRow, cursor.mask, + y * ((cursor.width() + 7) / 8), maskBytesPerRow); + } + + MemoryImageSource bitmap = + new MemoryImageSource(cursor.width(), cursor.height(), cursor.cm, + cursor.data, 0, cursor.width()); + int cw = (int)Math.floor((float)cursor.width() * scaleWidthRatio); + int ch = (int)Math.floor((float)cursor.height() * scaleHeightRatio); + int hint = java.awt.Image.SCALE_DEFAULT; + hotspot = new Point((int)Math.floor((float)hotspot.x * scaleWidthRatio), + (int)Math.floor((float)hotspot.y * scaleHeightRatio)); + Image cursorImage = (cw <= 0 || ch <= 0) ? tk.createImage(bitmap) : + tk.createImage(bitmap).getScaledInstance(cw,ch,hint); + softCursor = tk.createCustomCursor(cursorImage, + new java.awt.Point(hotspot.x,hotspot.y), "Cursor"); + } + + if (softCursor != null) { + setCursor(softCursor); + cursorAvailable = true; + return; + } + + if (!cursorAvailable) { + cursorAvailable = true; + } + + showLocalCursor(); + return; + } + + // setColourMapEntries() changes some of the entries in the colourmap. + // Unfortunately these messages are often sent one at a time, so we delay the + // settings taking effect unless the whole colourmap has changed. This is + // because getting java to recalculate its internal translation table and + // redraw the screen is expensive. + + synchronized public void setColourMapEntries(int firstColour, int nColours, + int[] rgbs) { + im.setColourMapEntries(firstColour, nColours, rgbs); + if (nColours <= 256) { + im.updateColourMap(); + im.put(0, 0, im.width(), im.height(), graphics); + } else { + if (setColourMapEntriesTimerThread == null) { + setColourMapEntriesTimerThread = new Thread(this); + setColourMapEntriesTimerThread.start(); + } + } + } + +// Update the actual window with the changed parts of the framebuffer. + + public void framebufferUpdateEnd() + { + drawInvalidRect(); + } + + // resize() is called when the desktop has changed size + synchronized public void resize() { + int w = cc.cp.width; + int h = cc.cp.height; + hideLocalCursor(); + setSize(w, h); + im.resize(w, h); + } + + final void drawInvalidRect() { + if (!invalidRect) return; + int x = invalidLeft; + int w = invalidRight - x; + int y = invalidTop; + int h = invalidBottom - y; + invalidRect = false; + + synchronized (this) { + im.put(x, y, w, h, graphics); + } + } + + final void invalidate(int x, int y, int w, int h) { + if (invalidRect) { + if (x < invalidLeft) invalidLeft = x; + if (x + w > invalidRight) invalidRight = x + w; + if (y < invalidTop) invalidTop = y; + if (y + h > invalidBottom) invalidBottom = y + h; + } else { + invalidLeft = x; + invalidRight = x + w; + invalidTop = y; + invalidBottom = y + h; + invalidRect = true; + } + + if ((invalidRight - invalidLeft) * (invalidBottom - invalidTop) > 100000) + drawInvalidRect(); + } + + public void beginRect(int x, int y, int w, int h, int encoding) { + invalidRect = false; + } + + public void endRect(int x, int y, int w, int h, int encoding) { + drawInvalidRect(); + } + + synchronized final public void fillRect(int x, int y, int w, int h, int pix) + { + if (overlapsCursor(x, y, w, h)) hideLocalCursor(); + im.fillRect(x, y, w, h, pix); + invalidate(x, y, w, h); + if (softCursor == null) + showLocalCursor(); + } + + synchronized final public void imageRect(int x, int y, int w, int h, + int[] pix) { + if (overlapsCursor(x, y, w, h)) hideLocalCursor(); + im.imageRect(x, y, w, h, pix); + invalidate(x, y, w, h); + if (softCursor == null) + showLocalCursor(); + } + + synchronized final public void copyRect(int x, int y, int w, int h, + int srcX, int srcY) { + if (overlapsCursor(x, y, w, h) || overlapsCursor(srcX, srcY, w, h)) + hideLocalCursor(); + im.copyRect(x, y, w, h, srcX, srcY); + if (!cc.viewer.fastCopyRect.getValue()) { + invalidate(x, y, w, h); + } + } + + + // mutex MUST be held when overlapsCursor() is called + final boolean overlapsCursor(int x, int y, int w, int h) { + return (x < cursorBackingX + cursorBacking.width() && + y < cursorBackingY + cursorBacking.height() && + x+w > cursorBackingX && y+h > cursorBackingY); + } + + + //////////////////////////////////////////////////////////////////// + // The following methods are all called from the GUI thread + + synchronized void resetLocalCursor() { + hideLocalCursor(); + cursorAvailable = false; + } + + // + // Callback methods to determine geometry of our Component. + // + + public Dimension getPreferredSize() { + return new Dimension(scaledWidth, scaledHeight); + } + + public Dimension getMinimumSize() { + return new Dimension(scaledWidth, scaledHeight); + } + + public Dimension getMaximumSize() { + return new Dimension(scaledWidth, scaledHeight); + } + + public void setScaledSize() { + if (!cc.options.autoScale && !cc.options.fixedRatioScale) { + scaledWidth = (int)Math.floor((float)cc.cp.width * (float)cc.scaleFactor/100.0); + scaledHeight = (int)Math.floor((float)cc.cp.height * (float)cc.scaleFactor/100.0); + } else { + if (cc.viewport == null) { + scaledWidth = cc.cp.width; + scaledHeight = cc.cp.height; + } else { + Dimension vpSize = cc.viewport.getSize(); + Insets vpInsets = cc.viewport.getInsets(); + Dimension availableSize = + new Dimension(vpSize.width - vpInsets.left - vpInsets.right, + vpSize.height - vpInsets.top - vpInsets.bottom); + if (availableSize.width == 0 || availableSize.height == 0) + availableSize = new Dimension(cc.cp.width, cc.cp.height); + if (cc.options.fixedRatioScale) { + float widthRatio = (float)availableSize.width / (float)cc.cp.width; + float heightRatio = (float)availableSize.height / (float)cc.cp.height; + float ratio = Math.min(widthRatio, heightRatio); + scaledWidth = (int)Math.floor(cc.cp.width * ratio); + scaledHeight = (int)Math.floor(cc.cp.height * ratio); + } else { + scaledWidth = availableSize.width; + scaledHeight = availableSize.height; + } + } + } + scaleWidthRatio = (float)scaledWidth / (float)cc.cp.width; + scaleHeightRatio = (float)scaledHeight / (float)cc.cp.height; + } + + synchronized public void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + if (cc.cp.width == scaledWidth && cc.cp.height == scaledHeight) { + g2.drawImage(im.image, 0, 0, null); + } else { + g2.drawImage(im.image, 0, 0, scaledWidth, scaledHeight, null); + } + } + + String oldContents = ""; + + synchronized public void checkClipboard() { + SecurityManager sm = System.getSecurityManager(); + try { + if (sm != null) sm.checkSystemClipboardAccess(); + Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); + if (cb != null && cc.viewer.sendClipboard.getValue()) { + Transferable t = cb.getContents(null); + if ((t != null) && t.isDataFlavorSupported(DataFlavor.stringFlavor)) { + try { + String newContents = (String)t.getTransferData(DataFlavor.stringFlavor); + if (newContents != null && !newContents.equals(oldContents)) { + cc.writeClientCutText(newContents, newContents.length()); + oldContents = newContents; + cc.clipboardDialog.setContents(newContents); + } + } catch (java.lang.Exception e) { + System.out.println("Exception getting clipboard data: " + e.getMessage()); + } + } + } + } catch(SecurityException e) { + System.err.println("Cannot access the system clipboard"); + } + } + + /** Mouse-Motion callback function */ + private void mouseMotionCB(MouseEvent e) { + if (!cc.viewer.viewOnly.getValue()) + cc.writePointerEvent(e); + // - If local cursor rendering is enabled then use it + synchronized(this) { + if (cursorAvailable) { + // - Render the cursor! + if (e.getX() != cursorPosX || e.getY() != cursorPosY) { + hideLocalCursor(); + if (e.getX() >= 0 && e.getX() < im.width() && + e.getY() >= 0 && e.getY() < im.height()) { + cursorPosX = e.getX(); + cursorPosY = e.getY(); + if (softCursor == null) + showLocalCursor(); + } + } + } + } + lastX = e.getX(); + lastY = e.getY(); + } + public void mouseDragged(MouseEvent e) { mouseMotionCB(e);} + public void mouseMoved(MouseEvent e) { mouseMotionCB(e);} + + /** Mouse callback function */ + private void mouseCB(MouseEvent e) { + if (!cc.viewer.viewOnly.getValue()) + cc.writePointerEvent(e); + lastX = e.getX(); + lastY = e.getY(); + } + public void mouseReleased(MouseEvent e){ mouseCB(e);} + public void mousePressed(MouseEvent e) { mouseCB(e);} + public void mouseClicked(MouseEvent e){} + public void mouseEntered(MouseEvent e){} + public void mouseExited(MouseEvent e){} + + /** MouseWheel callback function */ + private void mouseWheelCB(MouseWheelEvent e) { + if (!cc.viewer.viewOnly.getValue()) + cc.writeWheelEvent(e); + } + public void mouseWheelMoved(MouseWheelEvent e){ + mouseWheelCB(e); + } + + /** Handle the key-typed event. */ + public void keyTyped(KeyEvent e) {} + /** Handle the key-released event. */ + public void keyReleased(KeyEvent e) {} + /** Handle the key-pressed event. */ + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == + (KeyEvent.VK_F1+cc.menuKey-Keysyms.F1)) { + int sx = (scaleWidthRatio == 1.00) + ? lastX : (int)Math.floor(lastX*scaleWidthRatio); + int sy = (scaleHeightRatio == 1.00) + ? lastY : (int)Math.floor(lastY*scaleHeightRatio); + java.awt.Point ev = new java.awt.Point(lastX, lastY); + ev.translate(sx - lastX, sy - lastY); + cc.showMenu((int)ev.getX(), (int)ev.getY()); + return; + } + if (!cc.viewer.viewOnly.getValue()) + cc.writeKeyEvent(e); + } + + //////////////////////////////////////////////////////////////////// + // The following methods are called from both RFB and GUI threads + + // Note that mutex MUST be held when hideLocalCursor() and showLocalCursor() + // are called. + + private void hideLocalCursor() { + // - Blit the cursor backing store over the cursor + if (cursorVisible) { + cursorVisible = false; + im.imageRect(cursorBackingX, cursorBackingY, cursorBacking.width(), + cursorBacking.height(), cursorBacking.data); + im.put(cursorBackingX, cursorBackingY, cursorBacking.width(), + cursorBacking.height(), graphics); + } + } + + private void showLocalCursor() { + if (cursorAvailable && !cursorVisible) { + if (!im.getPF().equal(cursor.getPF()) || + cursor.width() == 0 || cursor.height() == 0) { + vlog.debug("attempting to render invalid local cursor"); + cursorAvailable = false; + return; + } + cursorVisible = true; + if (softCursor != null) return; + + int cursorLeft = cursor.hotspot.x; + int cursorTop = cursor.hotspot.y; + int cursorRight = cursorLeft + cursor.width(); + int cursorBottom = cursorTop + cursor.height(); + + int x = (cursorLeft >= 0 ? cursorLeft : 0); + int y = (cursorTop >= 0 ? cursorTop : 0); + int w = ((cursorRight < im.width() ? cursorRight : im.width()) - x); + int h = ((cursorBottom < im.height() ? cursorBottom : im.height()) - y); + + cursorBackingX = x; + cursorBackingY = y; + cursorBacking.setSize(w, h); + + for (int j = 0; j < h; j++) + System.arraycopy(im.data, (y+j) * im.width() + x, + cursorBacking.data, j*w, w); + + im.maskRect(cursorLeft, cursorTop, cursor.width(), cursor.height(), + cursor.data, cursor.mask); + im.put(x, y, w, h, graphics); + } + } + + + // run() is executed by the setColourMapEntriesTimerThread - it sleeps for + // 100ms before actually updating the colourmap. + public void run() { + try { + Thread.sleep(100); + } catch (InterruptedException e) {} + synchronized (this) { + im.updateColourMap(); + im.put(0, 0, im.width(), im.height(), graphics); + setColourMapEntriesTimerThread = null; + } + } + + // access to cc by different threads is specified in CConn + CConn cc; + + // access to the following must be synchronized: + PixelBufferImage im; + Graphics graphics; + Thread setColourMapEntriesTimerThread; + + Cursor cursor; + boolean cursorVisible; // Is cursor currently rendered? + boolean cursorAvailable; // Is cursor available for rendering? + int cursorPosX, cursorPosY; + ManagedPixelBuffer cursorBacking; + int cursorBackingX, cursorBackingY; + java.awt.Cursor softCursor; + static Toolkit tk = Toolkit.getDefaultToolkit(); + + public int scaledWidth = 0, scaledHeight = 0; + float scaleWidthRatio, scaleHeightRatio; + + // the following are only ever accessed by the RFB thread: + boolean invalidRect; + int invalidLeft, invalidRight, invalidTop, invalidBottom; + + // the following are only ever accessed by the GUI thread: + int lastX, lastY; + + static LogWriter vlog = new LogWriter("DesktopWindow"); +} |