State machine refactoring.
Add new states 'loaded', 'connect' and 'fatal': - Loaded state is first page state. Pass WebSockets mode message using this state. - Connect indicates that the user has issued a "connect" but we haven't gotten an WebSockets onopen yet. - Fatal is a condition that indicates inability to continue on: right now, lack of WebSockets/Flash or non-working canvas. Move much of the actual state transition code into updateState. Handle 'password' state better in default_controls.js; instead of disconnecting, prompt for password to send. Add comments to updateState indicating possible states.
This commit is contained in:
parent
53b112f2a6
commit
f00b1e3776
|
@ -82,6 +82,12 @@ load: function(target) {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setPassword: function() {
|
||||||
|
console.log("setPassword");
|
||||||
|
RFB.sendPassword($('VNC_password').value);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
sendCtrlAltDel: function() {
|
sendCtrlAltDel: function() {
|
||||||
RFB.sendCtrlAltDel();
|
RFB.sendCtrlAltDel();
|
||||||
},
|
},
|
||||||
|
@ -94,6 +100,7 @@ updateState: function(state, msg) {
|
||||||
cad = $('sendCtrlAltDelButton');
|
cad = $('sendCtrlAltDelButton');
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'failed':
|
case 'failed':
|
||||||
|
case 'fatal':
|
||||||
c.disabled = true;
|
c.disabled = true;
|
||||||
cad.disabled = true;
|
cad.disabled = true;
|
||||||
klass = "VNC_status_error";
|
klass = "VNC_status_error";
|
||||||
|
@ -106,6 +113,7 @@ updateState: function(state, msg) {
|
||||||
klass = "VNC_status_normal";
|
klass = "VNC_status_normal";
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
|
case 'loaded':
|
||||||
c.value = "Connect";
|
c.value = "Connect";
|
||||||
c.onclick = DefaultControls.connect;
|
c.onclick = DefaultControls.connect;
|
||||||
|
|
||||||
|
@ -113,6 +121,14 @@ updateState: function(state, msg) {
|
||||||
cad.disabled = true;
|
cad.disabled = true;
|
||||||
klass = "VNC_status_normal";
|
klass = "VNC_status_normal";
|
||||||
break;
|
break;
|
||||||
|
case 'password':
|
||||||
|
c.value = "Send Password";
|
||||||
|
c.onclick = DefaultControls.setPassword;
|
||||||
|
|
||||||
|
c.disabled = false;
|
||||||
|
cad.disabled = true;
|
||||||
|
klass = "VNC_status_warn";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
c.disabled = true;
|
c.disabled = true;
|
||||||
cad.disabled = true;
|
cad.disabled = true;
|
||||||
|
|
205
include/vnc.js
205
include/vnc.js
|
@ -124,22 +124,26 @@ load: function () {
|
||||||
/* Load web-socket-js if no builtin WebSocket support */
|
/* Load web-socket-js if no builtin WebSocket support */
|
||||||
if (VNC_native_ws) {
|
if (VNC_native_ws) {
|
||||||
Util.Info("Using native WebSockets");
|
Util.Info("Using native WebSockets");
|
||||||
RFB.updateState('disconnected', 'Disconnected');
|
RFB.updateState('loaded', 'noVNC ready (using native WebSockets)');
|
||||||
} else {
|
} else {
|
||||||
Util.Warn("Using web-socket-js flash bridge");
|
Util.Warn("Using web-socket-js flash bridge");
|
||||||
if ((! Util.Flash) ||
|
if ((! Util.Flash) ||
|
||||||
(Util.Flash.version < 9)) {
|
(Util.Flash.version < 9)) {
|
||||||
RFB.updateState('failed', "WebSockets or Adobe Flash is required");
|
RFB.updateState('fatal', "WebSockets or Adobe Flash is required");
|
||||||
} else if (document.location.href.substr(0, 7) === "file://") {
|
} else if (document.location.href.substr(0, 7) === "file://") {
|
||||||
RFB.updateState('failed',
|
RFB.updateState('fatal',
|
||||||
"'file://' URL is incompatible with Adobe Flash");
|
"'file://' URL is incompatible with Adobe Flash");
|
||||||
} else {
|
} else {
|
||||||
RFB.updateState('disconnected', 'Disconnected');
|
RFB.updateState('loaded', 'noVNC ready (using Flash WebSockets emulation)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize canvas/fxcanvas
|
// Initialize canvas/fxcanvas
|
||||||
Canvas.init(RFB.canvasID);
|
try {
|
||||||
|
Canvas.init(RFB.canvasID);
|
||||||
|
} catch (exc) {
|
||||||
|
RFB.updateState('fatal', "No working Canvas");
|
||||||
|
}
|
||||||
|
|
||||||
// Populate encoding lookup tables
|
// Populate encoding lookup tables
|
||||||
RFB.encHandlers = {};
|
RFB.encHandlers = {};
|
||||||
|
@ -171,36 +175,17 @@ connect: function (host, port, password, encrypt, true_color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!RFB.host) || (!RFB.port)) {
|
if ((!RFB.host) || (!RFB.port)) {
|
||||||
RFB.updateState('disconnected', "Must set host and port");
|
RFB.updateState('failed', "Must set host and port");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RFB.init_vars();
|
RFB.updateState('connect');
|
||||||
|
|
||||||
if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) {
|
|
||||||
RFB.ws.close();
|
|
||||||
}
|
|
||||||
RFB.init_ws();
|
|
||||||
|
|
||||||
RFB.updateState('ProtocolVersion');
|
|
||||||
//Util.Debug("<< connect");
|
//Util.Debug("<< connect");
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
disconnect: function () {
|
disconnect: function () {
|
||||||
//Util.Debug(">> disconnect");
|
//Util.Debug(">> disconnect");
|
||||||
if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) {
|
|
||||||
RFB.ws.close();
|
|
||||||
RFB.updateState('closed');
|
|
||||||
RFB.ws.onmessage = function (e) { return; };
|
|
||||||
}
|
|
||||||
if (Canvas.ctx) {
|
|
||||||
Canvas.stop();
|
|
||||||
if (! /__debug__$/i.test(document.location.href)) {
|
|
||||||
Canvas.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RFB.updateState('disconnected', 'Disconnected');
|
RFB.updateState('disconnected', 'Disconnected');
|
||||||
//Util.Debug("<< disconnect");
|
//Util.Debug("<< disconnect");
|
||||||
},
|
},
|
||||||
|
@ -327,6 +312,21 @@ init_msg: function () {
|
||||||
RFB.version = RFB.max_version;
|
RFB.version = RFB.max_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RFB.sendID = setInterval(function() {
|
||||||
|
/*
|
||||||
|
* Send updates either at a rate of one update every 50ms,
|
||||||
|
* or whatever slower rate the network can handle
|
||||||
|
*/
|
||||||
|
if (RFB.ws.bufferedAmount === 0) {
|
||||||
|
if (RFB.SQ) {
|
||||||
|
RFB.ws.send(RFB.SQ);
|
||||||
|
RFB.SQ = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Util.Debug("Delaying send");
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
|
||||||
cversion = "00" + parseInt(RFB.version,10) +
|
cversion = "00" + parseInt(RFB.version,10) +
|
||||||
".00" + ((RFB.version * 10) % 10);
|
".00" + ((RFB.version * 10) % 10);
|
||||||
RFB.send_string("RFB " + cversion + "\n");
|
RFB.send_string("RFB " + cversion + "\n");
|
||||||
|
@ -782,7 +782,7 @@ display_hextile: function() {
|
||||||
if (subencoding > 30) { // Raw
|
if (subencoding > 30) { // Raw
|
||||||
RFB.updateState('failed',
|
RFB.updateState('failed',
|
||||||
"Disconnected: illegal hextile subencoding " + subencoding);
|
"Disconnected: illegal hextile subencoding " + subencoding);
|
||||||
Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30));
|
//Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
subrects = 0;
|
subrects = 0;
|
||||||
|
@ -1314,27 +1314,134 @@ externalUpdateState: function(state, msg) {
|
||||||
// Stub
|
// Stub
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Running states:
|
||||||
|
* disconnected - idle state
|
||||||
|
* normal - connected
|
||||||
|
*
|
||||||
|
* Page states:
|
||||||
|
* loaded - page load, equivalent to disconnected
|
||||||
|
* connect - starting initialization
|
||||||
|
* password - waiting for password
|
||||||
|
* failed - abnormal transition to disconnected
|
||||||
|
* fatal - failed to load page, or fatal error
|
||||||
|
*
|
||||||
|
* VNC initialization states:
|
||||||
|
* ProtocolVersion
|
||||||
|
* Security
|
||||||
|
* Authentication
|
||||||
|
* SecurityResult
|
||||||
|
* ServerInitialization
|
||||||
|
*/
|
||||||
updateState: function(state, statusMsg) {
|
updateState: function(state, statusMsg) {
|
||||||
var func, cmsg;
|
var func, cmsg, oldstate = RFB.state;
|
||||||
if (state === 'failed') {
|
if (state === oldstate) {
|
||||||
|
/* Already here, ignore */
|
||||||
|
Util.Debug("Already in state '" + state + "', ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldstate === 'fatal') {
|
||||||
|
Util.Error("Fatal error, cannot continue");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((state === 'failed') || (state === 'fatal')) {
|
||||||
func = Util.Error;
|
func = Util.Error;
|
||||||
} else {
|
} else {
|
||||||
func = Util.Warn;
|
func = Util.Warn;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
|
cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
|
||||||
func("New state '" + state + "'." + cmsg);
|
func("New state '" + state + "', was '" + oldstate + "'." + cmsg);
|
||||||
|
|
||||||
if ((state === 'disconnected') && (RFB.state !== 'disconnected')) {
|
if ((oldstate === 'failed') && (state === 'disconnected')) {
|
||||||
RFB.show_timings();
|
// Do disconnect action, but stay in failed state.
|
||||||
|
RFB.state = 'failed';
|
||||||
|
} else {
|
||||||
|
RFB.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((RFB.state === 'failed') &&
|
switch (state) {
|
||||||
((state === 'disconnected') || (state === 'closed'))) {
|
case 'loaded':
|
||||||
|
case 'disconnected':
|
||||||
|
|
||||||
|
if (RFB.sendID) {
|
||||||
|
clearInterval(RFB.sendID);
|
||||||
|
RFB.sendID = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RFB.ws) {
|
||||||
|
if (RFB.ws.readyState === WebSocket.OPEN) {
|
||||||
|
RFB.ws.close();
|
||||||
|
}
|
||||||
|
RFB.ws.onmessage = function (e) { return; };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Canvas.ctx) {
|
||||||
|
Canvas.stop();
|
||||||
|
if (! /__debug__$/i.test(document.location.href)) {
|
||||||
|
Canvas.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RFB.show_timings();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'connect':
|
||||||
|
RFB.init_vars();
|
||||||
|
|
||||||
|
if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) {
|
||||||
|
RFB.ws.close();
|
||||||
|
}
|
||||||
|
RFB.init_ws(); // onopen transitions to 'ProtocolVersion'
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'password':
|
||||||
|
// Ignore password state by default
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'normal':
|
||||||
|
if ((oldstate === 'disconnected') || (oldstate === 'failed')) {
|
||||||
|
Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'failed':
|
||||||
|
if (oldstate === 'disconnected') {
|
||||||
|
Util.Error("Invalid transition from 'disconnected' to 'failed'");
|
||||||
|
}
|
||||||
|
if (oldstate === 'normal') {
|
||||||
|
Util.Error("Error while connected.");
|
||||||
|
}
|
||||||
|
if (oldstate === 'init') {
|
||||||
|
Util.Error("Error while initializing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) {
|
||||||
|
RFB.ws.close();
|
||||||
|
}
|
||||||
|
// Make sure we transition to disconnected
|
||||||
|
setTimeout(function() { RFB.updateState('disconnected'); }, 50);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Invalid state transition
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((oldstate === 'failed') && (state === 'disconnected')) {
|
||||||
// Leave the failed message
|
// Leave the failed message
|
||||||
RFB.externalUpdateState(state);
|
RFB.externalUpdateState(state);
|
||||||
} else {
|
} else {
|
||||||
RFB.state = state;
|
|
||||||
RFB.externalUpdateState(state, statusMsg);
|
RFB.externalUpdateState(state, statusMsg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1418,27 +1525,20 @@ init_ws: function () {
|
||||||
RFB.ws.onmessage = RFB.recv_message;
|
RFB.ws.onmessage = RFB.recv_message;
|
||||||
RFB.ws.onopen = function(e) {
|
RFB.ws.onopen = function(e) {
|
||||||
Util.Debug(">> WebSocket.onopen");
|
Util.Debug(">> WebSocket.onopen");
|
||||||
RFB.updateState('ProtocolVersion', "Starting VNC handshake");
|
if (RFB.state === "connect") {
|
||||||
RFB.sendID = setInterval(function() {
|
RFB.updateState('ProtocolVersion', "Starting VNC handshake");
|
||||||
/*
|
} else {
|
||||||
* Send updates either at a rate of one update every 50ms,
|
RFB.updateState('failed', "Got unexpected WebSockets connection");
|
||||||
* or whatever slower rate the network can handle
|
}
|
||||||
*/
|
|
||||||
if (RFB.ws.bufferedAmount === 0) {
|
|
||||||
if (RFB.SQ) {
|
|
||||||
RFB.ws.send(RFB.SQ);
|
|
||||||
RFB.SQ = "";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Util.Debug("Delaying send");
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
Util.Debug("<< WebSocket.onopen");
|
Util.Debug("<< WebSocket.onopen");
|
||||||
};
|
};
|
||||||
RFB.ws.onclose = function(e) {
|
RFB.ws.onclose = function(e) {
|
||||||
Util.Debug(">> WebSocket.onclose");
|
Util.Debug(">> WebSocket.onclose");
|
||||||
clearInterval(RFB.sendID);
|
if (RFB.state === 'normal') {
|
||||||
RFB.updateState('disconnected', 'VNC disconnected');
|
RFB.updateState('failed', 'Server disconnected');
|
||||||
|
} else {
|
||||||
|
RFB.updateState('disconnected', 'VNC disconnected');
|
||||||
|
}
|
||||||
Util.Debug("<< WebSocket.onclose");
|
Util.Debug("<< WebSocket.onclose");
|
||||||
};
|
};
|
||||||
RFB.ws.onerror = function(e) {
|
RFB.ws.onerror = function(e) {
|
||||||
|
@ -1450,7 +1550,6 @@ init_ws: function () {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
if (RFB.ws.readyState === WebSocket.CONNECTING) {
|
if (RFB.ws.readyState === WebSocket.CONNECTING) {
|
||||||
RFB.updateState('failed', "Connect timeout");
|
RFB.updateState('failed', "Connect timeout");
|
||||||
RFB.ws.close();
|
|
||||||
}
|
}
|
||||||
}, RFB.connectTimeout);
|
}, RFB.connectTimeout);
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,26 @@ Connect parameters are provided in query string:
|
||||||
sb = $('VNC_status_bar');
|
sb = $('VNC_status_bar');
|
||||||
cad = $('sendCtrlAltDelButton');
|
cad = $('sendCtrlAltDelButton');
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'failed': klass = "VNC_status_error"; break;
|
case 'failed':
|
||||||
case 'normal': klass = "VNC_status_normal"; break;
|
case 'fatal':
|
||||||
case 'disconnected': klass = "VNC_status_normal"; break;
|
klass = "VNC_status_error";
|
||||||
default: klass = "VNC_status_warn"; break;
|
break;
|
||||||
|
case 'normal':
|
||||||
|
klass = "VNC_status_normal";
|
||||||
|
break;
|
||||||
|
case 'disconnected':
|
||||||
|
case 'loaded':
|
||||||
|
klass = "VNC_status_normal";
|
||||||
|
break;
|
||||||
|
case 'password':
|
||||||
|
msg = '<form onsubmit="return setPassword();"';
|
||||||
|
msg += ' style="margin-bottom: 0px">';
|
||||||
|
msg += 'Password Required: ';
|
||||||
|
msg += '<input type=password size=10 id="password_input" class="VNC_status">';
|
||||||
|
msg += '</form>';
|
||||||
|
// Fall through
|
||||||
|
default:
|
||||||
|
klass = "VNC_status_warn";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state === "normal") { cad.disabled = false; }
|
if (state === "normal") { cad.disabled = false; }
|
||||||
|
@ -59,14 +75,6 @@ Connect parameters are provided in query string:
|
||||||
sb.setAttribute("class", klass);
|
sb.setAttribute("class", klass);
|
||||||
s.innerHTML = msg;
|
s.innerHTML = msg;
|
||||||
}
|
}
|
||||||
if (state === 'password') {
|
|
||||||
html = '<form onsubmit="return setPassword();"';
|
|
||||||
html += ' style="margin-bottom: 0px">';
|
|
||||||
html += 'Password Required: ';
|
|
||||||
html += '<input type=password size=10 id="password_input" class="VNC_status">';
|
|
||||||
html += '</form>';
|
|
||||||
s.innerHTML = html;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
|
|
Loading…
Reference in New Issue