mirror of https://github.com/apache/cloudstack.git
Console proxy refactoring incremental check-in - pluggable framework for VNC and RDP engine
This commit is contained in:
parent
d5941bee1c
commit
43cf095a13
|
|
@ -629,8 +629,7 @@ public class ConsoleProxy {
|
|||
viewer = connMap.get(key);
|
||||
}
|
||||
|
||||
long seconds_unused =
|
||||
(System.currentTimeMillis() - viewer.lastUsedTime) / 1000;
|
||||
long seconds_unused = (System.currentTimeMillis() - viewer.lastUsedTime) / 1000;
|
||||
|
||||
if (seconds_unused > viewerLinger / 2 && viewer.clientStream != null) {
|
||||
s_logger.info("Pinging client for " + viewer +
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package com.cloud.consoleproxy;
|
||||
|
||||
/**
|
||||
* @author Kelven Yang
|
||||
* ConsoleProxyClient defines an standard interface that a console client should implement,
|
||||
* example of such console clients could be a VNC client or a RDP client
|
||||
*
|
||||
* ConsoleProxyClient maintains a session towards the target host, it glues the session
|
||||
* to a AJAX frontend viewer
|
||||
*/
|
||||
public interface ConsoleProxyClient {
|
||||
int getClientId();
|
||||
|
||||
long getClientCreateTime();
|
||||
long getClientLastFrontEndActivityTime();
|
||||
|
||||
String getClientHostAddress();
|
||||
int getClientHostPort();
|
||||
String getClientHostPassword();
|
||||
String getClientTag();
|
||||
|
||||
void initClient(String clientHostAddress, int clientHostPort,
|
||||
String clientHostPassword, String clientTag);
|
||||
void closeClient();
|
||||
}
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
package com.cloud.consoleproxy;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.console.TileInfo;
|
||||
import com.cloud.console.TileTracker;
|
||||
import com.cloud.consoleproxy.vnc.FrameBufferCanvas;
|
||||
import com.cloud.consoleproxy.vnc.FrameBufferEventListener;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kelven Yang
|
||||
*
|
||||
* ConsoleProxyClientBase implements a base console client class that can be extended to realize
|
||||
* 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, FrameBufferEventListener {
|
||||
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 String host;
|
||||
protected int port;
|
||||
protected String passwordParam;
|
||||
protected String tag = "";
|
||||
protected long createTime = System.currentTimeMillis();
|
||||
protected long lastFrontEndActivityTime = System.currentTimeMillis();
|
||||
|
||||
protected boolean framebufferResized = false;
|
||||
protected int resizedFramebufferWidth;
|
||||
protected int resizedFramebufferHeight;
|
||||
|
||||
public ConsoleProxyClientBase() {
|
||||
}
|
||||
|
||||
//
|
||||
// interface ConsoleProxyClient
|
||||
//
|
||||
@Override
|
||||
public int getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getClientCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getClientLastFrontEndActivityTime() {
|
||||
return lastFrontEndActivityTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientHostAddress() {
|
||||
return host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClientHostPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientHostPassword() {
|
||||
return passwordParam;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void initClient(String clientHostAddress, int clientHostPort,
|
||||
String clientHostPassword, String clientTag);
|
||||
|
||||
@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("/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) {
|
||||
if(init) {
|
||||
synchronized(this) {
|
||||
ajaxSessionId++;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
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(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>";
|
||||
}
|
||||
|
||||
public String onAjaxClientStart(String title, List<String> languages, String guest) {
|
||||
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();";
|
||||
}
|
||||
|
||||
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<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;
|
||||
}
|
||||
|
||||
public long getAjaxSessionId() {
|
||||
return this.ajaxSessionId;
|
||||
}
|
||||
|
||||
public AjaxFIFOImageCache getAjaxImageCache() {
|
||||
return ajaxImageCache;
|
||||
}
|
||||
|
||||
private void signalTileDirtyEvent() {
|
||||
synchronized(tileDirtyEvent) {
|
||||
dirtyFlag = true;
|
||||
tileDirtyEvent.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFrontEndActivityTime() {
|
||||
lastFrontEndActivityTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
protected abstract FrameBufferCanvas getFrameBufferCavas();
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.cloud.consoleproxy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kelven Yang
|
||||
* ConsoleProxyClientStatsCollector collects client stats for console proxy agent to report
|
||||
* to external management software
|
||||
*/
|
||||
public class ConsoleProxyClientStatsCollector {
|
||||
|
||||
ArrayList<ConsoleProxyConnection> connections;
|
||||
|
||||
public ConsoleProxyClientStatsCollector() {
|
||||
}
|
||||
|
||||
public ConsoleProxyClientStatsCollector(Hashtable<String, ConsoleProxyClient> connMap) {
|
||||
setConnections(connMap);
|
||||
}
|
||||
|
||||
public String getStatsReport() {
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
return gson.toJson(this);
|
||||
}
|
||||
|
||||
private void setConnections(Hashtable<String, ConsoleProxyClient> connMap) {
|
||||
|
||||
ArrayList<ConsoleProxyConnection> conns = new ArrayList<ConsoleProxyConnection>();
|
||||
Enumeration<String> e = connMap.keys();
|
||||
while (e.hasMoreElements()) {
|
||||
synchronized (connMap) {
|
||||
String key = e.nextElement();
|
||||
ConsoleProxyClient client = connMap.get(key);
|
||||
|
||||
ConsoleProxyConnection conn = new ConsoleProxyConnection();
|
||||
|
||||
conn.id = client.getClientId();
|
||||
conn.clientInfo = "";
|
||||
conn.host = client.getClientHostAddress();
|
||||
conn.port = client.getClientHostPort();
|
||||
conn.tag = client.getClientTag();
|
||||
conn.createTime = client.getClientCreateTime();
|
||||
conn.lastUsedTime = client.getClientLastFrontEndActivityTime();
|
||||
conns.add(conn);
|
||||
}
|
||||
}
|
||||
connections = conns;
|
||||
}
|
||||
|
||||
public static class ConsoleProxyConnection {
|
||||
public int id;
|
||||
public String clientInfo;
|
||||
public String host;
|
||||
public int port;
|
||||
public String tag;
|
||||
public long createTime;
|
||||
public long lastUsedTime;
|
||||
|
||||
public ConsoleProxyConnection() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.cloud.consoleproxy;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kelven Yang
|
||||
* ConsoleProxyGCThread does house-keeping work for the process, it helps cleanup log files,
|
||||
* recycle idle client sessions without front-end activities and report client stats to external
|
||||
* management software
|
||||
*/
|
||||
public class ConsoleProxyGCThread extends Thread {
|
||||
private static final Logger s_logger = Logger.getLogger(ConsoleProxyGCThread.class);
|
||||
|
||||
private final static int MAX_SESSION_IDLE_SECONDS = 180;
|
||||
|
||||
private Hashtable<String, ConsoleProxyClient> connMap;
|
||||
private long lastLogScan = 0;
|
||||
|
||||
public ConsoleProxyGCThread(Hashtable<String, ConsoleProxyClient> connMap) {
|
||||
this.connMap = connMap;
|
||||
}
|
||||
|
||||
private void cleanupLogging() {
|
||||
if(lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000)
|
||||
return;
|
||||
|
||||
lastLogScan = System.currentTimeMillis();
|
||||
|
||||
File logDir = new File("./logs");
|
||||
File files[] = logDir.listFiles();
|
||||
if(files != null) {
|
||||
for(File file : files) {
|
||||
if(System.currentTimeMillis() - file.lastModified() >= 86400000L) {
|
||||
try {
|
||||
file.delete();
|
||||
} catch(Throwable e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
cleanupLogging();
|
||||
|
||||
s_logger.info("connMap=" + connMap);
|
||||
Enumeration<String> e = connMap.keys();
|
||||
while (e.hasMoreElements()) {
|
||||
String key;
|
||||
ConsoleProxyClient client;
|
||||
|
||||
synchronized (connMap) {
|
||||
key = e.nextElement();
|
||||
client = connMap.get(key);
|
||||
}
|
||||
|
||||
long seconds_unused = (System.currentTimeMillis() - client.getClientLastFrontEndActivityTime()) / 1000;
|
||||
if (seconds_unused < MAX_SESSION_IDLE_SECONDS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
synchronized (connMap) {
|
||||
connMap.remove(key);
|
||||
}
|
||||
|
||||
// close the server connection
|
||||
s_logger.info("Dropping " + client + " which has not been used for " + seconds_unused + " seconds");
|
||||
client.closeClient();
|
||||
|
||||
// report load changes
|
||||
String loadInfo = new ConsoleProxyClientStatsCollector(connMap).getStatsReport();
|
||||
ConsoleProxy.reportLoadInfo(loadInfo);
|
||||
if(s_logger.isDebugEnabled())
|
||||
s_logger.debug("Report load change : " + loadInfo);
|
||||
}
|
||||
|
||||
try { Thread.sleep(30000); } catch (InterruptedException ex) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,426 +1,36 @@
|
|||
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;
|
||||
import com.cloud.consoleproxy.vnc.FrameBufferCanvas;
|
||||
import com.cloud.consoleproxy.vnc.VncClient;
|
||||
|
||||
public class ConsoleProxyVncClient implements VncClientListener {
|
||||
/**
|
||||
*
|
||||
* @author Kelven Yang
|
||||
* 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 TileTracker tracker;
|
||||
|
||||
boolean dirtyFlag = false;
|
||||
private Object tileDirtyEvent = new Object();
|
||||
private AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2);
|
||||
|
||||
@Override
|
||||
public void onFramebufferSizeChange(int w, int h) {
|
||||
private VncClient client;
|
||||
|
||||
public ConsoleProxyVncClient() {
|
||||
}
|
||||
|
||||
public void initClient(String clientHostAddress, int clientHostPort,
|
||||
String clientHostPassword, String clientTag) {
|
||||
// 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();
|
||||
|
||||
public void closeClient() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private void signalTileDirtyEvent() {
|
||||
synchronized(tileDirtyEvent) {
|
||||
dirtyFlag = true;
|
||||
tileDirtyEvent.notifyAll();
|
||||
}
|
||||
protected FrameBufferCanvas getFrameBufferCavas() {
|
||||
if(client != null)
|
||||
return client.getFrameBufferCanvas();
|
||||
return null;
|
||||
}
|
||||
/*
|
||||
//
|
||||
// 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<TileInfo> 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<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("/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 "<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>";
|
||||
}
|
||||
|
||||
public String onAjaxClientStart(String title, List<String> 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<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);
|
||||
}
|
||||
}
|
||||
|
||||
boolean linuxGuest = true;
|
||||
if(guest != null && guest.equalsIgnoreCase("windows"))
|
||||
linuxGuest = false;
|
||||
|
||||
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();";
|
||||
}
|
||||
|
||||
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<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();
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import com.cloud.consoleproxy.util.ImageHelper;
|
|||
* A <code>BuffereImageCanvas</code> component represents frame buffer image on the
|
||||
* screen. It also notifies its subscribers when screen is repainted.
|
||||
*/
|
||||
public class BufferedImageCanvas extends Canvas {
|
||||
public class BufferedImageCanvas extends Canvas implements FrameBufferCanvas {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Offline screen buffer
|
||||
|
|
@ -80,6 +80,7 @@ public class BufferedImageCanvas extends Canvas {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getFrameBufferJpeg() {
|
||||
int width = 800;
|
||||
int height = 600;
|
||||
|
|
@ -102,6 +103,7 @@ public class BufferedImageCanvas extends Canvas {
|
|||
return imgBits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getTilesMergedJpeg(List<TileInfo> tileList, int tileWidth, int tileHeight) {
|
||||
int width = Math.max(tileWidth, tileWidth*tileList.size());
|
||||
BufferedImage bufferedImage = new BufferedImage(width, tileHeight,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.cloud.consoleproxy.vnc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.cloud.console.TileInfo;
|
||||
|
||||
public interface FrameBufferCanvas {
|
||||
public byte[] getFrameBufferJpeg();
|
||||
public byte[] getTilesMergedJpeg(List<TileInfo> tileList, int tileWidth, int tileHeight);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.cloud.consoleproxy.vnc;
|
||||
|
||||
public interface VncClientListener {
|
||||
public interface FrameBufferEventListener {
|
||||
void onFramebufferSizeChange(int w, int h);
|
||||
void onFramebufferUpdate(int x, int y, int w, int h);
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ public class VncClient {
|
|||
private VncServerPacketReceiver receiver;
|
||||
|
||||
private boolean noUI = false;
|
||||
private VncClientListener clientListener = null;
|
||||
private FrameBufferEventListener clientListener = null;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 3) {
|
||||
|
|
@ -62,7 +62,7 @@ public class VncClient {
|
|||
/* LOG */SimpleLogger.info("Usage: HOST PORT PASSWORD.");
|
||||
}
|
||||
|
||||
public VncClient(String host, int port, String password, boolean noUI, VncClientListener clientListener)
|
||||
public VncClient(String host, int port, String password, boolean noUI, FrameBufferEventListener clientListener)
|
||||
throws UnknownHostException, IOException {
|
||||
|
||||
this.noUI = noUI;
|
||||
|
|
@ -88,7 +88,6 @@ public class VncClient {
|
|||
socket.close();
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void connectTo(String host, int port, String password) throws UnknownHostException, IOException {
|
||||
|
|
@ -356,5 +355,11 @@ public class VncClient {
|
|||
screen.setDesktopName(desktopName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public FrameBufferCanvas getFrameBufferCanvas() {
|
||||
if(receiver != null)
|
||||
return receiver.getCanvas();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ public class VncServerPacketReceiver implements Runnable {
|
|||
private boolean connectionAlive = true;
|
||||
private VncClient vncConnection;
|
||||
private final FrameBufferUpdateListener fburListener;
|
||||
private final VncClientListener clientListener;
|
||||
private final FrameBufferEventListener clientListener;
|
||||
|
||||
public VncServerPacketReceiver(DataInputStream is, BufferedImageCanvas canvas, VncScreenDescription screen, VncClient vncConnection,
|
||||
FrameBufferUpdateListener fburListener, VncClientListener clientListener) {
|
||||
FrameBufferUpdateListener fburListener, FrameBufferEventListener clientListener) {
|
||||
this.screen = screen;
|
||||
this.canvas = canvas;
|
||||
this.is = is;
|
||||
|
|
@ -28,6 +28,10 @@ public class VncServerPacketReceiver implements Runnable {
|
|||
this.fburListener = fburListener;
|
||||
this.clientListener = clientListener;
|
||||
}
|
||||
|
||||
public BufferedImageCanvas getCanvas() {
|
||||
return canvas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import java.io.IOException;
|
|||
|
||||
import com.cloud.consoleproxy.vnc.BufferedImageCanvas;
|
||||
import com.cloud.consoleproxy.vnc.RfbConstants;
|
||||
import com.cloud.consoleproxy.vnc.VncClientListener;
|
||||
import com.cloud.consoleproxy.vnc.FrameBufferEventListener;
|
||||
import com.cloud.consoleproxy.vnc.VncScreenDescription;
|
||||
import com.cloud.consoleproxy.vnc.packet.server.CopyRect;
|
||||
import com.cloud.consoleproxy.vnc.packet.server.RawRect;
|
||||
|
|
@ -15,10 +15,10 @@ public class FramebufferUpdatePacket {
|
|||
|
||||
private final VncScreenDescription screen;
|
||||
private final BufferedImageCanvas canvas;
|
||||
private final VncClientListener clientListener;
|
||||
private final FrameBufferEventListener clientListener;
|
||||
|
||||
public FramebufferUpdatePacket(BufferedImageCanvas canvas, VncScreenDescription screen, DataInputStream is,
|
||||
VncClientListener clientListener) throws IOException {
|
||||
FrameBufferEventListener clientListener) throws IOException {
|
||||
|
||||
this.screen = screen;
|
||||
this.canvas = canvas;
|
||||
|
|
|
|||
Loading…
Reference in New Issue