| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| package com.cloud.consoleproxy; |
| |
| import java.io.IOException; |
| import java.net.URI; |
| import java.net.UnknownHostException; |
| |
| import org.apache.log4j.Logger; |
| |
| import com.cloud.consoleproxy.vnc.FrameBufferCanvas; |
| import com.cloud.consoleproxy.vnc.RfbConstants; |
| import com.cloud.consoleproxy.vnc.VncClient; |
| |
| /** |
| * |
| * ConsoleProxyVncClient bridges a VNC engine with the front-end AJAX viewer |
| * |
| */ |
| public class ConsoleProxyVncClient extends ConsoleProxyClientBase { |
| private static final Logger s_logger = Logger.getLogger(ConsoleProxyVncClient.class); |
| |
| private static final int SHIFT_KEY_MASK = 64; |
| private static final int CTRL_KEY_MASK = 128; |
| private static final int META_KEY_MASK = 256; |
| private static final int ALT_KEY_MASK = 512; |
| |
| private static final int X11_KEY_SHIFT = 0xffe1; |
| private static final int X11_KEY_CTRL = 0xffe3; |
| private static final int X11_KEY_ALT = 0xffe9; |
| private static final int X11_KEY_META = 0xffe7; |
| |
| private VncClient client; |
| private Thread worker; |
| private volatile boolean workerDone = false; |
| |
| private int lastModifierStates = 0; |
| private int lastPointerMask = 0; |
| |
| public ConsoleProxyVncClient() { |
| } |
| |
| @Override |
| public boolean isHostConnected() { |
| if (client != null) |
| return client.isHostConnected(); |
| |
| return false; |
| } |
| |
| @Override |
| public boolean isFrontEndAlive() { |
| if (workerDone || System.currentTimeMillis() - getClientLastFrontEndActivityTime() > ConsoleProxy.VIEWER_LINGER_SECONDS * 1000) { |
| s_logger.info("Front end has been idle for too long"); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void initClient(ConsoleProxyClientParam param) { |
| setClientParam(param); |
| |
| client = new VncClient(this); |
| worker = new Thread(new Runnable() { |
| @Override |
| public void run() { |
| String tunnelUrl = getClientParam().getClientTunnelUrl(); |
| String tunnelSession = getClientParam().getClientTunnelSession(); |
| |
| try { |
| if (tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null && !tunnelSession.isEmpty()) { |
| URI uri = new URI(tunnelUrl); |
| s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: " + tunnelSession); |
| |
| ConsoleProxy.ensureRoute(uri.getHost()); |
| client.connectTo( |
| uri.getHost(), uri.getPort(), |
| uri.getPath() + "?" + uri.getQuery(), |
| tunnelSession, "https".equalsIgnoreCase(uri.getScheme()), |
| getClientHostPassword()); |
| } else { |
| s_logger.info("Connect to VNC server directly. host: " + getClientHostAddress() + ", port: " + getClientHostPort()); |
| ConsoleProxy.ensureRoute(getClientHostAddress()); |
| client.connectTo(getClientHostAddress(), getClientHostPort(), getClientHostPassword()); |
| } |
| } catch (UnknownHostException e) { |
| s_logger.error("Unexpected exception", e); |
| } catch (IOException e) { |
| s_logger.error("Unexpected exception", e); |
| } catch (Throwable e) { |
| s_logger.error("Unexpected exception", e); |
| } |
| |
| s_logger.info("Receiver thread stopped."); |
| workerDone = true; |
| client.getClientListener().onClientClose(); |
| } |
| }); |
| |
| worker.setDaemon(true); |
| worker.start(); |
| } |
| |
| @Override |
| public void closeClient() { |
| workerDone = true; |
| if (client != null) |
| client.shutdown(); |
| } |
| |
| @Override |
| public void onClientConnected() { |
| } |
| |
| @Override |
| public void onClientClose() { |
| s_logger.info("Received client close indication. remove viewer from map."); |
| |
| ConsoleProxy.removeViewer(this); |
| } |
| |
| @Override |
| public void onFramebufferUpdate(int x, int y, int w, int h) { |
| super.onFramebufferUpdate(x, y, w, h); |
| client.requestUpdate(false); |
| } |
| |
| @Override |
| public void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers) { |
| if (client == null) |
| return; |
| |
| updateFrontEndActivityTime(); |
| |
| switch(event) { |
| case KEY_DOWN : |
| sendModifierEvents(modifiers); |
| client.sendClientKeyboardEvent(RfbConstants.KEY_DOWN, code, 0); |
| break; |
| |
| case KEY_UP : |
| client.sendClientKeyboardEvent(RfbConstants.KEY_UP, code, 0); |
| sendModifierEvents(0); |
| break; |
| |
| case KEY_PRESS : |
| break; |
| |
| default : |
| assert(false); |
| break; |
| } |
| } |
| |
| @Override |
| public void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers) { |
| if (client == null) |
| return; |
| |
| updateFrontEndActivityTime(); |
| |
| if (event == InputEventType.MOUSE_DOWN) { |
| if (code == 2) { |
| lastPointerMask |= 4; |
| } else if (code == 0) { |
| lastPointerMask |= 1; |
| } |
| } |
| |
| if (event == InputEventType.MOUSE_UP) { |
| if (code == 2) { |
| lastPointerMask ^= 4; |
| } else if (code == 0) { |
| lastPointerMask ^= 1; |
| } |
| } |
| |
| if (event == InputEventType.MOUSE_SCROLL) { |
| if (code == -1) { |
| lastPointerMask = 8; |
| } else if (code == 1) { |
| lastPointerMask = 16; |
| } else if (code == 0) { |
| lastPointerMask = 0; |
| } |
| } |
| |
| sendModifierEvents(modifiers); |
| client.sendClientMouseEvent(lastPointerMask, x, y, code, modifiers); |
| if (lastPointerMask == 0) |
| sendModifierEvents(0); |
| } |
| |
| @Override |
| protected FrameBufferCanvas getFrameBufferCavas() { |
| if (client != null) |
| return client.getFrameBufferCanvas(); |
| return null; |
| } |
| |
| private void sendModifierEvents(int modifiers) { |
| if ((modifiers & SHIFT_KEY_MASK) != (lastModifierStates & SHIFT_KEY_MASK)) |
| client.sendClientKeyboardEvent((modifiers & SHIFT_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_SHIFT, 0); |
| |
| if((modifiers & CTRL_KEY_MASK) != (lastModifierStates & CTRL_KEY_MASK)) |
| client.sendClientKeyboardEvent((modifiers & CTRL_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_CTRL, 0); |
| |
| if ((modifiers & META_KEY_MASK) != (lastModifierStates & META_KEY_MASK)) |
| client.sendClientKeyboardEvent((modifiers & META_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_META, 0); |
| |
| if((modifiers & ALT_KEY_MASK) != (lastModifierStates & ALT_KEY_MASK)) |
| client.sendClientKeyboardEvent((modifiers & ALT_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_ALT, 0); |
| |
| lastModifierStates = modifiers; |
| } |
| } |