From f7cb2bf231d8003fba5537563c1e32a89cb4cbfc Mon Sep 17 00:00:00 2001 From: Constantin Kaplinsky Date: Tue, 27 May 2008 08:38:28 +0000 Subject: [PATCH] Support for video area selection (revision range 2467:2563 from branches/javaviewer-selectvideo) merged back to trunk. git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@2564 3789f03b-4d11-0410-bbf8-ca57d06f2519 --- .../com/tightvnc/vncviewer/ButtonPanel.java | 33 ++- java/src/com/tightvnc/vncviewer/RfbProto.java | 47 +++- .../src/com/tightvnc/vncviewer/VncCanvas.java | 242 +++++++++++++++--- .../src/com/tightvnc/vncviewer/VncViewer.java | 10 +- 4 files changed, 294 insertions(+), 38 deletions(-) diff --git a/java/src/com/tightvnc/vncviewer/ButtonPanel.java b/java/src/com/tightvnc/vncviewer/ButtonPanel.java index bd4cbca4..422e6e2c 100644 --- a/java/src/com/tightvnc/vncviewer/ButtonPanel.java +++ b/java/src/com/tightvnc/vncviewer/ButtonPanel.java @@ -38,6 +38,10 @@ class ButtonPanel extends Panel implements ActionListener { Button clipboardButton; Button ctrlAltDelButton; Button refreshButton; + Button selectButton; + + final String selectEnterLabel = "Select Video Area"; + final String selectLeaveLabel = "Hide Selection"; ButtonPanel(VncViewer v) { viewer = v; @@ -69,6 +73,16 @@ class ButtonPanel extends Panel implements ActionListener { refreshButton.addActionListener(this); } + /** + * Add video selection button to the ButtonPanel. + */ + public void addSelectButton() { + selectButton = new Button(selectEnterLabel); + selectButton.setEnabled(false); + add(selectButton); + selectButton.addActionListener(this); + } + // // Enable buttons on successful connection. // @@ -77,6 +91,9 @@ class ButtonPanel extends Panel implements ActionListener { disconnectButton.setEnabled(true); clipboardButton.setEnabled(true); refreshButton.setEnabled(true); + if (selectButton != null) { + selectButton.setEnabled(true); + } } // @@ -94,8 +111,9 @@ class ButtonPanel extends Panel implements ActionListener { clipboardButton.setEnabled(false); ctrlAltDelButton.setEnabled(false); refreshButton.setEnabled(false); - - validate(); + if (selectButton != null) { + selectButton.setEnabled(false); + } } // @@ -150,6 +168,17 @@ class ButtonPanel extends Panel implements ActionListener { } catch (IOException e) { e.printStackTrace(); } + } else if (selectButton != null && evt.getSource() == selectButton) { + if (viewer.vc != null) { + boolean isSelecting = viewer.vc.isInSelectionMode(); + if (!isSelecting) { + selectButton.setLabel(selectLeaveLabel); + viewer.vc.enableSelection(true); + } else { + selectButton.setLabel(selectEnterLabel); + viewer.vc.enableSelection(false); + } + } } } } diff --git a/java/src/com/tightvnc/vncviewer/RfbProto.java b/java/src/com/tightvnc/vncviewer/RfbProto.java index 89a4caeb..39656fc9 100644 --- a/java/src/com/tightvnc/vncviewer/RfbProto.java +++ b/java/src/com/tightvnc/vncviewer/RfbProto.java @@ -98,10 +98,10 @@ class RfbProto { ClientCutText = 6; // Non-standard client-to-server messages - final static int - EnableContinuousUpdates = 150; - final static String - SigEnableContinuousUpdates = "CUC_ENCU"; + final static int EnableContinuousUpdates = 150; + final static int VideoRectangleSelection = 151; + final static String SigEnableContinuousUpdates = "CUC_ENCU"; + final static String SigVideoRectangleSelection = "VRECTSEL"; // Supported encodings and pseudo-encodings final static int @@ -499,6 +499,9 @@ class RfbProto { clientMsgCaps.add(EnableContinuousUpdates, TightVncVendor, SigEnableContinuousUpdates, "Enable/disable continuous updates"); + clientMsgCaps.add(VideoRectangleSelection, TightVncVendor, + SigVideoRectangleSelection, + "Select a rectangle to be treated as video"); // Supported encoding types encodingCaps.add(EncodingCopyRect, StandardVendor, @@ -1357,6 +1360,42 @@ class RfbProto { return continuousUpdatesActive; } + /** + * Send a rectangle selection to be treated as video by the server (but + * only if VideoRectangleSelection message is supported by the server). + * @param rect specifies coordinates and size of the rectangule. + * @throws java.io.IOException + */ + void trySendVideoSelection(Rectangle rect) throws IOException + { + if (!clientMsgCaps.isEnabled(VideoRectangleSelection)) { + System.out.println("Video area selection is not supported by the server"); + return; + } + + int x = rect.x; + int y = rect.y; + int w = rect.width; + int h = rect.height; + + byte[] b = new byte[10]; + + b[0] = (byte) VideoRectangleSelection; + b[1] = (byte) 0; // reserved + b[2] = (byte) ((x >> 8) & 0xff); + b[3] = (byte) (x & 0xff); + b[4] = (byte) ((y >> 8) & 0xff); + b[5] = (byte) (y & 0xff); + b[6] = (byte) ((w >> 8) & 0xff); + b[7] = (byte) (w & 0xff); + b[8] = (byte) ((h >> 8) & 0xff); + b[9] = (byte) (h & 0xff); + + os.write(b); + + System.out.println("Video rectangle selection message sent"); + } + // // Compress and write the data into the recorded session file. This diff --git a/java/src/com/tightvnc/vncviewer/VncCanvas.java b/java/src/com/tightvnc/vncviewer/VncCanvas.java index 4f8122ea..332efa38 100644 --- a/java/src/com/tightvnc/vncviewer/VncCanvas.java +++ b/java/src/com/tightvnc/vncviewer/VncCanvas.java @@ -121,13 +121,16 @@ class VncCanvas extends Canvas setPixelFormat(); + resetSelection(); + inputEnabled = false; if (!viewer.options.viewOnly) enableInput(true); - // Keyboard listener is enabled even in view-only mode, to catch - // 'r' or 'R' key presses used to request screen update. + // Enable mouse and keyboard event listeners. addKeyListener(this); + addMouseListener(this); + addMouseMotionListener(this); } public VncCanvas(VncViewer v) throws IOException { @@ -173,6 +176,17 @@ class VncCanvas extends Canvas g.drawImage(softCursor, x0, y0, null); } } + if (isInSelectionMode()) { + Rectangle r = getSelection(true); + if (r.width > 0 && r.height > 0) { + // Don't forget to correct the coordinates for the right and bottom + // borders, so that the borders are the part of the selection. + r.width -= 1; + r.height -= 1; + g.setXORMode(Color.yellow); + g.drawRect(r.x, r.y, r.width, r.height); + } + } } public void paintScaledFrameBuffer(Graphics g) { @@ -213,16 +227,12 @@ class VncCanvas extends Canvas public synchronized void enableInput(boolean enable) { if (enable && !inputEnabled) { inputEnabled = true; - addMouseListener(this); - addMouseMotionListener(this); if (viewer.showControls) { viewer.buttonPanel.enableRemoteAccessControls(true); } createSoftCursor(); // scaled cursor } else if (!enable && inputEnabled) { inputEnabled = false; - removeMouseListener(this); - removeMouseMotionListener(this); if (viewer.showControls) { viewer.buttonPanel.enableRemoteAccessControls(false); } @@ -1589,7 +1599,19 @@ class VncCanvas extends Canvas processLocalMouseEvent(evt, true); } - public void processLocalKeyEvent(KeyEvent evt) { + // + // Ignored events. + // + + public void mouseClicked(MouseEvent evt) {} + public void mouseEntered(MouseEvent evt) {} + public void mouseExited(MouseEvent evt) {} + + // + // Actual event processing. + // + + private void processLocalKeyEvent(KeyEvent evt) { if (viewer.rfb != null && rfb.inNormalProtocol) { if (!inputEnabled) { if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') && @@ -1619,34 +1641,36 @@ class VncCanvas extends Canvas evt.consume(); } - public void processLocalMouseEvent(MouseEvent evt, boolean moved) { + private void processLocalMouseEvent(MouseEvent evt, boolean moved) { if (viewer.rfb != null && rfb.inNormalProtocol) { - if (moved) { - softCursorMove(evt.getX(), evt.getY()); - } - if (rfb.framebufferWidth != scaledWidth) { - int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor; - int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor; - evt.translatePoint(sx - evt.getX(), sy - evt.getY()); - } - synchronized(rfb) { - try { - rfb.writePointerEvent(evt); - } catch (Exception e) { - e.printStackTrace(); - } - rfb.notify(); + if (!inSelectionMode) { + if (inputEnabled) { + sendMouseEvent(evt, moved); + } + } else { + handleSelectionMouseEvent(evt); } } } - // - // Ignored events. - // - - public void mouseClicked(MouseEvent evt) {} - public void mouseEntered(MouseEvent evt) {} - public void mouseExited(MouseEvent evt) {} + private void sendMouseEvent(MouseEvent evt, boolean moved) { + if (moved) { + softCursorMove(evt.getX(), evt.getY()); + } + if (rfb.framebufferWidth != scaledWidth) { + int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor; + int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor; + evt.translatePoint(sx - evt.getX(), sy - evt.getY()); + } + synchronized(rfb) { + try { + rfb.writePointerEvent(evt); + } catch (Exception e) { + e.printStackTrace(); + } + rfb.notify(); + } + } // // Reset update statistics. @@ -1914,4 +1938,162 @@ class VncCanvas extends Canvas cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight); } } + + ////////////////////////////////////////////////////////////////// + // + // Support for selecting a rectangular video area. + // + + /** This flag is false in normal operation, and true in the selection mode. */ + private boolean inSelectionMode; + + /** The point where the selection was started. */ + private Point selectionStart; + + /** The second point of the selection. */ + private Point selectionEnd; + + /** + * We change cursor when enabling the selection mode. In this variable, we + * save the original cursor so we can restore it on returning to the normal + * mode. + */ + private Cursor savedCursor; + + /** + * Initialize selection-related varibles. + */ + private synchronized void resetSelection() { + inSelectionMode = false; + selectionStart = new Point(0, 0); + selectionEnd = new Point(0, 0); + + savedCursor = getCursor(); + } + + /** + * Check current state of the selection mode. + * @return true in the selection mode, false otherwise. + */ + public boolean isInSelectionMode() { + return inSelectionMode; + } + + /** + * Get current selection. + * @param useScreenCoords use screen coordinates if true, or framebuffer + * coordinates if false. This makes difference when scaling factor is not 100. + * @return The selection as a {@link Rectangle}. + */ + private synchronized Rectangle getSelection(boolean useScreenCoords) { + int x = selectionStart.x; + int y = selectionStart.y; + int w = selectionEnd.x - selectionStart.x; + int h = selectionEnd.y - selectionStart.y; + // Make x and y point to the upper left corner of the selection. + boolean horizSwap = false; + boolean vertSwap = false; + if (w < 0) { + w = -w; + x = x - w; + horizSwap = true; + } + if (h < 0) { + h = -h; + y = y - h; + vertSwap = true; + } + // Make sure the borders are included in the selection. + if (w > 0 && h > 0) { + w += 1; + h += 1; + } + // Translate from screen coordinates to framebuffer coordinates. + if (rfb.framebufferWidth != scaledWidth) { + x = (x * 100 + scalingFactor/2) / scalingFactor; + y = (y * 100 + scalingFactor/2) / scalingFactor; + w = (w * 100 + scalingFactor/2) / scalingFactor; + h = (h * 100 + scalingFactor/2) / scalingFactor; + } + // Make width a multiple of 16. + int widthCorrection = w % 16; + if (widthCorrection >= 8) { + widthCorrection -= 16; + } + w -= widthCorrection; + if (horizSwap) { + x += widthCorrection; + } + // Make height a multiple of 8. + int heightCorrection = h % 8; + if (heightCorrection >= 4) { + heightCorrection -= 8; + } + h -= heightCorrection; + if (vertSwap) { + y += heightCorrection; + } + // Translate the selection back to screen coordinates if requested. + int clipWidth = rfb.framebufferWidth; + int clipHeight = rfb.framebufferHeight; + if (useScreenCoords && rfb.framebufferWidth != scaledWidth) { + x = (x * scalingFactor + 50) / 100; + y = (y * scalingFactor + 50) / 100; + w = (w * scalingFactor + 50) / 100; + h = (h * scalingFactor + 50) / 100; + clipWidth = scaledWidth; + clipHeight = scaledHeight; + } + // Clip the selection to screen/framebuffer and return the result. + Rectangle selection = new Rectangle(x, y, w, h); + Rectangle clip = new Rectangle(0, 0, clipWidth, clipHeight); + return selection.intersection(clip); + } + + /** + * Enable or disable the selection mode. + * @param enable enables the selection mode if true, disables if fasle. + */ + public synchronized void enableSelection(boolean enable) { + if (enable && !inSelectionMode) { + // Enter the selection mode. + inSelectionMode = true; + savedCursor = getCursor(); + setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + repaint(); + } else if (!enable && inSelectionMode) { + // Leave the selection mode. + inSelectionMode = false; + setCursor(savedCursor); + repaint(); + } + } + + /** + * Process mouse events in the selection mode. + * + * @param evt mouse event that was originally passed to + * {@link MouseListener} or {@link MouseMotionListener}. + */ + private synchronized void handleSelectionMouseEvent(MouseEvent evt) { + int id = evt.getID(); + boolean button1 = (evt.getModifiers() & InputEvent.BUTTON1_MASK) != 0; + + if (id == MouseEvent.MOUSE_PRESSED && button1) { + selectionStart = selectionEnd = evt.getPoint(); + repaint(); + } + if (id == MouseEvent.MOUSE_DRAGGED && button1) { + selectionEnd = evt.getPoint(); + repaint(); + } + if (id == MouseEvent.MOUSE_RELEASED && button1) { + try { + rfb.trySendVideoSelection(getSelection(false)); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } diff --git a/java/src/com/tightvnc/vncviewer/VncViewer.java b/java/src/com/tightvnc/vncviewer/VncViewer.java index 6fa77f85..c6fa1579 100644 --- a/java/src/com/tightvnc/vncviewer/VncViewer.java +++ b/java/src/com/tightvnc/vncviewer/VncViewer.java @@ -160,6 +160,11 @@ public class VncViewer extends java.applet.Applet connectAndAuthenticate(); doProtocolInitialisation(); + if (showControls && + rfb.clientMsgCaps.isEnabled(RfbProto.VideoRectangleSelection)) { + buttonPanel.addSelectButton(); + } + // FIXME: Use auto-scaling not only in a separate frame. if (options.autoScale && inSeparateFrame) { Dimension screenSize; @@ -206,8 +211,9 @@ public class VncViewer extends java.applet.Applet } - if (showControls) - buttonPanel.enableButtons(); + if (showControls) { + buttonPanel.enableButtons(); + } moveFocusToDesktop(); processNormalProtocol(); -- 2.39.5