Commit f00b1e37 authored by Joel Martin's avatar Joel Martin

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.
parent 53b112f2
...@@ -82,6 +82,12 @@ load: function(target) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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;
......
...@@ -124,22 +124,26 @@ load: function () { ...@@ -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) { ...@@ -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 () { ...@@ -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() { ...@@ -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) { ...@@ -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 ((oldstate === 'failed') && (state === 'disconnected')) {
// Do disconnect action, but stay in failed state.
RFB.state = 'failed';
} else {
RFB.state = state;
}
switch (state) {
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();
}
}
if ((state === 'disconnected') && (RFB.state !== 'disconnected')) {
RFB.show_timings(); 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 ((RFB.state === 'failed') && if ((oldstate === 'failed') && (state === 'disconnected')) {
((state === 'disconnected') || (state === 'closed'))) {
// 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 () { ...@@ -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 () { ...@@ -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: ...@@ -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: ...@@ -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 () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment