// 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; } } 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; } }