// Copyright 2012 Citrix Systems, Inc. Licensed under the // Apache License, Version 2.0 (the "License"); you may not use this // file except in compliance with the License. Citrix Systems, Inc. // reserves all rights not expressly granted by 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. // // Automatically generated by addcopyright.py at 04/03/2012 package com.cloud.consoleproxy; import java.awt.Image; import java.awt.Rectangle; import java.util.List; import org.apache.log4j.Logger; import com.cloud.consoleproxy.util.TileInfo; import com.cloud.consoleproxy.util.TileTracker; import com.cloud.consoleproxy.vnc.FrameBufferCanvas; /** * * @author Kelven Yang * * an instance of specialized console protocol implementation, such as VNC or RDP * * It mainly implements the features needed by front-end AJAX viewer * */ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, ConsoleProxyClientListener { private static final Logger s_logger = Logger.getLogger(ConsoleProxyClientBase.class); private static int s_nextClientId = 0; protected int clientId = getNextClientId(); protected long ajaxSessionId = 0; protected boolean dirtyFlag = false; protected Object tileDirtyEvent = new Object(); protected TileTracker tracker; protected AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2); protected ConsoleProxyClientParam clientParam; protected String clientToken; protected long createTime = System.currentTimeMillis(); protected long lastFrontEndActivityTime = System.currentTimeMillis(); protected boolean framebufferResized = false; protected int resizedFramebufferWidth; protected int resizedFramebufferHeight; public ConsoleProxyClientBase() { tracker = new TileTracker(); tracker.initTracking(64, 64, 800, 600); } // // interface ConsoleProxyClient // @Override public int getClientId() { return clientId; } public abstract boolean isHostConnected(); public abstract boolean isFrontEndAlive(); @Override public long getAjaxSessionId() { return this.ajaxSessionId; } @Override public AjaxFIFOImageCache getAjaxImageCache() { return ajaxImageCache; } public Image getClientScaledImage(int width, int height) { FrameBufferCanvas canvas = getFrameBufferCavas(); if(canvas != null) return canvas.getFrameBufferScaledImage(width, height); return null; } public abstract void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers); public abstract void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers); @Override public long getClientCreateTime() { return createTime; } @Override public long getClientLastFrontEndActivityTime() { return lastFrontEndActivityTime; } @Override public String getClientHostAddress() { return clientParam.getClientHostAddress(); } @Override public int getClientHostPort() { return clientParam.getClientHostPort(); } @Override public String getClientHostPassword() { return clientParam.getClientHostPassword(); } @Override public String getClientTag() { if(clientParam.getClientTag() != null) return clientParam.getClientTag(); return ""; } @Override public abstract void initClient(ConsoleProxyClientParam param); @Override public abstract void closeClient(); // // interface FrameBufferEventListener // @Override public void onFramebufferSizeChange(int w, int h) { tracker.resize(w, h); synchronized(this) { framebufferResized = true; resizedFramebufferWidth = w; resizedFramebufferHeight = h; } signalTileDirtyEvent(); } @Override public void onFramebufferUpdate(int x, int y, int w, int h) { if(s_logger.isTraceEnabled()) s_logger.trace("Frame buffer update {" + x + "," + y + "," + w + "," + h + "}"); tracker.invalidate(new Rectangle(x, y, w, h)); signalTileDirtyEvent(); } // // AJAX Image manipulation // public byte[] getFrameBufferJpeg() { FrameBufferCanvas canvas = getFrameBufferCavas(); if(canvas != null) return canvas.getFrameBufferJpeg(); return null; } public byte[] getTilesMergedJpeg(List<TileInfo> tileList, int tileWidth, int tileHeight) { FrameBufferCanvas canvas = getFrameBufferCavas(); if(canvas != null) return canvas.getTilesMergedJpeg(tileList, tileWidth, tileHeight); return null; } private String prepareAjaxImage(List<TileInfo> tiles, boolean init) { byte[] imgBits; if(init) imgBits = getFrameBufferJpeg(); else imgBits = getTilesMergedJpeg(tiles, tracker.getTileWidth(), tracker.getTileHeight()); if(imgBits == null) { s_logger.warn("Unable to generate jpeg image"); } else { if(s_logger.isTraceEnabled()) s_logger.trace("Generated jpeg image size: " + imgBits.length); } int key = ajaxImageCache.putImage(imgBits); StringBuffer sb = new StringBuffer(); sb.append("/ajaximg?token=").append(clientToken); sb.append("&key=").append(key); sb.append("&ts=").append(System.currentTimeMillis()); return sb.toString(); } private String prepareAjaxSession(boolean init) { if(init) { synchronized(this) { ajaxSessionId++; } } StringBuffer sb = new StringBuffer(); sb.append("/ajax?token=").append(clientToken).append("&sess=").append(ajaxSessionId); return sb.toString(); } @Override public String onAjaxClientKickoff() { return "onKickoff();"; } private boolean waitForViewerReady() { long startTick = System.currentTimeMillis(); while(System.currentTimeMillis() - startTick < 5000) { if(getFrameBufferCavas() != null) return true; try { Thread.sleep(100); } catch (InterruptedException e) { } } return false; } private String onAjaxClientConnectFailed() { return "<html><head></head><body><div id=\"main_panel\" tabindex=\"1\"><p>" + "Unable to start console session as connection is refused by the machine you are accessing" + "</p></div></body></html>"; } @Override public String onAjaxClientStart(String title, List<String> languages, String guest) { updateFrontEndActivityTime(); if(!waitForViewerReady()) return onAjaxClientConnectFailed(); synchronized(this) { ajaxSessionId++; framebufferResized = false; } int tileWidth = tracker.getTileWidth(); int tileHeight = tracker.getTileHeight(); int width = tracker.getTrackWidth(); int height = tracker.getTrackHeight(); if(s_logger.isTraceEnabled()) s_logger.trace("Ajax client start, frame buffer w: " + width + ", " + height); int retry = 0; tracker.initCoverageTest(); while(!tracker.hasFullCoverage() && retry < 10) { try { Thread.sleep(1000); } catch (InterruptedException e) { } retry++; } List<TileInfo> tiles = tracker.scan(true); String imgUrl = prepareAjaxImage(tiles, true); String updateUrl = prepareAjaxSession(true); StringBuffer sbTileSequence = new StringBuffer(); int i = 0; for(TileInfo tile : tiles) { sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]"); if(i < tiles.size() - 1) sbTileSequence.append(","); i++; } return getAjaxViewerPageContent(sbTileSequence.toString(), imgUrl, updateUrl, width, height, tileWidth, tileHeight, title, ConsoleProxy.keyboardType == ConsoleProxy.KEYBOARD_RAW, languages, guest); } private String getAjaxViewerPageContent(String tileSequence, String imgUrl, String updateUrl, int width, int height, int tileWidth, int tileHeight, String title, boolean rawKeyboard, List<String> languages, String guest) { StringBuffer sbLanguages = new StringBuffer(""); if(languages != null) { for(String lang : languages) { if(sbLanguages.length() > 0) { sbLanguages.append(","); } sbLanguages.append(lang); } } String[] content = new String[] { "<html>", "<head>", "<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/jquery.js\"></script>", "<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/cloud.logger.js\"></script>", "<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/ajaxviewer.js\"></script>", "<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/handler.js\"></script>", "<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/ajaxviewer.css\"></link>", "<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/logger.css\"></link>", "<title>" + title + "</title>", "</head>", "<body>", "<div id=\"toolbar\">", "<ul>", "<li>", "<a href=\"#\" cmd=\"sendCtrlAltDel\">", "<span><img align=\"left\" src=\"/resource/images/cad.gif\" alt=\"Ctrl-Alt-Del\" />Ctrl-Alt-Del</span>", "</a>", "</li>", "<li>", "<a href=\"#\" cmd=\"sendCtrlEsc\">", "<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Ctrl-Esc\" style=\"width:16px;height:16px\"/>Ctrl-Esc</span>", "</a>", "</li>", "<li class=\"pulldown\">", "<a href=\"#\">", "<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Keyboard\" style=\"width:16px;height:16px\"/>Keyboard</span>", "</a>", "<ul>", "<li><a href=\"#\" cmd=\"keyboard_us\"><span>Standard (US) keyboard</span></a></li>", "<li><a href=\"#\" cmd=\"keyboard_jp\"><span>Japanese keyboard</span></a></li>", "</ul>", "</li>", "</ul>", "<span id=\"light\" class=\"dark\" cmd=\"toggle_logwin\"></span>", "</div>", "<div id=\"main_panel\" tabindex=\"1\"></div>", "<script language=\"javascript\">", "var acceptLanguages = '" + sbLanguages.toString() + "';", "var tileMap = [ " + tileSequence + " ];", "var ajaxViewer = new AjaxViewer('main_panel', '" + imgUrl + "', '" + updateUrl + "', tileMap, ", String.valueOf(width) + ", " + String.valueOf(height) + ", " + String.valueOf(tileWidth) + ", " + String.valueOf(tileHeight) + ");", "$(function() {", "ajaxViewer.start();", "});", "</script>", "</body>", "</html>" }; StringBuffer sb = new StringBuffer(); for(int i = 0; i < content.length; i++) sb.append(content[i]); return sb.toString(); } public String onAjaxClientDisconnected() { return "onDisconnect();"; } @Override public String onAjaxClientUpdate() { updateFrontEndActivityTime(); if(!waitForViewerReady()) return onAjaxClientDisconnected(); synchronized(tileDirtyEvent) { if(!dirtyFlag) { try { tileDirtyEvent.wait(3000); } catch(InterruptedException e) { } } } boolean doResize = false; synchronized(this) { if(framebufferResized) { framebufferResized = false; doResize = true; } } List<TileInfo> tiles; if(doResize) tiles = tracker.scan(true); else tiles = tracker.scan(false); dirtyFlag = false; String imgUrl = prepareAjaxImage(tiles, false); StringBuffer sbTileSequence = new StringBuffer(); int i = 0; for(TileInfo tile : tiles) { sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]"); if(i < tiles.size() - 1) sbTileSequence.append(","); i++; } return getAjaxViewerUpdatePageContent(sbTileSequence.toString(), imgUrl, doResize, resizedFramebufferWidth, resizedFramebufferHeight, tracker.getTileWidth(), tracker.getTileHeight()); } private String getAjaxViewerUpdatePageContent(String tileSequence, String imgUrl, boolean resized, int width, int height, int tileWidth, int tileHeight) { String[] content = new String[] { "tileMap = [ " + tileSequence + " ];", resized ? "ajaxViewer.resize('main_panel', " + width + ", " + height + " , " + tileWidth + ", " + tileHeight + ");" : "", "ajaxViewer.refresh('" + imgUrl + "', tileMap, false);" }; StringBuffer sb = new StringBuffer(); for(int i = 0; i < content.length; i++) sb.append(content[i]); return sb.toString(); } // // Helpers // private synchronized static int getNextClientId() { return ++s_nextClientId; } private void signalTileDirtyEvent() { synchronized(tileDirtyEvent) { dirtyFlag = true; tileDirtyEvent.notifyAll(); } } public void updateFrontEndActivityTime() { lastFrontEndActivityTime = System.currentTimeMillis(); } protected abstract FrameBufferCanvas getFrameBufferCavas(); public ConsoleProxyClientParam getClientParam() { return clientParam; } public void setClientParam(ConsoleProxyClientParam clientParam) { this.clientParam = clientParam; ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(ConsoleProxy.getEncryptorPassword()); this.clientToken = encryptor.encryptObject(ConsoleProxyClientParam.class, clientParam); } }