From 5941ac46d2ffc3069752195303f2beab45c1bb61 Mon Sep 17 00:00:00 2001 From: Anshul Gangwar Date: Tue, 31 Dec 2013 00:56:27 +0530 Subject: [PATCH] CLOUDSTACK-5344 commit for console proxy rdp for hyperv --- .../HypervResourceController.cs | 39 +++ .../com/cloud/server/ManagementServer.java | 3 + .../cloud/server/ManagementServerImpl.java | 5 + .../servlet/ConsoleProxyClientParam.java | 47 ++- .../cloud/servlet/ConsoleProxyServlet.java | 49 ++- .../main/java/common/BufferedImageCanvas.java | 6 +- .../java/common/adapter/AwtCanvasAdapter.java | 4 +- .../src/main/java/rdpclient/RdpClient.java | 15 +- services/console-proxy/server/pom.xml | 5 + .../com/cloud/consoleproxy/ConsoleProxy.java | 20 +- .../consoleproxy/ConsoleProxyAjaxHandler.java | 6 + .../consoleproxy/ConsoleProxyClientParam.java | 32 +- .../ConsoleProxyHttpHandlerHelper.java | 34 +- .../consoleproxy/ConsoleProxyRdpClient.java | 318 ++++++++++++++++++ .../consoleproxy/rdp/KeysymToKeycode.java | 115 +++++++ .../rdp/RdpBufferedImageCanvas.java | 103 ++++++ 16 files changed, 765 insertions(+), 36 deletions(-) create mode 100644 services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyRdpClient.java create mode 100644 services/console-proxy/server/src/com/cloud/consoleproxy/rdp/KeysymToKeycode.java create mode 100644 services/console-proxy/server/src/com/cloud/consoleproxy/rdp/RdpBufferedImageCanvas.java diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs index a1c91a5b642..6daadeefaac 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs @@ -2000,6 +2000,45 @@ namespace HypervResource } } + // POST api/HypervResource/GetVncPortCommand + [HttpPost] + [ActionName(CloudStackTypes.GetVncPortCommand)] + public JContainer GetVncPortCommand([FromBody]dynamic cmd) + { + using (log4net.NDC.Push(Guid.NewGuid().ToString())) + { + logger.Info(CloudStackTypes.GetVncPortCommand + cmd.ToString()); + + string details = null; + bool result = false; + string address = null; + int port = -9; + + try + { + string vmName = (string)cmd.name; + var sys = wmiCallsV2.GetComputerSystem(vmName); + address = "instanceId=" + sys.Name ; + result = true; + } + catch (Exception sysEx) + { + details = CloudStackTypes.GetVncPortAnswer + " failed due to " + sysEx.Message; + logger.Error(details, sysEx); + } + + object ansContent = new + { + result = result, + details = details, + address = address, + port = port + }; + + return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetVncPortAnswer); + } + } + public static System.Net.NetworkInformation.NetworkInterface GetNicInfoFromIpAddress(string ipAddress, out string subnet) { System.Net.NetworkInformation.NetworkInterface[] nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); diff --git a/server/src/com/cloud/server/ManagementServer.java b/server/src/com/cloud/server/ManagementServer.java index 5a6ca78a9eb..3b5f5ff206a 100755 --- a/server/src/com/cloud/server/ManagementServer.java +++ b/server/src/com/cloud/server/ManagementServer.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.server; +import com.cloud.host.DetailVO; import com.cloud.host.HostVO; import com.cloud.storage.GuestOSVO; import com.cloud.utils.Pair; @@ -47,6 +48,8 @@ public interface ManagementServer extends ManagementService, PluggableService { */ HostVO getHostBy(long hostId); + DetailVO findDetail(long hostId, String name); + String getConsoleAccessUrlRoot(long vmId); GuestOSVO getGuestOs(Long guestOsId); diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index bbb31cfbb6c..4bffa3f60dd 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -854,6 +854,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe return _hostDao.findById(hostId); } + @Override + public DetailVO findDetail(long hostId, String name) { + return _detailsDao.findDetail(hostId, name); + } + @Override public long getId() { return MacAddress.getMacAddress().toLong(); diff --git a/server/src/com/cloud/servlet/ConsoleProxyClientParam.java b/server/src/com/cloud/servlet/ConsoleProxyClientParam.java index e9793373c16..ce06a9624a9 100644 --- a/server/src/com/cloud/servlet/ConsoleProxyClientParam.java +++ b/server/src/com/cloud/servlet/ConsoleProxyClientParam.java @@ -27,7 +27,11 @@ public class ConsoleProxyClientParam { private String clientTunnelUrl; private String clientTunnelSession; + private String hypervHost; + private String ajaxSessionId; + private String username; + private String password; public ConsoleProxyClientParam() { clientHostPort = 0; @@ -89,26 +93,51 @@ public class ConsoleProxyClientParam { this.clientTunnelSession = clientTunnelSession; } - public String getLocale() { - return this.locale; - } - - public void setLocale(String locale) { - this.locale = locale; - } - public String getAjaxSessionId() { - return this.ajaxSessionId; + return ajaxSessionId; } public void setAjaxSessionId(String ajaxSessionId) { this.ajaxSessionId = ajaxSessionId; } + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + public String getClientMapKey() { if (clientTag != null && !clientTag.isEmpty()) return clientTag; return clientHostAddress + ":" + clientHostPort; } + + public void setHypervHost(String host) { + hypervHost = host; + } + + public String getHypervHost() { + return hypervHost; + } + + public void setUsername(String username) { + this.username = username; + + } + + public String getUsername() { + return username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPassword() { + return password; + } } diff --git a/server/src/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/com/cloud/servlet/ConsoleProxyServlet.java index e0deaa21a49..5edd95d8dbd 100644 --- a/server/src/com/cloud/servlet/ConsoleProxyServlet.java +++ b/server/src/com/cloud/servlet/ConsoleProxyServlet.java @@ -328,15 +328,21 @@ public class ConsoleProxyServlet extends HttpServlet { s_logger.info("Parse host info returned from executing GetVNCPortCommand. host info: " + hostInfo); - if (hostInfo != null && hostInfo.startsWith("consoleurl")) { - String tokens[] = hostInfo.split("&"); + if (hostInfo != null) { + if (hostInfo.startsWith("consoleurl")) { + String tokens[] = hostInfo.split("&"); - if (hostInfo.length() > 19 && hostInfo.indexOf('/', 19) > 19) { - host = hostInfo.substring(19, hostInfo.indexOf('/', 19)).trim(); - tunnelUrl = tokens[0].substring("consoleurl=".length()); - tunnelSession = tokens[1].split("=")[1]; + if (hostInfo.length() > 19 && hostInfo.indexOf('/', 19) > 19) { + host = hostInfo.substring(19, hostInfo.indexOf('/', 19)).trim(); + tunnelUrl = tokens[0].substring("consoleurl=".length()); + tunnelSession = tokens[1].split("=")[1]; + } else { + host = ""; + } + } else if (hostInfo.startsWith("instanceId")) { + host = hostInfo.substring(hostInfo.indexOf('=') + 1); } else { - host = ""; + host = hostInfo; } } else { host = hostInfo; @@ -389,28 +395,49 @@ public class ConsoleProxyServlet extends HttpServlet { private String composeConsoleAccessUrl(String rootUrl, VirtualMachine vm, HostVO hostVo) { StringBuffer sb = new StringBuffer(rootUrl); String host = hostVo.getPrivateIpAddress(); + String username = _ms.findDetail(hostVo.getId(), "username").getValue(); + String password = _ms.findDetail(hostVo.getId(), "password").getValue(); Pair portInfo = _ms.getVncPort(vm); if (s_logger.isDebugEnabled()) s_logger.debug("Port info " + portInfo.first()); Ternary parsedHostInfo = parseHostInfo(portInfo.first()); + int port = -1; + String sid; + + if (portInfo.second() == -9) { + //for hyperv + port = 2179; + } else { + port = portInfo.second(); + } + sid = vm.getVncPassword(); UserVmDetailVO details = _userVmDetailsDao.findDetail(vm.getId(), "keyboard"); - String sid = vm.getVncPassword(); + String tag = vm.getUuid(); - String ticket = genAccessTicket(host, String.valueOf(portInfo.second()), sid, tag); + + String ticket = genAccessTicket(parsedHostInfo.first(), String.valueOf(port), sid, tag); ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(getEncryptorPassword()); ConsoleProxyClientParam param = new ConsoleProxyClientParam(); param.setClientHostAddress(parsedHostInfo.first()); - param.setClientHostPort(portInfo.second()); + param.setClientHostPort(port); param.setClientHostPassword(sid); param.setClientTag(tag); param.setTicket(ticket); + if (details != null) { param.setLocale(details.getValue()); } - if (parsedHostInfo.second() != null && parsedHostInfo.third() != null) { + + if (portInfo.second() == -9) { + //For Hyperv Clinet Host Address will send Instance id + param.setHypervHost(host); + param.setUsername(username); + param.setPassword(password); + } + if (parsedHostInfo.second() != null && parsedHostInfo.third() != null) { param.setClientTunnelUrl(parsedHostInfo.second()); param.setClientTunnelSession(parsedHostInfo.third()); } diff --git a/services/console-proxy-rdp/rdpconsole/src/main/java/common/BufferedImageCanvas.java b/services/console-proxy-rdp/rdpconsole/src/main/java/common/BufferedImageCanvas.java index 43abb273477..9cb523137ec 100755 --- a/services/console-proxy-rdp/rdpconsole/src/main/java/common/BufferedImageCanvas.java +++ b/services/console-proxy-rdp/rdpconsole/src/main/java/common/BufferedImageCanvas.java @@ -30,7 +30,7 @@ public class BufferedImageCanvas extends Canvas { private static final long serialVersionUID = 1L; // Offline screen buffer - private BufferedImage offlineImage; + protected BufferedImage offlineImage; // Cached Graphics2D object for offline screen buffer private Graphics2D graphics; @@ -76,4 +76,8 @@ public class BufferedImageCanvas extends Canvas { return graphics; } + public void updateFrameBuffer(int x, int y, int w, int h) { + //this method will be used to mark the dirty tiles + } + } diff --git a/services/console-proxy-rdp/rdpconsole/src/main/java/common/adapter/AwtCanvasAdapter.java b/services/console-proxy-rdp/rdpconsole/src/main/java/common/adapter/AwtCanvasAdapter.java index 55ca8fdbf64..f3a73d70dbd 100755 --- a/services/console-proxy-rdp/rdpconsole/src/main/java/common/adapter/AwtCanvasAdapter.java +++ b/services/console-proxy-rdp/rdpconsole/src/main/java/common/adapter/AwtCanvasAdapter.java @@ -77,13 +77,14 @@ public class AwtCanvasAdapter extends BaseElement { } private void handleCopyRect(CopyRectOrder order, ByteBuffer buf) { - // TODO Auto-generated method stub // Copy image canvas.getOfflineGraphics().copyArea(order.srcX, order.srcY, order.width, order.height, order.x - order.srcX, order.y - order.srcY); // Request update of repainted area + canvas.updateFrameBuffer(order.x, order.y, order.width, order.height); canvas.repaint(order.x, order.y, order.width, order.height); + } private void handleBitmap(BitmapOrder order, ByteBuffer buf) { @@ -137,6 +138,7 @@ public class AwtCanvasAdapter extends BaseElement { g.drawImage(rectImage, x, y, null); // Request update of repainted area + canvas.updateFrameBuffer(x, y, width, height); canvas.repaint(x, y, width, height); } diff --git a/services/console-proxy-rdp/rdpconsole/src/main/java/rdpclient/RdpClient.java b/services/console-proxy-rdp/rdpconsole/src/main/java/rdpclient/RdpClient.java index afde70698e3..a3db165c2fa 100755 --- a/services/console-proxy-rdp/rdpconsole/src/main/java/rdpclient/RdpClient.java +++ b/services/console-proxy-rdp/rdpconsole/src/main/java/rdpclient/RdpClient.java @@ -63,6 +63,9 @@ import common.adapter.AwtCanvasAdapter; public class RdpClient extends PipelineImpl { + AwtMouseEventSource mouseEventSource = null; + AwtKeyEventSource keyEventSource = null; + /** * Name of last OneTimePacket in handshake sequence. */ @@ -333,8 +336,8 @@ public class RdpClient extends PipelineImpl { // Main network // - AwtMouseEventSource mouseEventSource = new AwtMouseEventSource("mouse"); - AwtKeyEventSource keyEventSource = new AwtKeyEventSource("keyboard"); + mouseEventSource = new AwtMouseEventSource("mouse"); + keyEventSource = new AwtKeyEventSource("keyboard"); // Subscribe packet sender to various events canvas.addMouseListener(mouseEventSource); @@ -390,4 +393,12 @@ public class RdpClient extends PipelineImpl { link("client_x224_data_queue", "client_tpkt_queue", "client_tpkt_queue< queue"); } + + public AwtMouseEventSource getMouseEventSource() { + return mouseEventSource; + } + + public AwtKeyEventSource getKeyEventSource() { + return keyEventSource; + } } diff --git a/services/console-proxy/server/pom.xml b/services/console-proxy/server/pom.xml index bc780a70fb5..5d0b3bc9565 100644 --- a/services/console-proxy/server/pom.xml +++ b/services/console-proxy/server/pom.xml @@ -44,6 +44,11 @@ cloud-utils ${project.version} + + rdpclient + cloudstack-service-console-proxy-rdpclient + ${project.version} + diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java index f889cdbf05b..02fda6424be 100644 --- a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java @@ -196,8 +196,8 @@ public class ConsoleProxy { Object result; try { result = - authMethod.invoke(ConsoleProxy.context, param.getClientHostAddress(), String.valueOf(param.getClientHostPort()), param.getClientTag(), - param.getClientHostPassword(), param.getTicket(), new Boolean(reauthentication)); + authMethod.invoke(ConsoleProxy.context, param.getClientHostAddress(), String.valueOf(param.getClientHostPort()), param.getClientTag(), + param.getClientHostPassword(), param.getTicket(), new Boolean(reauthentication)); } catch (IllegalAccessException e) { s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + param.getClientTag(), e); authResult.setSuccess(false); @@ -407,7 +407,7 @@ public class ConsoleProxy { synchronized (connectionMap) { viewer = connectionMap.get(clientKey); if (viewer == null) { - viewer = new ConsoleProxyVncClient(); + viewer = getClient(param); viewer.initClient(param); connectionMap.put(clientKey, viewer); s_logger.info("Added viewer object " + viewer); @@ -418,7 +418,7 @@ public class ConsoleProxy { viewer.initClient(param); } else if (!param.getClientHostPassword().equals(viewer.getClientHostPassword())) { s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.getClientHostPassword() + ", sid in request: " + - param.getClientHostPassword()); + param.getClientHostPassword()); viewer.initClient(param); } } @@ -442,7 +442,7 @@ public class ConsoleProxy { ConsoleProxyClient viewer = connectionMap.get(clientKey); if (viewer == null) { authenticationExternally(param); - viewer = new ConsoleProxyVncClient(); + viewer = getClient(param); viewer.initClient(param); connectionMap.put(clientKey, viewer); @@ -457,7 +457,7 @@ public class ConsoleProxy { } if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() || - !param.getClientHostPassword().equals(viewer.getClientHostPassword())) + !param.getClientHostPassword().equals(viewer.getClientHostPassword())) throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": bad sid"); if (!viewer.isFrontEndAlive()) { @@ -479,6 +479,14 @@ public class ConsoleProxy { } } + private static ConsoleProxyClient getClient(ConsoleProxyClientParam param) { + if (param.getHypervHost() != null) { + return new ConsoleProxyRdpClient(); + } else { + return new ConsoleProxyVncClient(); + } + } + public static void removeViewer(ConsoleProxyClient viewer) { synchronized (connectionMap) { for (Map.Entry entry : connectionMap.entrySet()) { diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java index 18d97d250af..fa0bd06c09e 100644 --- a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java @@ -80,6 +80,9 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { String console_url = queryMap.get("consoleurl"); String console_host_session = queryMap.get("sessionref"); String vm_locale = queryMap.get("locale"); + String hypervHost = queryMap.get("hypervHost"); + String username = queryMap.get("username"); + String password = queryMap.get("password"); if (tag == null) tag = ""; @@ -128,6 +131,9 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { param.setClientTunnelUrl(console_url); param.setClientTunnelSession(console_host_session); param.setLocale(vm_locale); + param.setHypervHost(hypervHost); + param.setUsername(username); + param.setPassword(password); viewer = ConsoleProxy.getAjaxVncViewer(param, ajaxSessionIdStr); } catch (Exception e) { diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java index a04dd306960..e62ac4531b9 100644 --- a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java @@ -33,6 +33,10 @@ public class ConsoleProxyClientParam { private String locale; private String ajaxSessionId; + private String hypervHost; + private String username; + private String password; + public ConsoleProxyClientParam() { clientHostPort = 0; } @@ -94,7 +98,7 @@ public class ConsoleProxyClientParam { } public String getAjaxSessionId() { - return this.ajaxSessionId; + return ajaxSessionId; } public void setAjaxSessionId(String ajaxSessionId) { @@ -102,7 +106,7 @@ public class ConsoleProxyClientParam { } public String getLocale() { - return this.locale; + return locale; } public void setLocale(String locale) { @@ -115,4 +119,28 @@ public class ConsoleProxyClientParam { return clientHostAddress + ":" + clientHostPort; } + + public void setHypervHost(String hypervHost) { + this.hypervHost = hypervHost; + } + + public String getHypervHost() { + return hypervHost; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPassword() { + return password; + } } diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java index 7811d5030cf..51a703ae4b4 100644 --- a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java @@ -54,14 +54,29 @@ public class ConsoleProxyHttpHandlerHelper { // make sure we get information from token only guardUserInput(map); if (param != null) { - if (param.getClientHostAddress() != null) + if (param.getClientHostAddress() != null) { + s_logger.debug("decode token. host: " + param.getClientHostAddress()); map.put("host", param.getClientHostAddress()); - if (param.getClientHostPort() != 0) + } else { + s_logger.error("decode token. host info is not found!"); + } + if (param.getClientHostPort() != 0) { + s_logger.debug("decode token. port: " + param.getClientHostPort()); map.put("port", String.valueOf(param.getClientHostPort())); - if (param.getClientTag() != null) + } else { + s_logger.error("decode token. port info is not found!"); + } + if (param.getClientTag() != null) { + s_logger.debug("decode token. tag: " + param.getClientTag()); map.put("tag", param.getClientTag()); - if (param.getClientHostPassword() != null) + } else { + s_logger.error("decode token. tag info is not found!"); + } + if (param.getClientHostPassword() != null) { map.put("sid", param.getClientHostPassword()); + } else { + s_logger.error("decode token. sid info is not found!"); + } if (param.getClientTunnelUrl() != null) map.put("consoleurl", param.getClientTunnelUrl()); if (param.getClientTunnelSession() != null) @@ -70,6 +85,14 @@ public class ConsoleProxyHttpHandlerHelper { map.put("ticket", param.getTicket()); if (param.getLocale() != null) map.put("locale", param.getLocale()); + if (param.getHypervHost() != null) + map.put("hypervHost", param.getHypervHost()); + if (param.getUsername() != null) + map.put("username", param.getUsername()); + if (param.getPassword() != null) + map.put("password", param.getPassword()); + } else { + s_logger.error("Unable to decode token"); } } else { // we no longer accept information from parameter other than token @@ -88,5 +111,8 @@ public class ConsoleProxyHttpHandlerHelper { map.remove("sessionref"); map.remove("ticket"); map.remove("locale"); + map.remove("hypervHost"); + map.remove("username"); + map.remove("password"); } } diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyRdpClient.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyRdpClient.java new file mode 100644 index 00000000000..73c00be86d2 --- /dev/null +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyRdpClient.java @@ -0,0 +1,318 @@ +// 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.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.log4j.Logger; + +import rdpclient.RdpClient; +import streamer.Pipeline; +import streamer.PipelineImpl; +import streamer.SocketWrapper; +import streamer.apr.AprSocketWrapperImpl; +import streamer.ssl.SSLState; + +import com.cloud.consoleproxy.rdp.KeysymToKeycode; +import com.cloud.consoleproxy.rdp.RdpBufferedImageCanvas; +import com.cloud.consoleproxy.vnc.FrameBufferCanvas; + +import common.AwtKeyEventSource; +import common.AwtMouseEventSource; +import common.ScreenDescription; +import common.SizeChangeListener; + +public class ConsoleProxyRdpClient extends ConsoleProxyClientBase { + + private static final Logger s_logger = Logger.getLogger(ConsoleProxyRdpClient.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 RdpClient _client; + private ScreenDescription _screen; + private SocketWrapper _socket = null; + private RdpBufferedImageCanvas _canvas = null; + + private Thread _worker; + private volatile boolean _workerDone = false; + + private int _lastModifierStates = 0; + + private AwtMouseEventSource _mouseEventSource = null; + private AwtKeyEventSource _keyEventSource = null; + + public RdpBufferedImageCanvas getCanvas() { + return _canvas; + } + + public void setCanvas(RdpBufferedImageCanvas canvas) { + _canvas = canvas; + } + + @Override + public void onClientConnected() { + // TODO Auto-generated method stub + } + + @Override + public void onClientClose() { + s_logger.info("Received client close indication. remove viewer from map."); + ConsoleProxy.removeViewer(this); + } + + @Override + public boolean isHostConnected() { + //FIXME + return true; + } + + @Override + public boolean isFrontEndAlive() { + if (_socket != null) { + if (_workerDone || System.currentTimeMillis() - getClientLastFrontEndActivityTime() > ConsoleProxy.VIEWER_LINGER_SECONDS * 1000) { + s_logger.info("Front end has been idle for too long"); + _socket.shutdown(); + return false; + } else { + return true; + } + } else { + return false; + } + } + + @Override + public void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers) { + if (_client == null) + return; + + updateFrontEndActivityTime(); + + KeyEvent keyEvent = map(event, code, modifiers); + switch (event) { + case KEY_DOWN: + _keyEventSource.keyPressed(keyEvent); + break; + + case KEY_UP: + _keyEventSource.keyReleased(keyEvent); + break; + + case KEY_PRESS: + break; + + default: + assert (false); + break; + } + } + + private KeyEvent map(InputEventType event, int code, int modifiers) { + int keycode = KeysymToKeycode.getKeycode(code); + char keyChar = (char)keycode; + + KeyEvent keyEvent = null; + int modifier = mapModifier(modifiers); + + switch (event) { + case KEY_DOWN: + keyEvent = new KeyEvent(_canvas, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), modifier, keycode, keyChar); + break; + + case KEY_UP: + keyEvent = new KeyEvent(_canvas, KeyEvent.KEY_RELEASED, System.currentTimeMillis(), modifier, keycode, keyChar); + break; + + case KEY_PRESS: + break; + + default: + assert (false); + break; + } + return keyEvent; + } + + @Override + public void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers) { + if (_client == null) + return; + updateFrontEndActivityTime(); + + int mousecode = mapMouseButton(code); + int modifier = mapMouseModifier(code, modifiers); + + /*if (event == InputEventType.MOUSE_DOWN) { + _mouseEventSource.mousePressed(new MouseEvent(_canvas, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), modifier, x, y, 1, false, mousecode)); + } + + if (event == InputEventType.MOUSE_UP) { + _mouseEventSource.mouseReleased((new MouseEvent(_canvas, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), modifier, x, y, 1, false, mousecode))); + } + + if (event == InputEventType.MOUSE_DBLCLICK) { + _mouseEventSource.mouseReleased((new MouseEvent(_canvas, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), modifier, x, y, 2, false, mousecode))); + }*/ + } + + public int mapMouseModifier(int code, int modifiers) { + int mod = mapModifier(modifiers); + switch (code) { + case 0: + return mod = mod | MouseEvent.BUTTON1_DOWN_MASK; + case 2: + return mod = mod | MouseEvent.BUTTON3_DOWN_MASK; + default: + } + return mod; + } + + private int mapModifier(int modifiers) { + int mod = 0; + if ((modifiers & SHIFT_KEY_MASK) != (_lastModifierStates & SHIFT_KEY_MASK)) { + if ((modifiers & SHIFT_KEY_MASK) != 0) + mod = mod | InputEvent.SHIFT_DOWN_MASK; + } + + if ((modifiers & CTRL_KEY_MASK) != (_lastModifierStates & CTRL_KEY_MASK)) { + if ((modifiers & CTRL_KEY_MASK) != 0) + mod = mod | InputEvent.CTRL_DOWN_MASK; + } + + if ((modifiers & META_KEY_MASK) != (_lastModifierStates & META_KEY_MASK)) { + if ((modifiers & META_KEY_MASK) != 0) + mod = mod | InputEvent.META_DOWN_MASK; + } + + if ((modifiers & ALT_KEY_MASK) != (_lastModifierStates & ALT_KEY_MASK)) { + if ((modifiers & ALT_KEY_MASK) != 0) + mod = mod | InputEvent.ALT_DOWN_MASK; + } + _lastModifierStates = mod; + return mod; + } + + public int mapMouseButton(int code) { + switch (code) { + case 0: + return MouseEvent.BUTTON1; + case 2: + return MouseEvent.BUTTON3; + default: + return MouseEvent.BUTTON2; + } + + } + + @Override + public void initClient(final ConsoleProxyClientParam param) { + _workerDone = false; + + int canvasWidth = 1024; + int canvasHeight = 768; + setClientParam(param); + + final String host = param.getHypervHost(); + final String password = param.getPassword(); + final String instanceId = param.getClientHostAddress(); + final int port = param.getClientHostPort(); + + _screen = new ScreenDescription(); + _canvas = new RdpBufferedImageCanvas(this, canvasWidth, canvasHeight); + onFramebufferSizeChange(canvasWidth, canvasHeight); + + _screen.addSizeChangeListener(new SizeChangeListener() { + @Override + public void sizeChanged(int width, int height) { + if (_canvas != null) { + _canvas.setCanvasSize(width, height); + } + } + }); + + final SSLState sslState = new SSLState(); + + final String username = param.getUsername(); + String name = null; + String domain = null; + if (username.contains("\\")) { + String[] tokens = username.split("\\\\"); + name = tokens[1]; + domain = tokens[0]; + } else { + name = username; + domain = "Workgroup"; + } + + _client = new RdpClient("client", host, domain, name, password, instanceId, _screen, _canvas, + sslState); + + _mouseEventSource = _client.getMouseEventSource(); + _keyEventSource = _client.getKeyEventSource(); + + _worker = new Thread(new Runnable() { + @Override + public void run() { + _socket = new AprSocketWrapperImpl("socket", sslState); + Pipeline pipeline = new PipelineImpl("Client"); + pipeline.add(_socket, _client); + pipeline.link("socket", _client.getId(), "socket"); + pipeline.validate(); + + InetSocketAddress address = new InetSocketAddress(host, port); + ConsoleProxy.ensureRoute(host); + + try { + // Connect socket to remote server and run main loop(s) + _socket.connect(address); + } catch (IOException e) { + e.printStackTrace(); + } finally { + shutdown(); + } + + s_logger.info("Receiver thread stopped."); + _workerDone = true; + } + }); + _worker.setDaemon(true); + _worker.start(); + } + + @Override + public void closeClient() { + _workerDone = true; + shutdown(); + } + + @Override + protected FrameBufferCanvas getFrameBufferCavas() { + return _canvas; + } + + protected void shutdown() { + if (_socket != null) + _socket.shutdown(); + } +} diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/KeysymToKeycode.java b/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/KeysymToKeycode.java new file mode 100644 index 00000000000..10282ad0af3 --- /dev/null +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/KeysymToKeycode.java @@ -0,0 +1,115 @@ +// 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.rdp; + +import java.awt.event.KeyEvent; + +public class KeysymToKeycode { + + // this keymap is taken from http://openwonderland.googlecode.com/svn/trunk/modules/foundation/xremwin/src/classes/org/jdesktop/wonderland/modules/xremwin/client/KeycodeToKeysym.java + private final static int[][] map = { + /* XK_BackSpace */{0xFF08, KeyEvent.VK_BACK_SPACE}, + /* XK_Tab */{0xFF09, KeyEvent.VK_TAB}, + /* XK_Clear */{0xFF0B, KeyEvent.VK_CLEAR}, + /* XK_Return */{0xFF0D, KeyEvent.VK_ENTER}, + /* XK_Pause */{0xFF13, KeyEvent.VK_PAUSE}, + /* XK_Scroll_Lock */{0xFF14, KeyEvent.VK_SCROLL_LOCK}, + /* XK_Escape */{0xFF1B, KeyEvent.VK_ESCAPE}, + /* XK_Delete */{0xFFFF, KeyEvent.VK_DELETE}, + /* XK_Home */{0xFF50, KeyEvent.VK_HOME}, + /* XK_Left */{0xFF51, KeyEvent.VK_LEFT}, + /* XK_Up */{0xFF52, KeyEvent.VK_UP}, + /* XK_Right */{0xFF53, KeyEvent.VK_RIGHT}, + /* XK_Down */{0xFF54, KeyEvent.VK_DOWN}, + /* XK_Page_Up */{0xFF55, KeyEvent.VK_PAGE_UP}, + /* XK_Page_Down */{0xFF56, KeyEvent.VK_PAGE_DOWN}, + /* XK_End */{0xFF57, KeyEvent.VK_END}, + /* XK_Print */{0xFF61, KeyEvent.VK_PRINTSCREEN}, + /* XK_Insert */{0xFF63, KeyEvent.VK_INSERT}, + /* XK_Undo */{0xFF65, KeyEvent.VK_UNDO}, + /* XK_Find */{0xFF68, KeyEvent.VK_FIND}, + /* XK_Cancel */{0xFF69, KeyEvent.VK_CANCEL}, + /* XK_Help */{0xFF6A, KeyEvent.VK_HELP}, + /* XK_Mode_switch */{0xFF7E, KeyEvent.VK_MODECHANGE}, + /* XK_Num_Lock */{0xFF7F, KeyEvent.VK_NUM_LOCK}, + /* XK_F1 */{0xFFBE, KeyEvent.VK_F1}, + /* XK_F2 */{0xFFBF, KeyEvent.VK_F2}, + /* XK_F3 */{0xFFC0, KeyEvent.VK_F3}, + /* XK_F4 */{0xFFC1, KeyEvent.VK_F4}, + /* XK_F5 */{0xFFC2, KeyEvent.VK_F5}, + /* XK_F6 */{0xFFC3, KeyEvent.VK_F6}, + /* XK_F7 */{0xFFC4, KeyEvent.VK_F7}, + /* XK_F8 */{0xFFC5, KeyEvent.VK_F8}, + /* XK_F9 */{0xFFC6, KeyEvent.VK_F9}, + /* XK_F10 */{0xFFC7, KeyEvent.VK_F10}, + /* XK_F11 */{0xFFC8, KeyEvent.VK_F11}, + /* XK_F12 */{0xFFC9, KeyEvent.VK_F12}, + /* XK_F13 */{0xFFCA, KeyEvent.VK_F13}, + /* XK_F14 */{0xFFCB, KeyEvent.VK_F14}, + /* XK_F15 */{0xFFCC, KeyEvent.VK_F15}, + /* XK_F16 */{0xFFCD, KeyEvent.VK_F16}, + /* XK_F17 */{0xFFCE, KeyEvent.VK_F17}, + /* XK_F18 */{0xFFCF, KeyEvent.VK_F18}, + /* XK_F19 */{0xFFD0, KeyEvent.VK_F19}, + /* XK_F20 */{0xFFD1, KeyEvent.VK_F20}, + /* XK_F21 */{0xFFD2, KeyEvent.VK_F21}, + /* XK_F22 */{0xFFD3, KeyEvent.VK_F22}, + /* XK_F23 */{0xFFD4, KeyEvent.VK_F23}, + /* XK_F24 */{0xFFD5, KeyEvent.VK_F24}, + /* XK_Shift_L */{0xFFE1, KeyEvent.VK_SHIFT}, + /* XK_Control_L */{0xFFE3, KeyEvent.VK_CONTROL}, + /* XK_Caps_Lock */{0xFFE5, KeyEvent.VK_CAPS_LOCK}, + /* XK_Meta_L */{0xFFE7, KeyEvent.VK_META}, + /* XK_Alt_L */{0xFFE9, KeyEvent.VK_ALT}, + /* XK_a */{0x0061, KeyEvent.VK_A}, + /* XK_b */{0x0062, KeyEvent.VK_B}, + /* XK_c */{0x0063, KeyEvent.VK_C}, + /* XK_d */{0x0064, KeyEvent.VK_D}, + /* XK_e */{0x0065, KeyEvent.VK_E}, + /* XK_f */{0x0066, KeyEvent.VK_F}, + /* XK_g */{0x0067, KeyEvent.VK_G}, + /* XK_h */{0x0068, KeyEvent.VK_H}, + /* XK_i */{0x0069, KeyEvent.VK_I}, + /* XK_j */{0x006a, KeyEvent.VK_J}, + /* XK_k */{0x006b, KeyEvent.VK_K}, + /* XK_l */{0x006c, KeyEvent.VK_L}, + /* XK_m */{0x006d, KeyEvent.VK_M}, + /* XK_n */{0x006e, KeyEvent.VK_N}, + /* XK_o */{0x006f, KeyEvent.VK_O}, + /* XK_p */{0x0070, KeyEvent.VK_P}, + /* XK_q */{0x0071, KeyEvent.VK_Q}, + /* XK_r */{0x0072, KeyEvent.VK_R}, + /* XK_s */{0x0073, KeyEvent.VK_S}, + /* XK_t */{0x0074, KeyEvent.VK_T}, + /* XK_u */{0x0075, KeyEvent.VK_U}, + /* XK_v */{0x0076, KeyEvent.VK_V}, + /* XK_w */{0x0077, KeyEvent.VK_W}, + /* XK_x */{0x0078, KeyEvent.VK_X}, + /* XK_y */{0x0079, KeyEvent.VK_Y}, + /* XK_z */{0x007a, KeyEvent.VK_Z}, + }; + + public static int getKeycode(int keysym) { + for (int i = 0; i < (map.length - 1); i++) { + if (map[i][0] == keysym) { + return map[i][1]; + } + } + return keysym; + } + +} diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/RdpBufferedImageCanvas.java b/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/RdpBufferedImageCanvas.java new file mode 100644 index 00000000000..6dabe056ac4 --- /dev/null +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/RdpBufferedImageCanvas.java @@ -0,0 +1,103 @@ +// 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.rdp; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.List; + +import com.cloud.consoleproxy.ConsoleProxyRdpClient; +import com.cloud.consoleproxy.util.ImageHelper; +import com.cloud.consoleproxy.util.TileInfo; +import com.cloud.consoleproxy.vnc.FrameBufferCanvas; + +import common.BufferedImageCanvas; + +public class RdpBufferedImageCanvas extends BufferedImageCanvas implements FrameBufferCanvas { + /** + * + */ + private static final long serialVersionUID = 1L; + + private final ConsoleProxyRdpClient _rdpClient; + + public RdpBufferedImageCanvas(ConsoleProxyRdpClient client, int width, int height) { + super(width, height); + _rdpClient = client; + } + + @Override + public Image getFrameBufferScaledImage(int width, int height) { + if (offlineImage != null) + return offlineImage.getScaledInstance(width, height, Image.SCALE_DEFAULT); + return null; + } + + @Override + public byte[] getFrameBufferJpeg() { + int width = offlineImage.getWidth(); + int height = offlineImage.getHeight(); + + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D g = bufferedImage.createGraphics(); + synchronized (offlineImage) { + g.drawImage(offlineImage, 0, 0, width, height, 0, 0, width, height, null); + g.dispose(); + } + + byte[] imgBits = null; + try { + imgBits = ImageHelper.jpegFromImage(bufferedImage); + } catch (IOException e) { + } + + return imgBits; + } + + @Override + public byte[] getTilesMergedJpeg(List tileList, int tileWidth, int tileHeight) { + int width = Math.max(tileWidth, tileWidth * tileList.size()); + + BufferedImage bufferedImage = new BufferedImage(width, tileHeight, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D g = bufferedImage.createGraphics(); + + synchronized (offlineImage) { + int i = 0; + for (TileInfo tile : tileList) { + Rectangle rc = tile.getTileRect(); + g.drawImage(offlineImage, i * tileWidth, 0, i * tileWidth + rc.width, rc.height, rc.x, rc.y, rc.x + rc.width, rc.y + rc.height, null); + i++; + } + } + + byte[] imgBits = null; + try { + imgBits = ImageHelper.jpegFromImage(bufferedImage); + } catch (IOException e) { + } + return imgBits; + } + + @Override + public void updateFrameBuffer(int x, int y, int w, int h) { + _rdpClient.onFramebufferUpdate(x, y, w, h); + } + +}