Files
2026-07-28 03:21:34 +08:00

847 lines
29 KiB
Java

/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright (C) 2006 Constantin Kaplinsky. All Rights Reserved.
* Copyright (C) 2009 Paul Donohue. All Rights Reserved.
* Copyright (C) 2010, 2012-2013 D. R. Commander. All Rights Reserved.
* Copyright (C) 2011-2019 Brian P. Hinz
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* 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.geom.AffineTransform;
import java.awt.image.*;
import java.nio.*;
import java.util.*;
import javax.swing.*;
import javax.imageio.*;
import java.io.*;
import com.tigervnc.rfb.*;
import com.tigervnc.rfb.Cursor;
import com.tigervnc.rfb.Exception;
import com.tigervnc.rfb.Point;
import static java.awt.event.KeyEvent.*;
import static com.tigervnc.vncviewer.Parameters.*;
import static com.tigervnc.rfb.Keysymdef.*;
class Viewport extends JPanel implements ActionListener {
static LogWriter vlog = new LogWriter("Viewport");
enum ID { EXIT, FULLSCREEN, MINIMIZE, RESIZE, NEWVIEWER,
CTRL, ALT, MENUKEY, CTRLALTDEL, CLIPBOARD,
REFRESH, OPTIONS, INFO, ABOUT, DISMISS }
enum MENU { INACTIVE, TOGGLE, VALUE, RADIO,
INVISIBLE, SUBMENU_POINTER, SUBMENU, DIVIDER }
public Viewport(int w, int h, PixelFormat serverPF, CConn cc_)
{
cc = cc_;
setScaledSize(w, h);
frameBuffer = createFramebuffer(serverPF, w, h);
assert(frameBuffer != null);
setBackground(Color.BLACK);
cc.setFramebuffer(frameBuffer);
contextMenu = new JPopupMenu();
OptionsDialog.addCallback("handleOptions", this);
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { handle(e); }
public void mouseExited(MouseEvent e) { handle(e); }
public void mouseReleased(MouseEvent e) { handle(e); }
public void mousePressed(MouseEvent e) { handle(e); }
});
addMouseWheelListener(new MouseAdapter() {
public void mouseWheelMoved(MouseWheelEvent e) { handle(e); }
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) { handle(e); }
public void mouseMoved(MouseEvent e) { handle(e); }
});
addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent e) { }
public void keyPressed(KeyEvent e) { handleSystemEvent(e); }
public void keyReleased(KeyEvent e) { handleSystemEvent(e); }
});
addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
ClipboardDialog.clientCutText();
}
public void focusLost(FocusEvent e) {
releaseDownKeys();
}
});
// Override default key bindings from L&F
getActionMap().put("null", new AbstractAction() {
public void actionPerformed(ActionEvent e) { }
});
ArrayList<KeyStroke> keys = new ArrayList<KeyStroke>();
keys.add(KeyStroke.getKeyStroke(KeyEvent.VK_F10, 0, true));
keys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ALT, 0, true));
for (int i=0; i<keys.size(); i++)
getInputMap(JComponent.WHEN_FOCUSED).put(keys.get(i), "null");
setFocusTraversalKeysEnabled(false);
setFocusable(true);
setMenuKey();
// Send a fake pointer event so that the server will stop rendering
// a server-side cursor. Ideally we'd like to send the actual pointer
// position, but we can't really tell when the window manager is done
// placing us so we don't have a good time for that.
handlePointerEvent(new Point(w/2, h/2), 0);
}
// Most efficient format (from Viewport's point of view)
public PixelFormat getPreferredPF()
{
return frameBuffer.getPF();
}
// Copy the areas of the framebuffer that have been changed (damaged)
// to the displayed window.
public void updateWindow() {
Rect r = frameBuffer.getDamage();
if (!r.is_empty()) {
if (cc.server.width() != scaledWidth ||
cc.server.height() != scaledHeight) {
AffineTransform t = new AffineTransform();
t.scale((double)scaleRatioX, (double)scaleRatioY);
Rectangle s = new Rectangle(r.tl.x, r.tl.y, r.width(), r.height());
s = t.createTransformedShape(s).getBounds();
paintImmediately(s.x, s.y, s.width, s.height);
} else {
paintImmediately(r.tl.x, r.tl.y, r.width(), r.height());
}
}
}
static final int[] dotcursor_xpm = {
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
};
public void setCursor(int width, int height, Point hotspot,
byte[] data)
{
int i;
if (cursor != null)
cursor.flush();
for (i = 0; i < width*height; i++)
if (data[i*4 + 3] != 0) break;
useSystemCursor = false;
if ((i == width*height) && alwaysCursor.getValue()) {
String cursorTypeStr = cursorType.getValueStr();
if (cursorTypeStr.matches("^System$")) {
useSystemCursor = true;
} else {
vlog.debug("Cursor is empty: Using dot");
}
// Do this anyway to prevent cursor being null
cursor = new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB_PRE);
cursor.setRGB(0, 0, 5, 5, dotcursor_xpm, 0, 5);
cursorHotspot.x = cursorHotspot.y = 3;
} else {
if ((width == 0) || (height == 0)) {
cursor = new BufferedImage(tk.getBestCursorSize(0, 0).width,
tk.getBestCursorSize(0, 0).height,
BufferedImage.TYPE_INT_ARGB_PRE);
cursorHotspot.x = cursorHotspot.y = 0;
} else {
IntBuffer buffer = IntBuffer.allocate(width*height);
buffer.put(ByteBuffer.wrap(data).asIntBuffer());
cursor =
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
cursor.setRGB(0, 0, width, height, buffer.array(), 0, width);
cursorHotspot = hotspot;
}
}
int cw = (int) Math.floor((float) cursor.getWidth() * scaleRatioX);
int ch = (int) Math.floor((float) cursor.getHeight() * scaleRatioY);
int x = cursorHotspot.x;
int y = cursorHotspot.y;
Dimension cs = tk.getBestCursorSize(cw, ch);
if (cs.width != cursor.getWidth() || cs.height != cursor.getHeight()) {
cw = VncViewer.os.startsWith("windows") ? Math.min(cw, cs.width) : cs.width;
ch = VncViewer.os.startsWith("windows") ? Math.min(ch, cs.height) : cs.height;
BufferedImage tmp = new BufferedImage(cs.width, cs.height, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D g2 = tmp.createGraphics();
g2.drawImage(cursor, 0, 0, cw, ch, 0, 0, width, height, null);
g2.dispose();
x = (int) Math.min(Math.floor((float) x * (float) cw / (float) width), Math.max(cw - 1, 0));
y = (int) Math.min(Math.floor((float) y * (float) ch / (float) height), Math.max(ch - 1, 0));
cursor = tmp;
}
setCursor(cursor, x, y);
}
private void setCursor(Image img, int x, int y)
{
java.awt.Point hotspot;
java.awt.Cursor softCursor;
String name = "rfb cursor";
hotspot = new java.awt.Point(x, y);
softCursor = tk.createCustomCursor(img, hotspot, name);
if (useSystemCursor)
setCursor(java.awt.Cursor.getDefaultCursor());
else
setCursor(softCursor);
}
public void resize(int x, int y, int w, int h) {
if ((w != frameBuffer.width()) || (h != frameBuffer.height())) {
vlog.debug("Resizing framebuffer from "+frameBuffer.width()+"x"+
frameBuffer.height()+" to "+w+"x"+h);
frameBuffer = createFramebuffer(frameBuffer.getPF(), w, h);
assert(frameBuffer != null);
cc.setFramebuffer(frameBuffer);
}
setScaledSize(w, h);
}
public int handle(MouseEvent e)
{
int buttonMask, wheelMask;
switch (e.getID()) {
case MouseEvent.MOUSE_ENTERED:
if (cursor != null)
setCursor(cursor, cursorHotspot.x, cursorHotspot.y);
return 1;
case MouseEvent.MOUSE_EXITED:
setCursor(java.awt.Cursor.getDefaultCursor());
return 1;
case MouseEvent.MOUSE_PRESSED:
case MouseEvent.MOUSE_RELEASED:
case MouseEvent.MOUSE_DRAGGED:
case MouseEvent.MOUSE_MOVED:
case MouseEvent.MOUSE_WHEEL:
buttonMask = 0;
if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0)
buttonMask |= 1;
if ((e.getModifiersEx() & MouseEvent.BUTTON2_DOWN_MASK) != 0)
buttonMask |= 2;
if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0)
buttonMask |= 4;
if (e.getID() == MouseEvent.MOUSE_WHEEL) {
wheelMask = 0;
int clicks = ((MouseWheelEvent)e).getWheelRotation();
if (clicks < 0)
wheelMask |= e.isShiftDown() ? 32 : 8;
else
wheelMask |= e.isShiftDown() ? 64 : 16;
Point pt = new Point(e.getX(), e.getY());
for (int i = 0; i < Math.abs(clicks); i++) {
handlePointerEvent(pt, buttonMask|wheelMask);
handlePointerEvent(pt, buttonMask);
}
return 1;
}
handlePointerEvent(new Point(e.getX(), e.getY()), buttonMask);
return 1;
}
return -1;
}
private PlatformPixelBuffer createFramebuffer(PixelFormat pf, int w, int h)
{
PlatformPixelBuffer fb;
fb = new JavaPixelBuffer(w, h);
return fb;
}
//
// 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 paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
synchronized(frameBuffer.getImage()) {
if (cc.server.width() != scaledWidth ||
cc.server.height() != scaledHeight) {
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2.drawImage(frameBuffer.getImage(), 0, 0,
scaledWidth, scaledHeight, null);
} else {
g2.drawImage(frameBuffer.getImage(), 0, 0, null);
}
}
g2.dispose();
}
public void setScaledSize(int width, int height)
{
assert(width != 0 && height != 0);
String scaleString = scalingFactor.getValue();
if (remoteResize.getValue()) {
scaledWidth = width;
scaledHeight = height;
scaleRatioX = 1.00f;
scaleRatioY = 1.00f;
} else {
if (scaleString.matches("^[0-9]+$")) {
int scalingFactor = Integer.parseInt(scaleString);
scaledWidth =
(int)Math.floor((float)width * (float)scalingFactor/100.0);
scaledHeight =
(int)Math.floor((float)height * (float)scalingFactor/100.0);
} else if (scaleString.equalsIgnoreCase("Auto")) {
scaledWidth = width;
scaledHeight = height;
} else {
float widthRatio = (float)width / (float)cc.server.width();
float heightRatio = (float)height / (float)cc.server.height();
float ratio = Math.min(widthRatio, heightRatio);
scaledWidth = (int)Math.floor(cc.server.width() * ratio);
scaledHeight = (int)Math.floor(cc.server.height() * ratio);
}
scaleRatioX = (float)scaledWidth / (float)cc.server.width();
scaleRatioY = (float)scaledHeight / (float)cc.server.height();
}
if (scaledWidth != getWidth() || scaledHeight != getHeight())
setSize(new Dimension(scaledWidth, scaledHeight));
}
private void handlePointerEvent(Point pos, int buttonMask)
{
if (!viewOnly.getValue()) {
if (buttonMask != lastButtonMask || !pos.equals(lastPointerPos)) {
try {
if (cc.server.width() != scaledWidth ||
cc.server.height() != scaledHeight) {
int sx = (scaleRatioX == 1.00) ?
pos.x : (int)Math.floor(pos.x / scaleRatioX);
int sy = (scaleRatioY == 1.00) ?
pos.y : (int)Math.floor(pos.y / scaleRatioY);
pos = pos.translate(new Point(sx - pos.x, sy - pos.y));
}
cc.writer().writePointerEvent(pos, buttonMask);
} catch (Exception e) {
vlog.error("%s", e.getMessage());
cc.close();
}
}
lastPointerPos = pos;
lastButtonMask = buttonMask;
}
}
public void handleKeyPress(long keyCode, int keySym)
{
// Prevent recursion if the menu wants to send it's own
// activation key.
if ((menuKeySym != 0) && keySym == menuKeySym && !menuRecursion) {
popupContextMenu();
return;
}
if (viewOnly.getValue())
return;
if (keyCode == 0) {
vlog.error("No key code specified on key press");
return;
}
if (VncViewer.os.startsWith("mac os x")) {
// Alt on OS X behaves more like AltGr on other systems, and to get
// sane behaviour we should translate things in that manner for the
// remote VNC server. However that means we lose the ability to use
// Alt as a shortcut modifier. Do what RealVNC does and hijack the
// left command key as an Alt replacement.
switch (keySym) {
case XK_Meta_L:
keySym = XK_Alt_L;
break;
case XK_Meta_R:
keySym = XK_Super_L;
break;
case XK_Alt_L:
keySym = XK_Mode_switch;
break;
case XK_Alt_R:
keySym = XK_ISO_Level3_Shift;
break;
}
}
if (VncViewer.os.startsWith("windows")) {
// Ugly hack alert!
//
// Windows doesn't have a proper AltGr, but handles it using fake
// Ctrl+Alt. Unfortunately X11 doesn't generally like the combination
// Ctrl+Alt+AltGr, which we usually end up with when Xvnc tries to
// get everything in the correct state. Cheat and temporarily release
// Ctrl and Alt when we send some other symbol.
if (downKeySym.containsValue(XK_Control_L) &&
downKeySym.containsValue(XK_Alt_R)) {
vlog.debug("Faking release of AltGr (Ctrl_L+Alt_R)");
try {
cc.writer().writeKeyEvent(XK_Control_L, false);
cc.writer().writeKeyEvent(XK_Alt_R, false);
} catch (Exception e) {
vlog.error("%s", e.getMessage());
cc.close();
}
}
}
// Because of the way keyboards work, we cannot expect to have the same
// symbol on release as when pressed. This breaks the VNC protocol however,
// so we need to keep track of what keysym a key _code_ generated on press
// and send the same on release.
downKeySym.put(keyCode, keySym);
vlog.debug("Key pressed: 0x%016x => 0x%04x", keyCode, keySym);
try {
// Fake keycode?
if (keyCode > 0xffffffffL)
cc.writer().writeKeyEvent(keySym, true);
else
cc.writer().writeKeyEvent(keySym, true);
} catch (Exception e) {
vlog.error("%s", e.getMessage());
cc.close();
}
if (VncViewer.os.startsWith("windows")) {
// Ugly hack continued...
if (downKeySym.containsValue(XK_Control_L) &&
downKeySym.containsValue(XK_Alt_R)) {
vlog.debug("Restoring AltGr state");
try {
cc.writer().writeKeyEvent(XK_Control_L, true);
cc.writer().writeKeyEvent(XK_Alt_R, true);
} catch (Exception e) {
vlog.error("%s", e.getMessage());
cc.close();
}
}
}
}
public void handleKeyRelease(long keyCode)
{
Integer iter;
if (viewOnly.getValue())
return;
iter = downKeySym.get(keyCode);
if (iter == null) {
// These occur somewhat frequently so let's not spam them unless
// logging is turned up.
vlog.debug("Unexpected release of key code %d", keyCode);
return;
}
vlog.debug("Key released: 0x%016x => 0x%04x", keyCode, iter);
try {
if (keyCode > 0xffffffffL)
cc.writer().writeKeyEvent(iter, false);
else
cc.writer().writeKeyEvent(iter, false);
} catch (Exception e) {
vlog.error("%s", e.getMessage());
cc.close();
}
downKeySym.remove(keyCode);
}
private int handleSystemEvent(AWTEvent event)
{
if (event instanceof KeyEvent) {
KeyEvent ev = (KeyEvent)event;
if (KeyMap.get_keycode_fallback_extended(ev) == 0) {
// Not much we can do with this...
vlog.debug("Ignoring KeyEvent with unknown Java keycode");
return 0;
}
if (ev.getID() == KeyEvent.KEY_PRESSED) {
// Generate a fake keycode just for tracking if we can't figure
// out the proper one. Java virtual key codes aren't unique
// between left/right versions of keys, so we can't use them as
// indexes to the downKeySym map.
long keyCode = KeyMap.get_keycode_fallback_extended(ev) | ((long)ev.getKeyLocation()<<32);
// Pressing Ctrl wreaks havoc with the symbol lookup, so turn
// that off. But AltGr shows up as Ctrl_L+Alt_R in Windows, so
// construct a new KeyEvent that uses a proper AltGraph for the
// symbol lookup.
int keySym;
if (VncViewer.os.startsWith("windows") &&
downKeySym.containsValue(XK_Control_L) &&
downKeySym.containsValue(XK_Alt_R)) {
int mask = ev.getModifiers();
mask &= ~CTRL_MASK;
mask &= ~ALT_MASK;
mask |= ALT_GRAPH_MASK;
AWTKeyStroke ks =
AWTKeyStroke.getAWTKeyStroke(KeyMap.get_keycode_fallback_extended(ev), mask);
// The mask manipulations above break key combinations involving AltGr
// and a key with an accented letter on some keyboard layouts (i.e. IT).
// So the code should first try the modified event, but if it returns no
// symbol, the original event should be used.
final KeyEvent winev = new KeyEvent((JComponent)ev.getSource(), ev.getID(),
ev.getWhen(), mask, KeyMap.get_keycode_fallback_extended(ev),
ks.getKeyChar(), ev.getKeyLocation());
keySym = KeyMap.vkey_to_keysym(winev);
if (keySym == KeyMap.NoSymbol)
keySym = KeyMap.vkey_to_keysym(ev);
else
ev = winev;
} else {
keySym = KeyMap.vkey_to_keysym(ev);
}
if (keySym == KeyMap.NoSymbol)
vlog.error("No symbol for virtual key 0x%016x", keyCode);
if (VncViewer.os.startsWith("linux")) {
switch (keySym) {
// For the first few years, there wasn't a good consensus on what the
// Windows keys should be mapped to for X11. So we need to help out a
// bit and map all variants to the same key...
case XK_Hyper_L:
keySym = XK_Super_L;
break;
case XK_Hyper_R:
keySym = XK_Super_R;
break;
// There has been several variants for Shift-Tab over the years.
// RFB states that we should always send a normal tab.
case XK_ISO_Left_Tab:
keySym = XK_Tab;
break;
}
}
handleKeyPress(keyCode, keySym);
if (VncViewer.os.startsWith("mac os x")) {
// We don't get any release events for CapsLock, so we have to
// send the release right away.
if (keySym == XK_Caps_Lock)
handleKeyRelease(keyCode);
}
return 1;
} else if (ev.getID() == KeyEvent.KEY_RELEASED) {
long keyCode = KeyMap.get_keycode_fallback_extended(ev) | ((long)ev.getKeyLocation()<<32);
handleKeyRelease(keyCode);
return 1;
}
}
return 0;
}
private void initContextMenu()
{
contextMenu.setLightWeightPopupEnabled(false);
contextMenu.removeAll();
menu_add(contextMenu, "Exit viewer", KeyEvent.VK_X,
this, ID.EXIT, EnumSet.of(MENU.DIVIDER));
menu_add(contextMenu, "Full screen", KeyEvent.VK_F, this, ID.FULLSCREEN,
window().fullscreen_active() ?
EnumSet.of(MENU.TOGGLE, MENU.VALUE) : EnumSet.of(MENU.TOGGLE));
menu_add(contextMenu, "Minimize", KeyEvent.VK_Z,
this, ID.MINIMIZE, EnumSet.noneOf(MENU.class));
menu_add(contextMenu, "Resize window to session", KeyEvent.VK_W,
this, ID.RESIZE,
window().fullscreen_active() ?
EnumSet.of(MENU.INACTIVE, MENU.DIVIDER) : EnumSet.of(MENU.DIVIDER));
menu_add(contextMenu, "Clipboard viewer...", KeyEvent.VK_UNDEFINED,
this, ID.CLIPBOARD, EnumSet.of(MENU.DIVIDER));
menu_add(contextMenu, "Ctrl", KeyEvent.VK_C,
this, ID.CTRL,
menuCtrlKey ? EnumSet.of(MENU.TOGGLE, MENU.VALUE) : EnumSet.of(MENU.TOGGLE));
menu_add(contextMenu, "Alt", KeyEvent.VK_A,
this, ID.ALT,
menuAltKey ? EnumSet.of(MENU.TOGGLE, MENU.VALUE) : EnumSet.of(MENU.TOGGLE));
if (menuKeySym != 0) {
String sendMenuKey = String.format("Send %s", menuKey.getValueStr());
menu_add(contextMenu, sendMenuKey, menuKeyJava,
this, ID.MENUKEY, EnumSet.noneOf(MENU.class));
}
menu_add(contextMenu, "Send Ctrl-Alt-Del", KeyEvent.VK_D,
this, ID.CTRLALTDEL, EnumSet.of(MENU.DIVIDER));
menu_add(contextMenu, "Refresh screen", KeyEvent.VK_R,
this, ID.REFRESH, EnumSet.of(MENU.DIVIDER));
menu_add(contextMenu, "New connection...", KeyEvent.VK_N,
this, ID.NEWVIEWER, EnumSet.of(MENU.DIVIDER));
menu_add(contextMenu, "Options...", KeyEvent.VK_O,
this, ID.OPTIONS, EnumSet.noneOf(MENU.class));
menu_add(contextMenu, "Connection info...", KeyEvent.VK_I,
this, ID.INFO, EnumSet.noneOf(MENU.class));
menu_add(contextMenu, "About T2SRFBVNC...", KeyEvent.VK_T,
this, ID.ABOUT, EnumSet.of(MENU.DIVIDER));
menu_add(contextMenu, "Dismiss menu", KeyEvent.VK_M,
this, ID.DISMISS, EnumSet.noneOf(MENU.class));
}
static void menu_add(JPopupMenu menu, String text,
int shortcut, ActionListener cb,
ID data, EnumSet<MENU> flags)
{
JMenuItem item;
if (flags.contains(MENU.TOGGLE)) {
item = new JCheckBoxMenuItem(text, flags.contains(MENU.VALUE));
} else {
if (shortcut != 0)
item = new JMenuItem(text, shortcut);
else
item = new JMenuItem(text);
}
item.setActionCommand(data.toString());
item.addActionListener(cb);
item.setEnabled(!flags.contains(MENU.INACTIVE));
menu.add(item);
if (flags.contains(MENU.DIVIDER))
menu.addSeparator();
}
void popupContextMenu()
{
// initialize context menu before display
initContextMenu();
contextMenu.setCursor(java.awt.Cursor.getDefaultCursor());
contextMenu.show(this, lastPointerPos.x, lastPointerPos.y);
}
public void actionPerformed(ActionEvent ev)
{
switch(ID.valueOf(ev.getActionCommand())) {
case EXIT:
cc.close();
break;
case FULLSCREEN:
if (window().fullscreen_active())
window().fullscreen_off();
else
window().fullscreen_on();
break;
case MINIMIZE:
if (window().fullscreen_active())
window().fullscreen_off();
window().setExtendedState(JFrame.ICONIFIED);
break;
case RESIZE:
if (window().fullscreen_active())
break;
int dx = window().getInsets().left + window().getInsets().right;
int dy = window().getInsets().top + window().getInsets().bottom;
window().setSize(getWidth()+dx, getHeight()+dy);
break;
case CLIPBOARD:
ClipboardDialog.showDialog(window());
break;
case CTRL:
if (((JMenuItem)ev.getSource()).isSelected())
handleKeyPress(0x1d, XK_Control_L);
else
handleKeyRelease(0x1d);
menuCtrlKey = !menuCtrlKey;
break;
case ALT:
if (((JMenuItem)ev.getSource()).isSelected())
handleKeyPress(0x38, XK_Alt_L);
else
handleKeyRelease(0x38);
menuAltKey = !menuAltKey;
break;
case MENUKEY:
menuRecursion = true;
handleKeyPress(menuKeyCode, menuKeySym);
menuRecursion = false;
handleKeyRelease(menuKeyCode);
break;
case CTRLALTDEL:
handleKeyPress(0x1d, XK_Control_L);
handleKeyPress(0x38, XK_Alt_L);
handleKeyPress(0xd3, XK_Delete);
handleKeyRelease(0xd3);
handleKeyRelease(0x38);
handleKeyRelease(0x1d);
break;
case REFRESH:
cc.refreshFramebuffer();
break;
case NEWVIEWER:
VncViewer.newViewer();
break;
case OPTIONS:
OptionsDialog.showDialog(cc.desktop);
break;
case INFO:
Window fullScreenWindow =
DesktopWindow.getFullScreenWindow();
if (fullScreenWindow != null)
DesktopWindow.setFullScreenWindow(null);
JOptionPane op = new JOptionPane(cc.connectionInfo(),
JOptionPane.PLAIN_MESSAGE,
JOptionPane.DEFAULT_OPTION);
JDialog dlg = op.createDialog(window(), "VNC connection info");
dlg.setIconImage(VncViewer.frameIcon);
dlg.setAlwaysOnTop(true);
dlg.setVisible(true);
if (fullScreenWindow != null)
DesktopWindow.setFullScreenWindow(fullScreenWindow);
break;
case ABOUT:
VncViewer.about_vncviewer(cc.desktop);
break;
case DISMISS:
break;
}
}
private void setMenuKey()
{
menuKeyJava = MenuKey.getMenuKeyJavaCode();
menuKeyCode = MenuKey.getMenuKeyCode();
menuKeySym = MenuKey.getMenuKeySym();
}
public void handleOptions()
{
setMenuKey();
/*
setScaledSize(cc.server.width(), cc.server.height());
if (!oldSize.equals(new Dimension(scaledWidth, scaledHeight))) {
// Re-layout the DesktopWindow when the scaled size changes.
// Ideally we'd do this with a ComponentListener, but unfortunately
// sometimes a spurious resize event is triggered on the viewport
// when the DesktopWindow is manually resized via the drag handles.
if (cc.desktop != null && cc.desktop.isVisible()) {
JScrollPane scroll = (JScrollPane)((JViewport)getParent()).getParent();
scroll.setViewportBorder(BorderFactory.createEmptyBorder(0,0,0,0));
cc.desktop.pack();
}
*/
}
public void releaseDownKeys() {
while (!downKeySym.isEmpty())
handleKeyRelease(downKeySym.keySet().iterator().next());
}
private DesktopWindow window() {
return (DesktopWindow)getTopLevelAncestor();
}
private int x() { return getX(); }
private int y() { return getY(); }
private int w() { return getWidth(); }
private int h() { return getHeight(); }
// access to cc by different threads is specified in CConn
private CConn cc;
// access to the following must be synchronized:
private PlatformPixelBuffer frameBuffer;
Point lastPointerPos = new Point(0, 0);
int lastButtonMask = 0;
private class DownMap extends HashMap<Long, Integer> {
public DownMap(int capacity) {
super(capacity);
}
}
DownMap downKeySym = new DownMap(256);
int menuKeySym;
int menuKeyCode, menuKeyJava;
JPopupMenu contextMenu;
boolean menuRecursion = false;
boolean menuCtrlKey = false;
boolean menuAltKey = false;
static Toolkit tk = Toolkit.getDefaultToolkit();
public int scaledWidth = 0, scaledHeight = 0;
float scaleRatioX, scaleRatioY;
static BufferedImage cursor;
static boolean useSystemCursor = false;
Point cursorHotspot = new Point();
}