import java.util.*;
import java.awt.image.*;
import java.awt.event.*;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
public class VncInputPlayback implements VncEventListener, ActionListener {
// If fewer than SYNC_PIXEL_MATCH_THRESHOLD pixels in a sync point are
// mismatched, then we say that the image looks good and we can proceed.
public static float SYNC_PIXEL_MATCH_THRESHOLD = 0.12f;
// Give up if we don't sync within SYNC_TIMEOUT_SEC seconds
public static final int SYNC_TIMEOUT_SEC = 600;
// Wait some time (msec) after sync matches
public static final int WAIT_AFTER_SYNC = 1500;
private VncCanvas _vc;
private URLConnection _connection;
private FileReader _fr;
private BufferedReader _br;
private int _brLine;
private javax.swing.Timer _timer;
private FileWriter _auxwr;
private boolean _syncWait;
private long _syncWaitStarted;
private long _syncWaitClear;
private Map<String, String> _syncWaitOp;
private Map<String, String> _nextOp;
private long _lastEventCompletion;
private MouseEvent _lastMouseMotion;
private long _lastMouseMotionSent;
private boolean _autoExit;
private long _logStartTime;
private final static boolean debug = false;
private final static int _extraEventDelay = 0;
private final static int _eventDelayPercent = 100;
//preconditions for UImode
private boolean UIModeBeginStage = false;
private boolean UIModeBeginSync = false;
private boolean UIMode = false;
private boolean UIModeEndSync = false;
private boolean UIModeEndShortcut = false;
private int mode = 0;
private VncViewer _viewer;
private String message;
private boolean processDirective(String key, String value)
{
if(key.equalsIgnoreCase("PRINT"))
System.out.println(value);
return true;
}
public VncInputPlayback(VncCanvas vc, VncViewer v){
_vc = vc;
_viewer = v;
_vc.setVncEventListener(this);
_vc.setState(4);
// Start the recording, so we can sync with its startTime
try {
_vc.viewer.checkRecordingStatus();
} catch (IOException e) {
throw new RuntimeException(e);
}
_logStartTime = System.currentTimeMillis();
if (_vc.viewer.rfb.rec != null)
_logStartTime = _vc.viewer.rfb.rec.getStartTime();
try {
if(_vc.viewer.inAnApplet)
{
URL url = new URL(_vc.viewer.remoteUrl + "index.jsp?tracefile=" + _vc.viewer.sessId);
_connection = url.openConnection();
_connection.setDoInput(true);
_connection.setDoOutput(true);
_connection.setDefaultUseCaches(false);
_connection.setUseCaches(false);
_br = new BufferedReader(new InputStreamReader(_connection.getInputStream()));
_brLine = 0;
String currLine = null;
while((currLine = _br.readLine()) != null)
{
if(currLine.isEmpty())
continue;
if(currLine.equalsIgnoreCase("!ACTUAL_TRACEDATA!"))
break;
if(currLine.charAt(0) == '$')
{
String jspParams[] = new String[2];
jspParams = currLine.split(":", 2);
if(jspParams[0] != null && jspParams[1] != null)
{
jspParams[0] = jspParams[0].substring(1);
jspParams[0] = jspParams[0].trim();
jspParams[1] = jspParams[1].trim();
processDirective(jspParams[0], jspParams[1]);
}
}
}
}
else
{
_fr = new FileReader(new File(_vc.viewer.traceFile));
_br = new BufferedReader(_fr);
_brLine = 0;
_auxwr = new FileWriter(new File(_vc.viewer.recordFile + ".aux"));
}
} catch (IOException e) {
e.printStackTrace();
javax.swing.JOptionPane.showMessageDialog(null, e.getMessage());
}
_lastEventCompletion = System.currentTimeMillis();
_timer = new javax.swing.Timer(20, this);
_timer.start();
_autoExit = false;
_vc.setInputBlock(true);
message = null;
log("VNC input playback starting");
}
public void stop() {
_vc.setVncEventListener(null);
try {
_br.close();
if(!_vc.viewer.inAnApplet)
_auxwr.close();
} catch (Exception e) {
e.printStackTrace();
}
if(_timer.isRunning())
_timer.stop();
System.out.println("VNC input playback stopping on line " + _brLine);
if (_autoExit)
System.exit(0);
}
private void log(String m) {
System.out.println(m);
if(!_vc.viewer.inAnApplet)
{
try {
_auxwr.write(m + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void setAutoExit(boolean v) {
_autoExit = v;
}
private int getInt(Map<String, String> m, String key) {
return Integer.parseInt(m.get(key));
}
//used for timer processing...
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(_timer))
{
long t = System.currentTimeMillis();
if (_syncWait && _lastMouseMotion != null)
{
if (t > _lastMouseMotionSent + 1000) {
log("Trying to wiggle the mouse...");
MouseEvent wiggle =
new MouseEvent(_vc,
_lastMouseMotion.getID(),
t,
_lastMouseMotion.getModifiers(),
_lastMouseMotion.getX() - 1,
_lastMouseMotion.getY(),
0, false,
_lastMouseMotion.getButton());
_vc.processLocalMouseEvent(wiggle, true);
_vc.processLocalMouseEvent(_lastMouseMotion, true);
_lastMouseMotionSent = t;
}
}
if (_syncWait && (_syncWaitStarted > 0) && (t > _syncWaitStarted + SYNC_TIMEOUT_SEC * 1000))
{
log("Timed out waiting for sync after " + (t - _syncWaitStarted) + " msec");
stop();
}
while (!_syncWait && _syncWaitClear < System.currentTimeMillis()
&& mode == 0) {
Map<String, String> m = next();
if (m == null) {
stop();
return;
}
if (mode == 1){
userInteraction(true);
return;
}
long ts = Long.parseLong(m.get("run-at"));
if (ts < System.currentTimeMillis()) {
doEvent(m);
_lastEventCompletion = System.currentTimeMillis();
/* _lastEventCompletion = ts; */
} else {
pushBack(m);
return;
}
}
}
}
//Checks for each line if the preconditions hold
private int conditionHold(Map<String, String> m){
int mo = 0;
if (m == null){
return mo;
}
else if (m.get("type").equals("stage")){
if (m.get("title").equals("UserInteractionBegin")){
UIModeBeginStage = true;
if(m.get("msg") != null)
message = m.get("msg");
mo = 1; //UIMode starts
}
else if(m.get("title").equals("UserInteractionEnd")
&& UIModeEndShortcut && UIModeEndSync){
mo = 4; //UIMode ends
}
}
else if (m.get("type").equals("sync")){
if (UIModeBeginStage && !UIModeEndShortcut){
UIModeBeginSync = true;
UIMode = true;
mo = 2; //now, UImode is running
}
else if(UIModeEndShortcut){
UIModeEndSync = true;
mo = 3;
}
}
//System.out.println("Mode: " + mode);
return mo;
}
private long logTime() {
return System.currentTimeMillis() - _logStartTime;
}
//here, the processing of the log is done
private void doEvent(Map<String, String> m) {
String type = m.get("type");
if (type.equals("sync")) {
_syncWaitOp = m;
checkSync();
} else if (type.equals("java.awt.event.MouseEvent")) {
int id = getInt(m, "id");
int modifiers = getInt(m, "modifiers");
int x = getInt(m, "x");
int y = getInt(m, "y");
int button = getInt(m, "button");
MouseEvent me = new MouseEvent(_vc, id, System.currentTimeMillis(),
modifiers, x, y, 0, false, button);
_vc.processLocalMouseEvent(me, true);
if (button != 0)
log("== sending mouse click event at (" + me.getX()+","+me.getY()+"), " + logTime());
if (id == MouseEvent.MOUSE_DRAGGED ||id == MouseEvent.MOUSE_MOVED) {
_lastMouseMotion = me;
_lastMouseMotionSent = System.currentTimeMillis();
}
} else if (type.equals("java.awt.event.KeyEvent")) {
int id = getInt(m, "id");
int modifiers = getInt(m, "modifiers");
int keyCode = getInt(m, "keycode");
char keyChar = (char) getInt(m, "keychar");
KeyEvent ke = new KeyEvent(_vc, id, System.currentTimeMillis(),
modifiers, keyCode, keyChar);
_vc.processLocalKeyEvent(ke);
log("== sending keyboard event at " + logTime());
}
else if (type.equals("stage")){
//stages are not unknown but must be caught befor this method.
}
else {
log("unknown type of event: " + type);
}
}
//helper method used by checkSync()
private boolean syncOk() {
BufferedImage im = _vc.getBufferedImage();
int x0 = getInt(_syncWaitOp, "x0");
int x1 = getInt(_syncWaitOp, "x1");
int y0 = getInt(_syncWaitOp, "y0");
int y1 = getInt(_syncWaitOp, "y1");
//log("Checking sync...");
Map<String, String> newMap = new HashMap<String, String>();
newMap.put("x0", "" + x0);
newMap.put("x1", "" + x1);
newMap.put("y0", "" + y0);
newMap.put("y1", "" + y1);
int mismatchCount = 0;
int pixelCount = (x1 - x0) * (y1 - y0);
Float maxMismatch = SYNC_PIXEL_MATCH_THRESHOLD*pixelCount;
for (int x = x0; x < x1; x++) {
for (int y = y0; y < y1; y++) {
int syncPixel = getInt(_syncWaitOp, "px" + x + "y" + y);
int realPixel = im.getRGB(x, y);
newMap.put("px" + x + "y" + y, "" + realPixel);
if (syncPixel != realPixel) {
mismatchCount++;
}
}
}
log("Sync mismatches: " + mismatchCount + "/" + pixelCount +
", threshold " + maxMismatch.intValue());
if (debug && mismatchCount >= maxMismatch) {
log("Original image:");
printMap(_syncWaitOp);
log("Current image:");
printMap(newMap);
}
//if (mismatchCount < maxMismatch)
_vc.drawSyncArea(new java.awt.Rectangle(x0, y0, x1-x0, y1-y0));
return (mismatchCount < maxMismatch);
}
private void checkSync() {
_syncWait = true;
_syncWaitStarted = 0;
_syncWaitClear = 0;
log("Waiting for sync... at " + logTime());
if (_syncWait && syncOk()) {
long syncWaitTime = 0;
if (_syncWaitStarted > 0) {
syncWaitTime = System.currentTimeMillis() - _syncWaitStarted;
_syncWaitClear = System.currentTimeMillis() + WAIT_AFTER_SYNC;
}
log("Sync ok after " + syncWaitTime + " msec");
log("Sync ok at " + logTime());
_syncWait = false;
_lastEventCompletion = System.currentTimeMillis();
}
_syncWaitStarted = System.currentTimeMillis();
}
private void printMap(Map<String, String> m) {
String s = "0";
for (String k: m.keySet())
s += " " + k + "=" + m.get(k);
log(s);
}
public void screenEvent(int x, int y, int w, int h) {
checkSync();
}
public void mouseEvent(MouseEvent e) {
}
public void keyEvent(KeyEvent e) {
}
//returns next Operation to execute
private Map<String, String> next() {
Map<String, String> m;
if (_nextOp != null)
m = _nextOp;
else
m = readNext();
_nextOp = null;
mode = conditionHold(m);
return m;
}
private void pushBack(Map<String, String> m) {
if (_nextOp != null)
throw new RuntimeException("pushback: nextop is not null");
_nextOp = m;
}
private Map<String, String> readNext() {
Map<String, String> m = new HashMap<String, String>();
String in;
try {
in = _br.readLine();
_brLine++;
} catch (IOException e) {
e.printStackTrace();
return null;
}
if (in == null || in.trim().isEmpty())
return null;
StringTokenizer st = new StringTokenizer(in);
String delay = st.nextToken();
if(delay.equals("9999")){
delay = Long.toString(System.currentTimeMillis() - _lastEventCompletion);
System.out.println(delay);
}
m.put("run-at", "" + (_lastEventCompletion + _extraEventDelay
+ _eventDelayPercent * Integer.parseInt(delay) / 100));
while (st.hasMoreTokens()) {
String t = st.nextToken();
StringTokenizer st2 = new StringTokenizer(t, "=");
String key = st2.nextToken();
String value = st2.nextToken();
//modification to deal with values which contain spaces
if(value.contains("\"")){
String tmp = st.nextToken();
while(!tmp.contains("\"")){
value += " "+tmp;
tmp = st.nextToken();
}
value += " "+tmp;
value = value.replaceAll("\"", "");
value.trim();
}
m.put(key, value);
}
return m;
}
//Stitches to mode 5 and back to 4, does the syncPoint checks.
public void userInteraction(boolean enable){
Map<String, String> m = next();
if(enable && mode == 2){
_syncWaitOp = m;
checkSync();
_vc.saveMousePos();
if(message != null)
_viewer.msgPanel.showMessage(message);
_viewer.pack();
_vc.setInputBlock(false);
_vc.setState(5);
_syncWait = false;
UIMode = true;
UIModeBeginStage = false;
log("UserInteractionMode Begin");
}
else if(!enable && mode == 3){
UIModeEndShortcut = true;
//end UI mode
_syncWaitOp = m;
checkSync();
m = next();
if(mode == 4){
_vc.setState(4);
_vc.correctMousePos();
_vc.setInputBlock(true);
message=null;
_viewer.msgPanel.removeMessage();
_viewer.pack();
UIMode = false;
UIModeBeginStage = false;
UIModeBeginSync = false;
UIModeEndShortcut = false;
UIModeEndSync = false;
log("UserInteractionMode End");
}
}
}
}