package com.cloud.consoleproxy; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; import org.apache.log4j.Logger; import com.cloud.console.TileInfo; import com.cloud.console.TileTracker; import com.cloud.consoleproxy.vnc.VncClientListener; public class ConsoleProxyVncClient implements VncClientListener { private static final Logger s_logger = Logger.getLogger(ConsoleProxyVncClient.class); private TileTracker tracker; boolean dirtyFlag = false; private Object tileDirtyEvent = new Object(); private AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2); @Override public void onFramebufferSizeChange(int w, int h) { // TODO } @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(); } private void signalTileDirtyEvent() { synchronized(tileDirtyEvent) { dirtyFlag = true; tileDirtyEvent.notifyAll(); } } /* // // AJAX Image manipulation // public void copyTile(Graphics2D g, int x, int y, Rectangle rc) { if(vc != null && vc.memImage != null) { synchronized(vc.memImage) { g.drawImage(vc.memImage, x, y, x + rc.width, y + rc.height, rc.x, rc.y, rc.x + rc.width, rc.y + rc.height, null); } } } public byte[] getFrameBufferJpeg() { int width = 800; int height = 600; if(vc != null) { width = vc.scaledWidth; height = vc.scaledHeight; } if(s_logger.isTraceEnabled()) s_logger.trace("getFrameBufferJpeg, w: " + width + ", h: " + height); BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); if(vc != null && vc.memImage != null) { synchronized(vc.memImage) { Graphics2D g = bufferedImage.createGraphics(); g.drawImage(vc.memImage, 0, 0, width, height, 0, 0, width, height, null); } } byte[] imgBits = null; try { imgBits = jpegFromImage(bufferedImage); } catch (IOException e) { } return imgBits; } 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); if(s_logger.isTraceEnabled()) s_logger.trace("Create merged image, w: " + width + ", h: " + tileHeight); if(vc != null && vc.memImage != null) { synchronized(vc.memImage) { Graphics2D g = bufferedImage.createGraphics(); int i = 0; for(TileInfo tile : tileList) { Rectangle rc = tile.getTileRect(); if(s_logger.isTraceEnabled()) s_logger.trace("Merge tile into jpeg from (" + rc.x + "," + rc.y + "," + (rc.x + rc.width) + "," + (rc.y + rc.height) + ") to (" + i*tileWidth + ",0)" ); g.drawImage(vc.memImage, 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 = jpegFromImage(bufferedImage); if(s_logger.isTraceEnabled()) s_logger.trace("Merge jpeg image size: " + imgBits.length + ", tiles: " + tileList.size()); } catch (IOException e) { } return imgBits; } public byte[] jpegFromImage(BufferedImage image) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(128000); javax.imageio.ImageIO.write(image, "jpg", bos); byte[] jpegBits = bos.toByteArray(); bos.close(); return jpegBits; } private String prepareAjaxImage(List 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("/ajaximg?host="); sb.append(host).append("&port=").append(port).append("&sid=").append(passwordParam); sb.append("&key=").append(key).append("&ts=").append(System.currentTimeMillis()); return sb.toString(); } private String prepareAjaxSession(boolean init) { StringBuffer sb = new StringBuffer(); if(init) ajaxSessionId++; sb.append("/ajax?host=").append(host).append("&port=").append(port); sb.append("&sid=").append(passwordParam).append("&sess=").append(ajaxSessionId); return sb.toString(); } public String onAjaxClientKickoff() { return "onKickoff();"; } private boolean waitForViewerReady() { long startTick = System.currentTimeMillis(); while(System.currentTimeMillis() - startTick < 5000) { if(this.status == ConsoleProxyViewer.STATUS_NORMAL_OPERATION) return true; try { Thread.sleep(100); } catch (InterruptedException e) { } } return false; } private String onAjaxClientConnectFailed() { return "

" + "Unable to start console session as connection is refused by the machine you are accessing" + "

"; } public String onAjaxClientStart(String title, List languages, String guest) { if(!waitForViewerReady()) return onAjaxClientConnectFailed(); // make sure we switch to AJAX view on start setAjaxViewer(true); 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); synchronized(this) { if(framebufferResized) { framebufferResized = false; } } int retry = 0; if(justCreated()) { tracker.initCoverageTest(); try { rfb.writeFramebufferUpdateRequest(0, 0, tracker.getTrackWidth(), tracker.getTrackHeight(), false); while(!tracker.hasFullCoverage() && retry < 10) { try { Thread.sleep(1000); } catch (InterruptedException e) { } retry++; } } catch (IOException e1) { s_logger.warn("Connection was broken "); } } List 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 languages, String guest) { StringBuffer sbLanguages = new StringBuffer(""); if(languages != null) { for(String lang : languages) { if(sbLanguages.length() > 0) { sbLanguages.append(","); } sbLanguages.append(lang); } } boolean linuxGuest = true; if(guest != null && guest.equalsIgnoreCase("windows")) linuxGuest = false; String[] content = new String[] { "", "", "", "", "", "", "", "", "" + title + "", "", "", "
", "", "", "
", "
", "", "", "" }; StringBuffer sb = new StringBuffer(); for(int i = 0; i < content.length; i++) sb.append(content[i]); return sb.toString(); } public String onAjaxClientDisconnected() { return "onDisconnect();"; } public String onAjaxClientUpdate() { 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 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(); } public long getAjaxSessionId() { return this.ajaxSessionId; } public AjaxFIFOImageCache getAjaxImageCache() { return ajaxImageCache; } public boolean isAjaxViewer() { return ajaxViewer; } public synchronized void setAjaxViewer(boolean ajaxViewer) { if(this.ajaxViewer != ajaxViewer) { if(this.ajaxViewer) { // previous session was AJAX session this.ajaxSessionId++; // increase the session id so that it will disconnect existing AJAX viewer } else { // close java client session if(clientStream != null) { byte[] bs = new byte[2]; bs[0] = (byte)250; bs[1] = 1; writeToClientStream(bs); try { clientStream.close(); } catch (IOException e) { } clientStream = null; } } this.ajaxViewer = ajaxViewer; } } */ }