0) {
- if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); }
- while(i >= 0) {
- if(p < k) {
- d = (this[i]&((1<>(p+=this.DB-k);
- }
- else {
- d = (this[i]>>(p-=k))&km;
- if(p <= 0) { p += this.DB; --i; }
- }
- if(d > 0) m = true;
- if(m) r += int2char(d);
- }
- }
- return m?r:"0";
-}
-
-// (public) -this
-function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
-
-// (public) |this|
-function bnAbs() { return (this.s<0)?this.negate():this; }
-
-// (public) return + if this > a, - if this < a, 0 if equal
-function bnCompareTo(a) {
- var r = this.s-a.s;
- if(r != 0) return r;
- var i = this.t;
- r = i-a.t;
- if(r != 0) return r;
- while(--i >= 0) if((r=this[i]-a[i]) != 0) return r;
- return 0;
-}
-
-// returns bit length of the integer x
-function nbits(x) {
- var r = 1, t;
- if((t=x>>>16) != 0) { x = t; r += 16; }
- if((t=x>>8) != 0) { x = t; r += 8; }
- if((t=x>>4) != 0) { x = t; r += 4; }
- if((t=x>>2) != 0) { x = t; r += 2; }
- if((t=x>>1) != 0) { x = t; r += 1; }
- return r;
-}
-
-// (public) return the number of bits in "this"
-function bnBitLength() {
- if(this.t <= 0) return 0;
- return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM));
-}
-
-// (protected) r = this << n*DB
-function bnpDLShiftTo(n,r) {
- var i;
- for(i = this.t-1; i >= 0; --i) r[i+n] = this[i];
- for(i = n-1; i >= 0; --i) r[i] = 0;
- r.t = this.t+n;
- r.s = this.s;
-}
-
-// (protected) r = this >> n*DB
-function bnpDRShiftTo(n,r) {
- for(var i = n; i < this.t; ++i) r[i-n] = this[i];
- r.t = Math.max(this.t-n,0);
- r.s = this.s;
-}
-
-// (protected) r = this << n
-function bnpLShiftTo(n,r) {
- var bs = n%this.DB;
- var cbs = this.DB-bs;
- var bm = (1<= 0; --i) {
- r[i+ds+1] = (this[i]>>cbs)|c;
- c = (this[i]&bm)<= 0; --i) r[i] = 0;
- r[ds] = c;
- r.t = this.t+ds+1;
- r.s = this.s;
- r.clamp();
-}
-
-// (protected) r = this >> n
-function bnpRShiftTo(n,r) {
- r.s = this.s;
- var ds = Math.floor(n/this.DB);
- if(ds >= this.t) { r.t = 0; return; }
- var bs = n%this.DB;
- var cbs = this.DB-bs;
- var bm = (1<>bs;
- for(var i = ds+1; i < this.t; ++i) {
- r[i-ds-1] |= (this[i]&bm)<>bs;
- }
- if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB;
- }
- if(a.t < this.t) {
- c -= a.s;
- while(i < this.t) {
- c += this[i];
- r[i++] = c&this.DM;
- c >>= this.DB;
- }
- c += this.s;
- }
- else {
- c += this.s;
- while(i < a.t) {
- c -= a[i];
- r[i++] = c&this.DM;
- c >>= this.DB;
- }
- c -= a.s;
- }
- r.s = (c<0)?-1:0;
- if(c < -1) r[i++] = this.DV+c;
- else if(c > 0) r[i++] = c;
- r.t = i;
- r.clamp();
-}
-
-// (protected) r = this * a, r != this,a (HAC 14.12)
-// "this" should be the larger one if appropriate.
-function bnpMultiplyTo(a,r) {
- var x = this.abs(), y = a.abs();
- var i = x.t;
- r.t = i+y.t;
- while(--i >= 0) r[i] = 0;
- for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t);
- r.s = 0;
- r.clamp();
- if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
-}
-
-// (protected) r = this^2, r != this (HAC 14.16)
-function bnpSquareTo(r) {
- var x = this.abs();
- var i = r.t = 2*x.t;
- while(--i >= 0) r[i] = 0;
- for(i = 0; i < x.t-1; ++i) {
- var c = x.am(i,x[i],r,2*i,0,1);
- if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
- r[i+x.t] -= x.DV;
- r[i+x.t+1] = 1;
- }
- }
- if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1);
- r.s = 0;
- r.clamp();
-}
-
-// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
-// r != q, this != m. q or r may be null.
-function bnpDivRemTo(m,q,r) {
- var pm = m.abs();
- if(pm.t <= 0) return;
- var pt = this.abs();
- if(pt.t < pm.t) {
- if(q != null) q.fromInt(0);
- if(r != null) this.copyTo(r);
- return;
- }
- if(r == null) r = nbi();
- var y = nbi(), ts = this.s, ms = m.s;
- var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus
- if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); }
- else { pm.copyTo(y); pt.copyTo(r); }
- var ys = y.t;
- var y0 = y[ys-1];
- if(y0 == 0) return;
- var yt = y0*(1<1)?y[ys-2]>>this.F2:0);
- var d1 = this.FV/yt, d2 = (1<= 0) {
- r[r.t++] = 1;
- r.subTo(t,r);
- }
- BigInteger.ONE.dlShiftTo(ys,t);
- t.subTo(y,y); // "negative" y so we can replace sub with am later
- while(y.t < ys) y[y.t++] = 0;
- while(--j >= 0) {
- // Estimate quotient digit
- var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2);
- if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out
- y.dlShiftTo(j,t);
- r.subTo(t,r);
- while(r[i] < --qd) r.subTo(t,r);
- }
- }
- if(q != null) {
- r.drShiftTo(ys,q);
- if(ts != ms) BigInteger.ZERO.subTo(q,q);
- }
- r.t = ys;
- r.clamp();
- if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder
- if(ts < 0) BigInteger.ZERO.subTo(r,r);
-}
-
-// (public) this mod a
-function bnMod(a) {
- var r = nbi();
- this.abs().divRemTo(a,null,r);
- if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
- return r;
-}
-
-// Modular reduction using "classic" algorithm
-function Classic(m) { this.m = m; }
-function cConvert(x) {
- if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
- else return x;
-}
-function cRevert(x) { return x; }
-function cReduce(x) { x.divRemTo(this.m,null,x); }
-function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
-function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
-
-Classic.prototype.convert = cConvert;
-Classic.prototype.revert = cRevert;
-Classic.prototype.reduce = cReduce;
-Classic.prototype.mulTo = cMulTo;
-Classic.prototype.sqrTo = cSqrTo;
-
-// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
-// justification:
-// xy == 1 (mod m)
-// xy = 1+km
-// xy(2-xy) = (1+km)(1-km)
-// x[y(2-xy)] = 1-k^2m^2
-// x[y(2-xy)] == 1 (mod m^2)
-// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
-// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
-// JS multiply "overflows" differently from C/C++, so care is needed here.
-function bnpInvDigit() {
- if(this.t < 1) return 0;
- var x = this[0];
- if((x&1) == 0) return 0;
- var y = x&3; // y == 1/x mod 2^2
- y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
- y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
- y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
- // last step - calculate inverse mod DV directly;
- // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
- y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits
- // we really want the negative inverse, and -DV < y < DV
- return (y>0)?this.DV-y:-y;
-}
-
-// Montgomery reduction
-function Montgomery(m) {
- this.m = m;
- this.mp = m.invDigit();
- this.mpl = this.mp&0x7fff;
- this.mph = this.mp>>15;
- this.um = (1<<(m.DB-15))-1;
- this.mt2 = 2*m.t;
-}
-
-// xR mod m
-function montConvert(x) {
- var r = nbi();
- x.abs().dlShiftTo(this.m.t,r);
- r.divRemTo(this.m,null,r);
- if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
- return r;
-}
-
-// x/R mod m
-function montRevert(x) {
- var r = nbi();
- x.copyTo(r);
- this.reduce(r);
- return r;
-}
-
-// x = x/R mod m (HAC 14.32)
-function montReduce(x) {
- while(x.t <= this.mt2) // pad x so am has enough room later
- x[x.t++] = 0;
- for(var i = 0; i < this.m.t; ++i) {
- // faster way of calculating u0 = x[i]*mp mod DV
- var j = x[i]&0x7fff;
- var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
- // use am to combine the multiply-shift-add into one call
- j = i+this.m.t;
- x[j] += this.m.am(0,u0,x,i,0,this.m.t);
- // propagate carry
- while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; }
- }
- x.clamp();
- x.drShiftTo(this.m.t,x);
- if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
-}
-
-// r = "x^2/R mod m"; x != r
-function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
-
-// r = "xy/R mod m"; x,y != r
-function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
-
-Montgomery.prototype.convert = montConvert;
-Montgomery.prototype.revert = montRevert;
-Montgomery.prototype.reduce = montReduce;
-Montgomery.prototype.mulTo = montMulTo;
-Montgomery.prototype.sqrTo = montSqrTo;
-
-// (protected) true iff this is even
-function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; }
-
-// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
-function bnpExp(e,z) {
- if(e > 0xffffffff || e < 1) return BigInteger.ONE;
- var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
- g.copyTo(r);
- while(--i >= 0) {
- z.sqrTo(r,r2);
- if((e&(1< 0) z.mulTo(r2,g,r);
- else { var t = r; r = r2; r2 = t; }
- }
- return z.revert(r);
-}
-
-// (public) this^e % m, 0 <= e < 2^32
-function bnModPowInt(e,m) {
- var z;
- if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
- return this.exp(e,z);
-}
-
-// protected
-BigInteger.prototype.copyTo = bnpCopyTo;
-BigInteger.prototype.fromInt = bnpFromInt;
-BigInteger.prototype.fromString = bnpFromString;
-BigInteger.prototype.clamp = bnpClamp;
-BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
-BigInteger.prototype.drShiftTo = bnpDRShiftTo;
-BigInteger.prototype.lShiftTo = bnpLShiftTo;
-BigInteger.prototype.rShiftTo = bnpRShiftTo;
-BigInteger.prototype.subTo = bnpSubTo;
-BigInteger.prototype.multiplyTo = bnpMultiplyTo;
-BigInteger.prototype.squareTo = bnpSquareTo;
-BigInteger.prototype.divRemTo = bnpDivRemTo;
-BigInteger.prototype.invDigit = bnpInvDigit;
-BigInteger.prototype.isEven = bnpIsEven;
-BigInteger.prototype.exp = bnpExp;
-
-// public
-BigInteger.prototype.toString = bnToString;
-BigInteger.prototype.negate = bnNegate;
-BigInteger.prototype.abs = bnAbs;
-BigInteger.prototype.compareTo = bnCompareTo;
-BigInteger.prototype.bitLength = bnBitLength;
-BigInteger.prototype.mod = bnMod;
-BigInteger.prototype.modPowInt = bnModPowInt;
-
-// "constants"
-BigInteger.ZERO = nbv(0);
-BigInteger.ONE = nbv(1);
diff --git a/static/js/spice-html5/lz.js b/static/js/spice-html5/lz.js
old mode 100644
new mode 100755
index 4292eac..53c1141
--- a/static/js/spice-html5/lz.js
+++ b/static/js/spice-html5/lz.js
@@ -141,6 +141,19 @@ function lz_rgb32_decompress(in_buf, at, out_buf, type, default_alpha)
return encoder - 1;
}
+function flip_image_data(img)
+{
+ var wb = img.width * 4;
+ var h = img.height;
+ var temp_h = h;
+ var buff = new Uint8Array(img.width * img.height * 4);
+ while (temp_h--)
+ {
+ buff.set(img.data.subarray(temp_h * wb, (temp_h + 1) * wb), (h - temp_h - 1) * wb);
+ }
+ img.data.set(buff);
+}
+
function convert_spice_lz_to_web(context, lz_image)
{
var at;
@@ -150,6 +163,9 @@ function convert_spice_lz_to_web(context, lz_image)
var ret = context.createImageData(lz_image.width, lz_image.height);
at = lz_rgb32_decompress(u8, 0, ret.data, LZ_IMAGE_TYPE_RGB32, lz_image.type != LZ_IMAGE_TYPE_RGBA);
+ if (!lz_image.top_down)
+ flip_image_data(ret);
+
if (lz_image.type == LZ_IMAGE_TYPE_RGBA)
lz_rgb32_decompress(u8, at, ret.data, LZ_IMAGE_TYPE_RGBA, false);
}
diff --git a/static/js/spice-html5/main.js b/static/js/spice-html5/main.js
old mode 100644
new mode 100755
index bfc102a..173ff97
--- a/static/js/spice-html5/main.js
+++ b/static/js/spice-html5/main.js
@@ -22,7 +22,7 @@
** SpiceMainConn
** This is the master Javascript class for establishing and
** managing a connection to a Spice Server.
-**
+**
** Invocation: You must pass an object with properties as follows:
** uri (required) Uri of a WebSocket listener that is
** connected to a spice server.
@@ -59,11 +59,24 @@ function SpiceMainConn()
this.file_xfer_tasks = {};
this.file_xfer_task_id = 0;
this.file_xfer_read_queue = [];
+ this.ports = [];
}
SpiceMainConn.prototype = Object.create(SpiceConn.prototype);
SpiceMainConn.prototype.process_channel_message = function(msg)
{
+ if (msg.type == SPICE_MSG_MAIN_MIGRATE_BEGIN)
+ {
+ this.known_unimplemented(msg.type, "Main Migrate Begin");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_MIGRATE_CANCEL)
+ {
+ this.known_unimplemented(msg.type, "Main Migrate Cancel");
+ return true;
+ }
+
if (msg.type == SPICE_MSG_MAIN_INIT)
{
this.log_info("Connected to " + this.ws.url);
@@ -86,6 +99,9 @@ SpiceMainConn.prototype.process_channel_message = function(msg)
" ; ram_hint " + this.main_init.ram_hint);
}
+ this.our_mm_time = Date.now();
+ this.mm_time = this.main_init.multi_media_time;
+
this.handle_mouse_mode(this.main_init.current_mouse_mode,
this.main_init.supported_mouse_modes);
@@ -107,6 +123,12 @@ SpiceMainConn.prototype.process_channel_message = function(msg)
return true;
}
+ if (msg.type == SPICE_MSG_MAIN_MULTI_MEDIA_TIME)
+ {
+ this.known_unimplemented(msg.type, "Main Multi Media Time");
+ return true;
+ }
+
if (msg.type == SPICE_MSG_MAIN_CHANNELS_LIST)
{
var i;
@@ -123,7 +145,12 @@ SpiceMainConn.prototype.process_channel_message = function(msg)
chan_id : chans.channels[i].id
};
if (chans.channels[i].type == SPICE_CHANNEL_DISPLAY)
- this.display = new SpiceDisplayConn(conn);
+ {
+ if (this.display !== undefined)
+ this.log_warn("The spice-html5 client does not handle multiple heads.");
+ else
+ this.display = new SpiceDisplayConn(conn);
+ }
else if (chans.channels[i].type == SPICE_CHANNEL_INPUTS)
{
this.inputs = new SpiceInputsConn(conn);
@@ -133,12 +160,14 @@ SpiceMainConn.prototype.process_channel_message = function(msg)
this.cursor = new SpiceCursorConn(conn);
else if (chans.channels[i].type == SPICE_CHANNEL_PLAYBACK)
this.cursor = new SpicePlaybackConn(conn);
+ else if (chans.channels[i].type == SPICE_CHANNEL_PORT)
+ this.ports.push(new SpicePortConn(conn));
else
{
- this.log_err("Channel type " + chans.channels[i].type + " unknown.");
if (! ("extra_channels" in this))
this.extra_channels = [];
this.extra_channels[i] = new SpiceConn(conn);
+ this.log_err("Channel type " + this.extra_channels[i].channel_type() + " not implemented");
}
}
@@ -201,6 +230,48 @@ SpiceMainConn.prototype.process_channel_message = function(msg)
return false;
}
+ if (msg.type == SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST)
+ {
+ this.known_unimplemented(msg.type, "Main Migrate Switch Host");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_MIGRATE_END)
+ {
+ this.known_unimplemented(msg.type, "Main Migrate End");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_NAME)
+ {
+ this.known_unimplemented(msg.type, "Main Name");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_UUID)
+ {
+ this.known_unimplemented(msg.type, "Main UUID");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS)
+ {
+ this.known_unimplemented(msg.type, "Main Migrate Begin Seamless");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK)
+ {
+ this.known_unimplemented(msg.type, "Main Migrate Dst Seamless ACK");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK)
+ {
+ this.known_unimplemented(msg.type, "Main Migrate Dst Seamless NACK");
+ return true;
+ }
+
return false;
}
@@ -415,3 +486,9 @@ SpiceMainConn.prototype.handle_mouse_mode = function(current, supported)
this.inputs.mouse_mode = current;
}
+/* Shift current time to attempt to get a time matching that of the server */
+SpiceMainConn.prototype.relative_now = function()
+{
+ var ret = (Date.now() - this.our_mm_time) + this.mm_time;
+ return ret;
+}
diff --git a/static/js/spice-html5/playback.js b/static/js/spice-html5/playback.js
old mode 100644
new mode 100755
index 7209fbe..5af9233
--- a/static/js/spice-html5/playback.js
+++ b/static/js/spice-html5/playback.js
@@ -29,8 +29,6 @@ function SpicePlaybackConn()
this.queue = new Array();
this.append_okay = false;
this.start_time = 0;
- this.skip_until = 0;
- this.gap_time = 0;
}
SpicePlaybackConn.prototype = Object.create(SpiceConn.prototype);
@@ -46,7 +44,7 @@ SpicePlaybackConn.prototype.process_channel_message = function(msg)
{
var start = new SpiceMsgPlaybackStart(msg.data);
- DEBUG > 0 && console.log("PlaybackStart; frequency " + start.frequency);
+ PLAYBACK_DEBUG > 0 && console.log("PlaybackStart; frequency " + start.frequency);
if (start.frequency != OPUS_FREQUENCY)
{
@@ -72,6 +70,7 @@ SpicePlaybackConn.prototype.process_channel_message = function(msg)
this.media_source.spiceconn = this;
this.audio = document.createElement("audio");
+ this.audio.spiceconn = this;
this.audio.setAttribute('autoplay', true);
this.audio.src = window.URL.createObjectURL(this.media_source);
document.getElementById(this.parent.screen_id).appendChild(this.audio);
@@ -90,56 +89,59 @@ SpicePlaybackConn.prototype.process_channel_message = function(msg)
{
var data = new SpiceMsgPlaybackData(msg.data);
- // If this packet has the same time as the last, just bump up by one.
- if (this.last_data_time && data.time <= this.last_data_time)
+ if (! this.source_buffer)
+ return true;
+
+ if (this.audio.readyState >= 3 && this.audio.buffered.length > 1 &&
+ this.audio.currentTime == this.audio.buffered.end(0) &&
+ this.audio.currentTime < this.audio.buffered.start(this.audio.buffered.length - 1))
{
- // FIXME - this is arguably wrong. But delaying the transmission was worse,
- // in initial testing. Could use more research.
- DEBUG > 1 && console.log("Hacking time of " + data.time + " to " + this.last_data_time + 1);
- data.time = this.last_data_time + 1;
+ console.log("Audio underrun: we appear to have fallen behind; advancing to " +
+ this.audio.buffered.start(this.audio.buffered.length - 1));
+ this.audio.currentTime = this.audio.buffered.start(this.audio.buffered.length - 1);
}
- /* Gap detection: If there has been a delay since our last packet, then audio must
- have paused. Handling that gets tricky. In Chrome, you can seek forward,
- but you cannot in Firefox. And seeking forward in Chrome is nice, as it keeps
- Chrome from being overly cautious in it's buffer strategy.
+ /* Around version 45, Firefox started being very particular about the
+ time stamps put into the Opus stream. The time stamps from the Spice server are
+ somewhat irregular. They mostly arrive every 10 ms, but sometimes it is 11, or sometimes
+ with two time stamps the same in a row. The previous logic resulted in fuzzy and/or
+ distorted audio streams in Firefox in a row.
- So we do two things. First, we seek forward. Second, we compute how much of a gap
- there would have been, and essentially eliminate it.
+ In theory, the sequence mode should be appropriate for us, but as of 09/27/2016,
+ I was unable to make sequence mode work with Firefox.
+
+ Thus, we end up with an inelegant hack. Essentially, we force every packet to have
+ a 10ms time delta, unless there is an obvious gap in time stream, in which case we
+ will resync.
*/
- if (this.last_data_time && data.time >= (this.last_data_time + GAP_DETECTION_THRESHOLD))
+
+ if (this.start_time != 0 && data.time != (this.last_data_time + EXPECTED_PACKET_DURATION))
{
- this.skip_until = data.time;
- this.gap_time = (data.time - this.start_time) -
- (this.source_buffer.buffered.end(this.source_buffer.buffered.end.length - 1) * 1000.0).toFixed(0);
+ if (Math.abs(data.time - (EXPECTED_PACKET_DURATION + this.last_data_time)) < MAX_CLUSTER_TIME)
+ {
+ PLAYBACK_DEBUG > 1 && console.log("Hacking time of " + data.time + " to " +
+ (this.last_data_time + EXPECTED_PACKET_DURATION));
+ data.time = this.last_data_time + EXPECTED_PACKET_DURATION;
+ }
+ else
+ {
+ PLAYBACK_DEBUG > 1 && console.log("Apparent gap in audio time; now is " + data.time + " last was " + this.last_data_time);
+ }
}
this.last_data_time = data.time;
-
- DEBUG > 1 && console.log("PlaybackData; time " + data.time + "; length " + data.data.byteLength);
-
- if (! this.source_buffer)
- return true;
+ PLAYBACK_DEBUG > 1 && console.log("PlaybackData; time " + data.time + "; length " + data.data.byteLength);
if (this.start_time == 0)
this.start_playback(data);
- else if (data.time - this.cluster_time >= MAX_CLUSTER_TIME || this.skip_until > 0)
+ else if (data.time - this.cluster_time >= MAX_CLUSTER_TIME)
this.new_cluster(data);
else
this.simple_block(data, false);
- if (this.skip_until > 0)
- {
- this.audio.currentTime = (this.skip_until - this.start_time - this.gap_time) / 1000.0;
- this.skip_until = 0;
- }
-
- if (this.audio.paused)
- this.audio.play();
-
return true;
}
@@ -156,6 +158,39 @@ SpicePlaybackConn.prototype.process_channel_message = function(msg)
if (msg.type == SPICE_MSG_PLAYBACK_STOP)
{
+ PLAYBACK_DEBUG > 0 && console.log("PlaybackStop");
+ if (this.source_buffer)
+ {
+ document.getElementById(this.parent.screen_id).removeChild(this.audio);
+ window.URL.revokeObjectURL(this.audio.src);
+
+ delete this.source_buffer;
+ delete this.media_source;
+ delete this.audio;
+
+ this.append_okay = false;
+ this.queue = new Array();
+ this.start_time = 0;
+
+ return true;
+ }
+ }
+
+ if (msg.type == SPICE_MSG_PLAYBACK_VOLUME)
+ {
+ this.known_unimplemented(msg.type, "Playback Volume");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_PLAYBACK_MUTE)
+ {
+ this.known_unimplemented(msg.type, "Playback Mute");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_PLAYBACK_LATENCY)
+ {
+ this.known_unimplemented(msg.type, "Playback Latency");
return true;
}
@@ -167,10 +202,13 @@ SpicePlaybackConn.prototype.start_playback = function(data)
this.start_time = data.time;
var h = new webm_Header();
+ var te = new webm_AudioTrackEntry;
+ var t = new webm_Tracks(te);
- var mb = new ArrayBuffer(h.buffer_size())
+ var mb = new ArrayBuffer(h.buffer_size() + t.buffer_size())
this.bytes_written = h.to_buffer(mb);
+ this.bytes_written = t.to_buffer(mb, this.bytes_written);
this.source_buffer.addEventListener('error', handle_sourcebuffer_error, false);
this.source_buffer.addEventListener('updateend', handle_append_buffer_done, false);
@@ -183,7 +221,7 @@ SpicePlaybackConn.prototype.new_cluster = function(data)
{
this.cluster_time = data.time;
- var c = new webm_Cluster(data.time - this.start_time - this.gap_time);
+ var c = new webm_Cluster(data.time - this.start_time);
var mb = new ArrayBuffer(c.buffer_size());
this.bytes_written += c.to_buffer(mb);
@@ -222,6 +260,12 @@ function handle_source_open(e)
p.log_err('Codec ' + SPICE_PLAYBACK_CODEC + ' not available.');
return;
}
+
+ if (PLAYBACK_DEBUG > 0)
+ playback_handle_event_debug.call(this, e);
+
+ listen_for_audio_events(p);
+
p.source_buffer.spiceconn = p;
p.source_buffer.mode = "segments";
@@ -245,12 +289,38 @@ function handle_source_closed(e)
p.log_err('Audio source unexpectedly closed.');
}
-function handle_append_buffer_done(b)
+function condense_playback_queue(queue)
+{
+ if (queue.length == 1)
+ return queue.shift();
+
+ var len = 0;
+ var i = 0;
+ for (i = 0; i < queue.length; i++)
+ len += queue[i].byteLength;
+
+ var mb = new ArrayBuffer(len);
+ var tmp = new Uint8Array(mb);
+ len = 0;
+ for (i = 0; i < queue.length; i++)
+ {
+ tmp.set(new Uint8Array(queue[i]), len);
+ len += queue[i].byteLength;
+ }
+ queue.length = 0;
+ return mb;
+}
+
+function handle_append_buffer_done(e)
{
var p = this.spiceconn;
+
+ if (PLAYBACK_DEBUG > 1)
+ playback_handle_event_debug.call(this, e);
+
if (p.queue.length > 0)
{
- var mb = p.queue.shift();
+ var mb = condense_playback_queue(p.queue);
playback_append_buffer(p, mb);
}
else
@@ -276,3 +346,53 @@ function playback_append_buffer(p, b)
p.log_err("Error invoking appendBuffer: " + e.message);
}
}
+
+function playback_handle_event_debug(e)
+{
+ var p = this.spiceconn;
+ if (p.audio)
+ {
+ if (PLAYBACK_DEBUG > 0 || p.audio.buffered.len > 1)
+ console.log(p.audio.currentTime + ": event " + e.type +
+ dump_media_element(p.audio));
+ }
+
+ if (PLAYBACK_DEBUG > 1 && p.media_source)
+ console.log(" media_source " + dump_media_source(p.media_source));
+
+ if (PLAYBACK_DEBUG > 1 && p.source_buffer)
+ console.log(" source_buffer " + dump_source_buffer(p.source_buffer));
+
+ if (PLAYBACK_DEBUG > 0 || p.queue.length > 1)
+ console.log(' queue len ' + p.queue.length + '; append_okay: ' + p.append_okay);
+}
+
+function playback_debug_listen_for_one_event(name)
+{
+ this.addEventListener(name, playback_handle_event_debug);
+}
+
+function listen_for_audio_events(spiceconn)
+{
+ var audio_0_events = [
+ "abort", "error"
+ ];
+
+ var audio_1_events = [
+ "loadstart", "suspend", "emptied", "stalled", "loadedmetadata", "loadeddata", "canplay",
+ "canplaythrough", "playing", "waiting", "seeking", "seeked", "ended", "durationchange",
+ "timeupdate", "play", "pause", "ratechange"
+ ];
+
+ var audio_2_events = [
+ "progress",
+ "resize",
+ "volumechange"
+ ];
+
+ audio_0_events.forEach(playback_debug_listen_for_one_event, spiceconn.audio);
+ if (PLAYBACK_DEBUG > 0)
+ audio_1_events.forEach(playback_debug_listen_for_one_event, spiceconn.audio);
+ if (PLAYBACK_DEBUG > 1)
+ audio_2_events.forEach(playback_debug_listen_for_one_event, spiceconn.audio);
+}
diff --git a/static/js/spice-html5/png.js b/static/js/spice-html5/png.js
old mode 100644
new mode 100755
diff --git a/static/js/spice-html5/port.js b/static/js/spice-html5/port.js
new file mode 100755
index 0000000..ee22073
--- /dev/null
+++ b/static/js/spice-html5/port.js
@@ -0,0 +1,85 @@
+"use strict";
+/*
+ Copyright (C) 2016 by Oliver Gutierrez
+ Miroslav Chodil
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see .
+*/
+
+/*----------------------------------------------------------------------------
+** SpicePortConn
+** Drive the Spice Port Channel
+**--------------------------------------------------------------------------*/
+function SpicePortConn()
+{
+ DEBUG > 0 && console.log('SPICE port: created SPICE port channel. Args:', arguments);
+ SpiceConn.apply(this, arguments);
+ this.port_name = null;
+}
+
+SpicePortConn.prototype = Object.create(SpiceConn.prototype);
+
+SpicePortConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_PORT_INIT)
+ {
+ if (this.port_name === null)
+ {
+ var m = new SpiceMsgPortInit(msg.data);
+ this.portName = arraybuffer_to_str(new Uint8Array(m.name));
+ this.portOpened = m.opened
+ DEBUG > 0 && console.log('SPICE port: Port', this.portName, 'initialized');
+ return true;
+ }
+
+ DEBUG > 0 && console.log('SPICE port: Port', this.port_name, 'is already initialized.');
+ }
+ else if (msg.type == SPICE_MSG_PORT_EVENT)
+ {
+ DEBUG > 0 && console.log('SPICE port: Port event received for', this.portName, msg);
+ var event = new CustomEvent('spice-port-event', {
+ detail: {
+ channel: this,
+ spiceEvent: new Uint8Array(msg.data)
+ },
+ bubbles: true,
+ cancelable: true
+ });
+
+ window.dispatchEvent(event);
+ return true;
+ }
+ else if (msg.type == SPICE_MSG_SPICEVMC_DATA)
+ {
+ DEBUG > 0 && console.log('SPICE port: Data received in port', this.portName, msg);
+ var event = new CustomEvent('spice-port-data', {
+ detail: {
+ channel: this,
+ data: msg.data
+ },
+ bubbles: true,
+ cancelable: true
+ });
+ window.dispatchEvent(event);
+ return true;
+ }
+ else
+ {
+ DEBUG > 0 && console.log('SPICE port: SPICE message type not recognized:', msg)
+ }
+
+ return false;
+};
diff --git a/static/js/spice-html5/prng4.js b/static/js/spice-html5/prng4.js
deleted file mode 100644
index 4715372..0000000
--- a/static/js/spice-html5/prng4.js
+++ /dev/null
@@ -1,79 +0,0 @@
-// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
-
-/*
- * Copyright (c) 2003-2005 Tom Wu
- * All Rights Reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
- * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
- *
- * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
- * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
- * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
- * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
- * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- * In addition, the following condition applies:
- *
- * All redistributions must retain an intact copy of this copyright notice
- * and disclaimer.
- */
-
-
-// prng4.js - uses Arcfour as a PRNG
-
-function Arcfour() {
- this.i = 0;
- this.j = 0;
- this.S = new Array();
-}
-
-// Initialize arcfour context from key, an array of ints, each from [0..255]
-function ARC4init(key) {
- var i, j, t;
- for(i = 0; i < 256; ++i)
- this.S[i] = i;
- j = 0;
- for(i = 0; i < 256; ++i) {
- j = (j + this.S[i] + key[i % key.length]) & 255;
- t = this.S[i];
- this.S[i] = this.S[j];
- this.S[j] = t;
- }
- this.i = 0;
- this.j = 0;
-}
-
-function ARC4next() {
- var t;
- this.i = (this.i + 1) & 255;
- this.j = (this.j + this.S[this.i]) & 255;
- t = this.S[this.i];
- this.S[this.i] = this.S[this.j];
- this.S[this.j] = t;
- return this.S[(t + this.S[this.i]) & 255];
-}
-
-Arcfour.prototype.init = ARC4init;
-Arcfour.prototype.next = ARC4next;
-
-// Plug in your RNG constructor here
-function prng_newstate() {
- return new Arcfour();
-}
-
-// Pool size must be a multiple of 4 and greater than 32.
-// An array of bytes the size of the pool will be passed to init()
-var rng_psize = 256;
diff --git a/static/js/spice-html5/quic.js b/static/js/spice-html5/quic.js
old mode 100644
new mode 100755
index 9bb9f47..22ea3c7
--- a/static/js/spice-html5/quic.js
+++ b/static/js/spice-html5/quic.js
@@ -280,6 +280,7 @@ function QuicModel(bpc)
case 2:
case 4:
console.log("quic: findmodelparams(): evol value obsolete!!!\n");
+ break;
default:
console.log("quic: findmodelparams(): evol out of range!!!\n");
}
diff --git a/static/js/spice-html5/resize.js b/static/js/spice-html5/resize.js
old mode 100644
new mode 100755
index f5410d3..51fb1cc
--- a/static/js/spice-html5/resize.js
+++ b/static/js/spice-html5/resize.js
@@ -33,17 +33,29 @@
function resize_helper(sc)
{
var w = document.getElementById(sc.screen_id).clientWidth;
- var h = document.getElementById(sc.screen_id).clientHeight;
-
var m = document.getElementById(sc.message_id);
/* Resize vertically; basically we leave a 20 pixel margin
at the bottom, and use the position of the message window
to figure out how to resize */
- var hd = window.innerHeight - m.offsetHeight - m.offsetTop - 20;
+
+ var h = window.innerHeight - 20;
+
+ /* Screen height based on debug console visibility */
+ if (window.getComputedStyle(m).getPropertyValue("display") == 'none')
+ {
+ /* Get console height from spice.css .spice-message */
+ var mh = parseInt(window.getComputedStyle(m).getPropertyValue("height"), 10);
+ h = h - mh;
+ }
+ else
+ {
+ /* Show both div elements - spice-area and message-div */
+ h = h - m.offsetHeight - m.clientHeight;
+ }
+
/* Xorg requires height be a multiple of 8; round up */
- h = h + hd;
if (h % 8 > 0)
h += (8 - (h % 8));
diff --git a/static/js/spice-html5/rng.js b/static/js/spice-html5/rng.js
deleted file mode 100644
index 829a23c..0000000
--- a/static/js/spice-html5/rng.js
+++ /dev/null
@@ -1,102 +0,0 @@
-// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
-
-/*
- * Copyright (c) 2003-2005 Tom Wu
- * All Rights Reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
- * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
- *
- * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
- * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
- * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
- * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
- * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- * In addition, the following condition applies:
- *
- * All redistributions must retain an intact copy of this copyright notice
- * and disclaimer.
- */
-
-
-// Random number generator - requires a PRNG backend, e.g. prng4.js
-
-// For best results, put code like
-//
-// in your main HTML document.
-
-var rng_state;
-var rng_pool;
-var rng_pptr;
-
-// Mix in a 32-bit integer into the pool
-function rng_seed_int(x) {
- rng_pool[rng_pptr++] ^= x & 255;
- rng_pool[rng_pptr++] ^= (x >> 8) & 255;
- rng_pool[rng_pptr++] ^= (x >> 16) & 255;
- rng_pool[rng_pptr++] ^= (x >> 24) & 255;
- if(rng_pptr >= rng_psize) rng_pptr -= rng_psize;
-}
-
-// Mix in the current time (w/milliseconds) into the pool
-function rng_seed_time() {
- rng_seed_int(new Date().getTime());
-}
-
-// Initialize the pool with junk if needed.
-if(rng_pool == null) {
- rng_pool = new Array();
- rng_pptr = 0;
- var t;
- if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) {
- // Extract entropy (256 bits) from NS4 RNG if available
- var z = window.crypto.random(32);
- for(t = 0; t < z.length; ++t)
- rng_pool[rng_pptr++] = z.charCodeAt(t) & 255;
- }
- while(rng_pptr < rng_psize) { // extract some randomness from Math.random()
- t = Math.floor(65536 * Math.random());
- rng_pool[rng_pptr++] = t >>> 8;
- rng_pool[rng_pptr++] = t & 255;
- }
- rng_pptr = 0;
- rng_seed_time();
- //rng_seed_int(window.screenX);
- //rng_seed_int(window.screenY);
-}
-
-function rng_get_byte() {
- if(rng_state == null) {
- rng_seed_time();
- rng_state = prng_newstate();
- rng_state.init(rng_pool);
- for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr)
- rng_pool[rng_pptr] = 0;
- rng_pptr = 0;
- //rng_pool = null;
- }
- // TODO: allow reseeding after first request
- return rng_state.next();
-}
-
-function rng_get_bytes(ba) {
- var i;
- for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte();
-}
-
-function SecureRandom() {}
-
-SecureRandom.prototype.nextBytes = rng_get_bytes;
diff --git a/static/js/spice-html5/rsa.js b/static/js/spice-html5/rsa.js
deleted file mode 100644
index 1bbf249..0000000
--- a/static/js/spice-html5/rsa.js
+++ /dev/null
@@ -1,146 +0,0 @@
-// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
-
-/*
- * Copyright (c) 2003-2005 Tom Wu
- * All Rights Reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
- * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
- *
- * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
- * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
- * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
- * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
- * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- * In addition, the following condition applies:
- *
- * All redistributions must retain an intact copy of this copyright notice
- * and disclaimer.
- */
-
-
-// Depends on jsbn.js and rng.js
-
-// Version 1.1: support utf-8 encoding in pkcs1pad2
-
-// convert a (hex) string to a bignum object
-function parseBigInt(str,r) {
- return new BigInteger(str,r);
-}
-
-function linebrk(s,n) {
- var ret = "";
- var i = 0;
- while(i + n < s.length) {
- ret += s.substring(i,i+n) + "\n";
- i += n;
- }
- return ret + s.substring(i,s.length);
-}
-
-function byte2Hex(b) {
- if(b < 0x10)
- return "0" + b.toString(16);
- else
- return b.toString(16);
-}
-
-// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
-function pkcs1pad2(s,n) {
- if(n < s.length + 11) { // TODO: fix for utf-8
- alert("Message too long for RSA");
- return null;
- }
- var ba = new Array();
- var i = s.length - 1;
- while(i >= 0 && n > 0) {
- var c = s.charCodeAt(i--);
- if(c < 128) { // encode using utf-8
- ba[--n] = c;
- }
- else if((c > 127) && (c < 2048)) {
- ba[--n] = (c & 63) | 128;
- ba[--n] = (c >> 6) | 192;
- }
- else {
- ba[--n] = (c & 63) | 128;
- ba[--n] = ((c >> 6) & 63) | 128;
- ba[--n] = (c >> 12) | 224;
- }
- }
- ba[--n] = 0;
- var rng = new SecureRandom();
- var x = new Array();
- while(n > 2) { // random non-zero pad
- x[0] = 0;
- while(x[0] == 0) rng.nextBytes(x);
- ba[--n] = x[0];
- }
- ba[--n] = 2;
- ba[--n] = 0;
- return new BigInteger(ba);
-}
-
-// "empty" RSA key constructor
-function RSAKey() {
- this.n = null;
- this.e = 0;
- this.d = null;
- this.p = null;
- this.q = null;
- this.dmp1 = null;
- this.dmq1 = null;
- this.coeff = null;
-}
-
-// Set the public key fields N and e from hex strings
-function RSASetPublic(N,E) {
- if(N != null && E != null && N.length > 0 && E.length > 0) {
- this.n = parseBigInt(N,16);
- this.e = parseInt(E,16);
- }
- else
- alert("Invalid RSA public key");
-}
-
-// Perform raw public operation on "x": return x^e (mod n)
-function RSADoPublic(x) {
- return x.modPowInt(this.e, this.n);
-}
-
-// Return the PKCS#1 RSA encryption of "text" as an even-length hex string
-function RSAEncrypt(text) {
- var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3);
- if(m == null) return null;
- var c = this.doPublic(m);
- if(c == null) return null;
- var h = c.toString(16);
- if((h.length & 1) == 0) return h; else return "0" + h;
-}
-
-// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string
-//function RSAEncryptB64(text) {
-// var h = this.encrypt(text);
-// if(h) return hex2b64(h); else return null;
-//}
-
-// protected
-RSAKey.prototype.doPublic = RSADoPublic;
-
-// public
-RSAKey.prototype.setPublic = RSASetPublic;
-RSAKey.prototype.encrypt = RSAEncrypt;
-//RSAKey.prototype.encrypt_b64 = RSAEncryptB64;
diff --git a/static/js/spice-html5/sha1.js b/static/js/spice-html5/sha1.js
deleted file mode 100644
index 8118cb4..0000000
--- a/static/js/spice-html5/sha1.js
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
- * in FIPS 180-1
- * Version 2.2 Copyright Paul Johnston 2000 - 2009.
- * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
- * Distributed under the BSD License
- * See http://pajhome.org.uk/crypt/md5 for details.
- */
-
- /* Downloaded 6/1/2012 from the above address by Jeremy White.
- License reproduce here for completeness:
-
-Copyright (c) 1998 - 2009, Paul Johnston & Contributors
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-
-Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
- */
-
-/*
- * Configurable variables. You may need to tweak these to be compatible with
- * the server-side, but the defaults work in most cases.
- */
-var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
-var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
-
-/*
- * These are the functions you'll usually want to call
- * They take string arguments and return either hex or base-64 encoded strings
- */
-function hex_sha1(s) { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); }
-function b64_sha1(s) { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); }
-function any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); }
-function hex_hmac_sha1(k, d)
- { return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
-function b64_hmac_sha1(k, d)
- { return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
-function any_hmac_sha1(k, d, e)
- { return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
-
-/*
- * Perform a simple self-test to see if the VM is working
- */
-function sha1_vm_test()
-{
- return hex_sha1("abc").toLowerCase() == "a9993e364706816aba3e25717850c26c9cd0d89d";
-}
-
-/*
- * Calculate the SHA1 of a raw string
- */
-function rstr_sha1(s)
-{
- return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8));
-}
-
-/*
- * Calculate the HMAC-SHA1 of a key and some data (raw strings)
- */
-function rstr_hmac_sha1(key, data)
-{
- var bkey = rstr2binb(key);
- if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8);
-
- var ipad = Array(16), opad = Array(16);
- for(var i = 0; i < 16; i++)
- {
- ipad[i] = bkey[i] ^ 0x36363636;
- opad[i] = bkey[i] ^ 0x5C5C5C5C;
- }
-
- var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8);
- return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160));
-}
-
-/*
- * Convert a raw string to a hex string
- */
-function rstr2hex(input)
-{
- try { hexcase } catch(e) { hexcase=0; }
- var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
- var output = "";
- var x;
- for(var i = 0; i < input.length; i++)
- {
- x = input.charCodeAt(i);
- output += hex_tab.charAt((x >>> 4) & 0x0F)
- + hex_tab.charAt( x & 0x0F);
- }
- return output;
-}
-
-/*
- * Convert a raw string to a base-64 string
- */
-function rstr2b64(input)
-{
- try { b64pad } catch(e) { b64pad=''; }
- var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- var output = "";
- var len = input.length;
- for(var i = 0; i < len; i += 3)
- {
- var triplet = (input.charCodeAt(i) << 16)
- | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
- | (i + 2 < len ? input.charCodeAt(i+2) : 0);
- for(var j = 0; j < 4; j++)
- {
- if(i * 8 + j * 6 > input.length * 8) output += b64pad;
- else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
- }
- }
- return output;
-}
-
-/*
- * Convert a raw string to an arbitrary string encoding
- */
-function rstr2any(input, encoding)
-{
- var divisor = encoding.length;
- var remainders = Array();
- var i, q, x, quotient;
-
- /* Convert to an array of 16-bit big-endian values, forming the dividend */
- var dividend = Array(Math.ceil(input.length / 2));
- for(i = 0; i < dividend.length; i++)
- {
- dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
- }
-
- /*
- * Repeatedly perform a long division. The binary array forms the dividend,
- * the length of the encoding is the divisor. Once computed, the quotient
- * forms the dividend for the next step. We stop when the dividend is zero.
- * All remainders are stored for later use.
- */
- while(dividend.length > 0)
- {
- quotient = Array();
- x = 0;
- for(i = 0; i < dividend.length; i++)
- {
- x = (x << 16) + dividend[i];
- q = Math.floor(x / divisor);
- x -= q * divisor;
- if(quotient.length > 0 || q > 0)
- quotient[quotient.length] = q;
- }
- remainders[remainders.length] = x;
- dividend = quotient;
- }
-
- /* Convert the remainders to the output string */
- var output = "";
- for(i = remainders.length - 1; i >= 0; i--)
- output += encoding.charAt(remainders[i]);
-
- /* Append leading zero equivalents */
- var full_length = Math.ceil(input.length * 8 /
- (Math.log(encoding.length) / Math.log(2)))
- for(i = output.length; i < full_length; i++)
- output = encoding[0] + output;
-
- return output;
-}
-
-/*
- * Encode a string as utf-8.
- * For efficiency, this assumes the input is valid utf-16.
- */
-function str2rstr_utf8(input)
-{
- var output = "";
- var i = -1;
- var x, y;
-
- while(++i < input.length)
- {
- /* Decode utf-16 surrogate pairs */
- x = input.charCodeAt(i);
- y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
- if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
- {
- x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
- i++;
- }
-
- /* Encode output as utf-8 */
- if(x <= 0x7F)
- output += String.fromCharCode(x);
- else if(x <= 0x7FF)
- output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
- 0x80 | ( x & 0x3F));
- else if(x <= 0xFFFF)
- output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
- 0x80 | ((x >>> 6 ) & 0x3F),
- 0x80 | ( x & 0x3F));
- else if(x <= 0x1FFFFF)
- output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
- 0x80 | ((x >>> 12) & 0x3F),
- 0x80 | ((x >>> 6 ) & 0x3F),
- 0x80 | ( x & 0x3F));
- }
- return output;
-}
-
-/*
- * Encode a string as utf-16
- */
-function str2rstr_utf16le(input)
-{
- var output = "";
- for(var i = 0; i < input.length; i++)
- output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
- (input.charCodeAt(i) >>> 8) & 0xFF);
- return output;
-}
-
-function str2rstr_utf16be(input)
-{
- var output = "";
- for(var i = 0; i < input.length; i++)
- output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
- input.charCodeAt(i) & 0xFF);
- return output;
-}
-
-/*
- * Convert a raw string to an array of big-endian words
- * Characters >255 have their high-byte silently ignored.
- */
-function rstr2binb(input)
-{
- var output = Array(input.length >> 2);
- for(var i = 0; i < output.length; i++)
- output[i] = 0;
- for(var i = 0; i < input.length * 8; i += 8)
- output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
- return output;
-}
-
-/*
- * Convert an array of big-endian words to a string
- */
-function binb2rstr(input)
-{
- var output = "";
- for(var i = 0; i < input.length * 32; i += 8)
- output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);
- return output;
-}
-
-/*
- * Calculate the SHA-1 of an array of big-endian words, and a bit length
- */
-function binb_sha1(x, len)
-{
- /* append padding */
- x[len >> 5] |= 0x80 << (24 - len % 32);
- x[((len + 64 >> 9) << 4) + 15] = len;
-
- var w = Array(80);
- var a = 1732584193;
- var b = -271733879;
- var c = -1732584194;
- var d = 271733878;
- var e = -1009589776;
-
- for(var i = 0; i < x.length; i += 16)
- {
- var olda = a;
- var oldb = b;
- var oldc = c;
- var oldd = d;
- var olde = e;
-
- for(var j = 0; j < 80; j++)
- {
- if(j < 16) w[j] = x[i + j];
- else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
- var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)),
- safe_add(safe_add(e, w[j]), sha1_kt(j)));
- e = d;
- d = c;
- c = bit_rol(b, 30);
- b = a;
- a = t;
- }
-
- a = safe_add(a, olda);
- b = safe_add(b, oldb);
- c = safe_add(c, oldc);
- d = safe_add(d, oldd);
- e = safe_add(e, olde);
- }
- return Array(a, b, c, d, e);
-
-}
-
-/*
- * Perform the appropriate triplet combination function for the current
- * iteration
- */
-function sha1_ft(t, b, c, d)
-{
- if(t < 20) return (b & c) | ((~b) & d);
- if(t < 40) return b ^ c ^ d;
- if(t < 60) return (b & c) | (b & d) | (c & d);
- return b ^ c ^ d;
-}
-
-/*
- * Determine the appropriate additive constant for the current iteration
- */
-function sha1_kt(t)
-{
- return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
- (t < 60) ? -1894007588 : -899497514;
-}
-
-/*
- * Add integers, wrapping at 2^32. This uses 16-bit operations internally
- * to work around bugs in some JS interpreters.
- */
-function safe_add(x, y)
-{
- var lsw = (x & 0xFFFF) + (y & 0xFFFF);
- var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
- return (msw << 16) | (lsw & 0xFFFF);
-}
-
-/*
- * Bitwise rotate a 32-bit number to the left.
- */
-function bit_rol(num, cnt)
-{
- return (num << cnt) | (num >>> (32 - cnt));
-}
diff --git a/static/js/spice-html5/simulatecursor.js b/static/js/spice-html5/simulatecursor.js
old mode 100644
new mode 100755
index b1fce06..ffd9089
--- a/static/js/spice-html5/simulatecursor.js
+++ b/static/js/spice-html5/simulatecursor.js
@@ -71,7 +71,7 @@ simulate_cursor: function (spicecursor, cursor, screen, pngstr)
if (window.getComputedStyle(screen, null).cursor == 'auto')
{
- SpiceSimulateCursor.unknown_cursor(cursor_sha,
+ SpiceSimulateCursor.unknown_cursor(cursor_sha,
SpiceSimulateCursor.create_icondir(cursor.header.width, cursor.header.height,
cursor.data.byteLength, cursor.header.hot_spot_x, cursor.header.hot_spot_y) + pngstr);
@@ -99,7 +99,7 @@ simulate_cursor: function (spicecursor, cursor, screen, pngstr)
spicecursor.spice_simulated_cursor.style.pointerEvents = "none";
}
else
- {
+ {
if (spicecursor.spice_simulated_cursor)
{
spicecursor.spice_simulated_cursor.spice_screen.removeChild(spicecursor.spice_simulated_cursor);
@@ -162,7 +162,7 @@ create_icondir: function (width, height, bytes, hot_x, hot_y)
};
-SpiceSimulateCursor.ICONDIR.prototype =
+SpiceSimulateCursor.ICONDIR.prototype =
{
to_buffer: function(a, at)
{
diff --git a/static/js/spice-html5/spice.css b/static/js/spice-html5/spice.css
new file mode 100755
index 0000000..ee1b2f3
--- /dev/null
+++ b/static/js/spice-html5/spice.css
@@ -0,0 +1,117 @@
+body
+{
+ background-color: #999999;
+ color: #000000; margin: 0; padding: 0;
+ font-family: "Lucida Grande", "Lucida Sans Unicode", "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif;
+ font-size: 12pt;
+ line-height: 1.5em;
+}
+
+* { margin: 0; }
+
+#login
+{
+ width: 95%;
+ margin-left: auto;
+ margin-right: auto;
+ border: 1px solid #999999;
+ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#24414e));
+ background: -moz-linear-gradient(top, #fff, #24414e);
+ background-color: #24414e;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+}
+#login span.logo
+{
+ display: inline-block;
+ margin-right: 5px;
+ padding: 2px 10px 2px 20px;
+ border-right: 1px solid #999999;
+ font-size: 20px;
+ font-weight: bolder;
+ text-shadow: #efefef 1px 1px 0px;
+}
+#login label { color: #ffffff; text-shadow: 1px 1px 0px rgba(175, 210, 220, 0.8); }
+#login input
+{
+ padding: 5px;
+ background-color: #fAfAfA;
+ border: 1px inset #999999;
+ outline: none;
+ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+}
+#login input#host { width: 200px; }
+#login input#port { width: 75px; }
+#login input#password { width: 100px; }
+#login button
+{
+ padding: 5px 10px 5px 10px;
+ margin-left: 5px;
+ text-shadow: #efefef 1px 1px 0px;
+ border: 1px outset #999999;
+ cursor: pointer;
+ -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px;
+}
+#login button:hover
+{
+ background-color: #666666;
+ color: #ffffff;
+}
+
+#spice-area
+{
+ height: 100%;
+ width: 95%;
+ padding: 0;
+ margin-left: auto;
+ margin-right: auto;
+ border: solid #222222 1px;
+ -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
+ box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+}
+.spice-screen
+{
+ min-height: 600px;
+ height: 100%;
+ margin: 10px;
+ padding: 0;
+ background-color: #333333;
+}
+.spice-message {
+ width: 700px;
+ height: 50px;
+ overflow: auto;
+ margin-top: 5px;
+ margin-left: auto;
+ margin-right: auto;
+ padding: 10px;
+ background-color: #efefef;
+ border: solid #c3c3c3 2px;
+ font-size: 8pt;
+ line-height: 1.1em;
+ font-family: 'Andale Mono', monospace;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+ -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
+ box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
+}
+.spice-message p {
+ margin-bottom: 0em;
+ margin-top: 0em;
+}
+.spice-message-warning {
+ color: orange;
+}
+.spice-message-error {
+ color: red;
+}
diff --git a/static/js/spice-html5/spicearraybuffer.js b/static/js/spice-html5/spicearraybuffer.js
old mode 100644
new mode 100755
diff --git a/static/js/spice-html5/spiceconn.js b/static/js/spice-html5/spiceconn.js
old mode 100644
new mode 100755
index ec42d8d..33e7388
--- a/static/js/spice-html5/spiceconn.js
+++ b/static/js/spice-html5/spiceconn.js
@@ -23,7 +23,7 @@
** This is the base Javascript class for establishing and
** managing a connection to a Spice Server.
** It is used to provide core functionality to the Spice main,
-** display, inputs, and cursor channels. See main.js for
+** display, inputs, and cursor channels. See main.js for
** usage.
**--------------------------------------------------------------------------*/
function SpiceConn(o)
@@ -119,20 +119,36 @@ SpiceConn.prototype =
msg.connection_id = this.connection_id;
msg.channel_type = this.type;
- // FIXME - we're not setting a channel_id...
+ msg.channel_id = this.chan_id;
+
msg.common_caps.push(
(1 << SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION) |
(1 << SPICE_COMMON_CAP_MINI_HEADER)
);
if (msg.channel_type == SPICE_CHANNEL_PLAYBACK)
- msg.channel_caps.push(
- (1 << SPICE_PLAYBACK_CAP_OPUS)
- );
+ {
+ var caps = 0;
+ if ('MediaSource' in window && MediaSource.isTypeSupported(SPICE_PLAYBACK_CODEC))
+ caps |= (1 << SPICE_PLAYBACK_CAP_OPUS);
+ msg.channel_caps.push(caps);
+ }
else if (msg.channel_type == SPICE_CHANNEL_MAIN)
+ {
msg.channel_caps.push(
(1 << SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS)
);
+ }
+ else if (msg.channel_type == SPICE_CHANNEL_DISPLAY)
+ {
+ var caps = (1 << SPICE_DISPLAY_CAP_SIZED_STREAM) |
+ (1 << SPICE_DISPLAY_CAP_STREAM_REPORT) |
+ (1 << SPICE_DISPLAY_CAP_MULTI_CODEC) |
+ (1 << SPICE_DISPLAY_CAP_CODEC_MJPEG);
+ if ('MediaSource' in window && MediaSource.isTypeSupported(SPICE_VP8_CODEC))
+ caps |= (1 << SPICE_DISPLAY_CAP_CODEC_VP8);
+ msg.channel_caps.push(caps);
+ }
hdr.size = msg.buffer_size();
@@ -180,8 +196,11 @@ SpiceConn.prototype =
if (msg.type > 500)
{
- alert("Something has gone very wrong; we think we have message of type " + msg.type);
- debugger;
+ if (DEBUG > 0)
+ {
+ alert("Something has gone very wrong; we think we have message of type " + msg.type);
+ debugger;
+ }
}
if (msg.size == 0)
@@ -329,6 +348,7 @@ SpiceConn.prototype =
process_message: function(msg)
{
var rc;
+ var start = Date.now();
DEBUG > 0 && console.log("<< hdr " + this.channel_type() + " type " + msg.type + " size " + (msg.data && msg.data.byteLength));
rc = this.process_common_messages(msg);
if (! rc)
@@ -337,10 +357,10 @@ SpiceConn.prototype =
{
rc = this.process_channel_message(msg);
if (! rc)
- this.log_warn(this.type + ": Unknown message type " + msg.type + "!");
+ this.log_warn(this.channel_type() + ": Unknown message type " + msg.type + "!");
}
else
- this.log_err(this.type + ": No message handlers for this channel; message " + msg.type);
+ this.log_err(this.channel_type() + ": No message handlers for this channel; message " + msg.type);
}
if (this.msgs_until_ack !== undefined && this.ack_window)
@@ -356,6 +376,9 @@ SpiceConn.prototype =
}
}
+ var delta = Date.now() - start;
+ if (DEBUG > 0 || delta > GAP_DETECTION_THRESHOLD)
+ console.log("delta " + this.channel_type() + ":" + msg.type + " " + delta);
return rc;
},
@@ -369,6 +392,20 @@ SpiceConn.prototype =
return "inputs";
else if (this.type == SPICE_CHANNEL_CURSOR)
return "cursor";
+ else if (this.type == SPICE_CHANNEL_PLAYBACK)
+ return "playback";
+ else if (this.type == SPICE_CHANNEL_RECORD)
+ return "record";
+ else if (this.type == SPICE_CHANNEL_TUNNEL)
+ return "tunnel";
+ else if (this.type == SPICE_CHANNEL_SMARTCARD)
+ return "smartcard";
+ else if (this.type == SPICE_CHANNEL_USBREDIR)
+ return "usbredir";
+ else if (this.type == SPICE_CHANNEL_PORT)
+ return "port";
+ else if (this.type == SPICE_CHANNEL_WEBDAV)
+ return "webdav";
return "unknown-" + this.type;
},
diff --git a/static/js/spice-html5/spicedataview.js b/static/js/spice-html5/spicedataview.js
old mode 100644
new mode 100755
index 800df03..719d968
--- a/static/js/spice-html5/spicedataview.js
+++ b/static/js/spice-html5/spicedataview.js
@@ -20,10 +20,10 @@
/*----------------------------------------------------------------------------
** SpiceDataView
-** FIXME FIXME
+** FIXME FIXME
** This is used because Firefox does not have DataView yet.
-** We should use DataView if we have it, because it *has* to
-** be faster than this code
+** We should use DataView if we have it, because it *has* to
+** be faster than this code
**--------------------------------------------------------------------------*/
function SpiceDataView(buffer, byteOffset, byteLength)
{
@@ -63,7 +63,7 @@ SpiceDataView.prototype = {
high = 2;
}
- return (this.getUint16(byteOffset + high, littleEndian) << 16) |
+ return (this.getUint16(byteOffset + high, littleEndian) << 16) |
this.getUint16(byteOffset + low, littleEndian);
},
getUint64: function (byteOffset, littleEndian)
diff --git a/static/js/spice-html5/spicemsg.js b/static/js/spice-html5/spicemsg.js
old mode 100644
new mode 100755
index e167b3d..3619996
--- a/static/js/spice-html5/spicemsg.js
+++ b/static/js/spice-html5/spicemsg.js
@@ -21,7 +21,7 @@
/*----------------------------------------------------------------------------
** Spice messages
** This file contains classes for passing messages to and from
-** a spice server. This file should arguably be generated from
+** a spice server. This file should arguably be generated from
** spice.proto, but it was instead put together by hand.
**--------------------------------------------------------------------------*/
function SpiceLinkHeader(a, at)
@@ -63,7 +63,7 @@ SpiceLinkHeader.prototype =
dv.setUint32(at, this.size, true); at += 4;
},
buffer_size: function()
- {
+ {
return 16;
},
}
@@ -938,7 +938,7 @@ function SpiceMsgcMousePosition(sc, e)
this.x = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft + scrollLeft;
this.y = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop + scrollTop;
sc.mousex = this.x;
- sc.mousey = this.y;
+ sc.mousey = this.y;
}
else
{
@@ -1146,6 +1146,29 @@ SpiceMsgDisplayStreamData.prototype =
},
}
+function SpiceMsgDisplayStreamDataSized(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamDataSized.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceStreamDataHeader;
+ at = this.base.from_dv(dv, at, a);
+ this.width = dv.getUint32(at, true); at += 4;
+ this.height = dv.getUint32(at, true); at += 4;
+ this.dest = new SpiceRect;
+ at = this.dest.from_dv(dv, at, a);
+ this.data_size = dv.getUint32(at, true); at += 4;
+ this.data = dv.u8.subarray(at, at + this.data_size);
+ },
+}
+
+
function SpiceMsgDisplayStreamClip(a, at)
{
this.from_buffer(a, at);
@@ -1178,6 +1201,60 @@ SpiceMsgDisplayStreamDestroy.prototype =
},
}
+function SpiceMsgDisplayStreamActivateReport(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamActivateReport.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.stream_id = dv.getUint32(at, true); at += 4;
+ this.unique_id = dv.getUint32(at, true); at += 4;
+ this.max_window_size = dv.getUint32(at, true); at += 4;
+ this.timeout_ms = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgcDisplayStreamReport(stream_id, unique_id)
+{
+ this.stream_id = stream_id;
+ this.unique_id = unique_id;
+ this.start_frame_mm_time = 0;
+ this.end_frame_mm_time = 0;
+ this.num_frames = 0;
+ this.num_drops = 0;
+ this.last_frame_delay = 0;
+
+ // TODO - Implement audio delay
+ this.audio_delay = -1;
+}
+
+SpiceMsgcDisplayStreamReport.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.stream_id, true); at += 4;
+ dv.setUint32(at, this.unique_id, true); at += 4;
+ dv.setUint32(at, this.start_frame_mm_time, true); at += 4;
+ dv.setUint32(at, this.end_frame_mm_time, true); at += 4;
+ dv.setUint32(at, this.num_frames, true); at += 4;
+ dv.setUint32(at, this.num_drops, true); at += 4;
+ dv.setUint32(at, this.last_frame_delay, true); at += 4;
+ dv.setUint32(at, this.audio_delay, true); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 8 * 4;
+ }
+}
+
function SpiceMsgDisplayInvalList(a, at)
{
this.count = 0;
@@ -1201,3 +1278,21 @@ SpiceMsgDisplayInvalList.prototype =
}
},
}
+
+function SpiceMsgPortInit(a, at)
+{
+ this.from_buffer(a,at);
+};
+
+SpiceMsgPortInit.prototype =
+{
+ from_buffer: function (a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ var namesize = dv.getUint32(at, true); at += 4;
+ var offset = dv.getUint32(at, true); at += 4;
+ this.opened = dv.getUint8(at, true); at += 1;
+ this.name = a.slice(offset, offset + namesize - 1);
+ }
+}
diff --git a/static/js/spice-html5/spicetype.js b/static/js/spice-html5/spicetype.js
old mode 100644
new mode 100755
index 951b277..e145abc
--- a/static/js/spice-html5/spicetype.js
+++ b/static/js/spice-html5/spicetype.js
@@ -469,5 +469,5 @@ SpiceSurface.prototype =
},
}
-/* FIXME - SpiceImage types lz_plt, jpeg, zlib_glz, and jpeg_alpha are
+/* FIXME - SpiceImage types lz_plt, jpeg, zlib_glz, and jpeg_alpha are
completely unimplemented */
diff --git a/static/js/spice-html5/thirdparty/jsbn.js b/static/js/spice-html5/thirdparty/jsbn.js
old mode 100644
new mode 100755
index 9b9476e..d88ec54
--- a/static/js/spice-html5/thirdparty/jsbn.js
+++ b/static/js/spice-html5/thirdparty/jsbn.js
@@ -15,9 +15,9 @@
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
- * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
diff --git a/static/js/spice-html5/thirdparty/prng4.js b/static/js/spice-html5/thirdparty/prng4.js
old mode 100644
new mode 100755
index 4715372..ef3efd6
--- a/static/js/spice-html5/thirdparty/prng4.js
+++ b/static/js/spice-html5/thirdparty/prng4.js
@@ -15,9 +15,9 @@
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
- * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
diff --git a/static/js/spice-html5/thirdparty/rng.js b/static/js/spice-html5/thirdparty/rng.js
old mode 100644
new mode 100755
index 829a23c..efbf382
--- a/static/js/spice-html5/thirdparty/rng.js
+++ b/static/js/spice-html5/thirdparty/rng.js
@@ -15,9 +15,9 @@
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
- * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
@@ -66,7 +66,7 @@ if(rng_pool == null) {
var z = window.crypto.random(32);
for(t = 0; t < z.length; ++t)
rng_pool[rng_pptr++] = z.charCodeAt(t) & 255;
- }
+ }
while(rng_pptr < rng_psize) { // extract some randomness from Math.random()
t = Math.floor(65536 * Math.random());
rng_pool[rng_pptr++] = t >>> 8;
diff --git a/static/js/spice-html5/thirdparty/rsa.js b/static/js/spice-html5/thirdparty/rsa.js
old mode 100644
new mode 100755
index 1bbf249..ea0e45b
--- a/static/js/spice-html5/thirdparty/rsa.js
+++ b/static/js/spice-html5/thirdparty/rsa.js
@@ -15,9 +15,9 @@
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
- * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
diff --git a/static/js/spice-html5/thirdparty/sha1.js b/static/js/spice-html5/thirdparty/sha1.js
old mode 100644
new mode 100755
diff --git a/static/js/spice-html5/ticket.js b/static/js/spice-html5/ticket.js
old mode 100644
new mode 100755
diff --git a/static/js/spice-html5/utils.js b/static/js/spice-html5/utils.js
old mode 100644
new mode 100755
index 9eb42ff..7aeefdb
--- a/static/js/spice-html5/utils.js
+++ b/static/js/spice-html5/utils.js
@@ -22,9 +22,16 @@
** Utility settings and functions for Spice
**--------------------------------------------------------------------------*/
var DEBUG = 0;
+var PLAYBACK_DEBUG = 0;
+var STREAM_DEBUG = 0;
var DUMP_DRAWS = false;
var DUMP_CANVASES = false;
+/*----------------------------------------------------------------------------
+** We use an Image temporarily, and the image/src does not get garbage
+** collected as quickly as we might like. This blank image helps with that.
+**--------------------------------------------------------------------------*/
+var EMPTY_GIF_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
/*----------------------------------------------------------------------------
** combine_array_buffers
@@ -97,10 +104,17 @@ function hexdump_buffer(a)
}
}
+/*----------------------------------------------------------------------------
+** Convert arraybuffer to string
+**--------------------------------------------------------------------------*/
+function arraybuffer_to_str(buf) {
+ return String.fromCharCode.apply(null, new Uint16Array(buf));
+}
+
/*----------------------------------------------------------------------------
** Converting keycodes to AT scancodes is very hard.
** luckly there are some resources on the web and in the Xorg driver that help
-** us figure out what browser depenend keycodes match to what scancodes.
+** us figure out what browser dependent keycodes match to what scancodes.
**
** This will most likely not work for non US keyboard and browsers other than
** modern Chrome and FireFox.
@@ -155,7 +169,7 @@ common_scanmap[121] = KEY_F10;
common_scanmap[122] = KEY_F11;
common_scanmap[123] = KEY_F12;
-/* These externded scancodes do not line up with values from atKeynames */
+/* These extended scancodes do not line up with values from atKeynames */
common_scanmap[42] = 99;
common_scanmap[19] = 101; // Break
common_scanmap[111] = 0xE035; // KP_Divide
@@ -263,3 +277,56 @@ function keycode_to_end_scan(code)
return 0x80e0 | ((scancode - 0x100) << 8);
}
}
+
+function dump_media_element(m)
+{
+ var ret =
+ "[networkState " + m.networkState +
+ "|readyState " + m.readyState +
+ "|error " + m.error +
+ "|seeking " + m.seeking +
+ "|duration " + m.duration +
+ "|paused " + m.paused +
+ "|ended " + m.error +
+ "|buffered " + dump_timerange(m.buffered) +
+ "]";
+ return ret;
+}
+
+function dump_media_source(ms)
+{
+ var ret =
+ "[duration " + ms.duration +
+ "|readyState " + ms.readyState + "]";
+ return ret;
+}
+
+function dump_source_buffer(sb)
+{
+ var ret =
+ "[appendWindowStart " + sb.appendWindowStart +
+ "|appendWindowEnd " + sb.appendWindowEnd +
+ "|buffered " + dump_timerange(sb.buffered) +
+ "|timeStampOffset " + sb.timeStampOffset +
+ "|updating " + sb.updating +
+ "]";
+ return ret;
+}
+
+function dump_timerange(tr)
+{
+ var ret;
+
+ if (tr)
+ {
+ var i = tr.length;
+ ret = "{len " + i;
+ if (i > 0)
+ ret += "; start " + tr.start(0) + "; end " + tr.end(i - 1);
+ ret += "}";
+ }
+ else
+ ret = "N/A";
+
+ return ret;
+}
diff --git a/static/js/spice-html5/webm.js b/static/js/spice-html5/webm.js
old mode 100644
new mode 100755
index 35cbc07..789da14
--- a/static/js/spice-html5/webm.js
+++ b/static/js/spice-html5/webm.js
@@ -61,6 +61,10 @@ var WEBM_CODEC_DELAY = [ 0x56, 0xAA ];
var WEBM_CODEC_PRIVATE = [ 0x63, 0xA2 ];
var WEBM_CODEC_ID = [ 0x86 ];
+var WEBM_VIDEO = [ 0xE0 ] ;
+var WEBM_PIXEL_WIDTH = [ 0xB0 ] ;
+var WEBM_PIXEL_HEIGHT = [ 0xBA ] ;
+
var WEBM_AUDIO = [ 0xE1 ] ;
var WEBM_SAMPLING_FREQUENCY = [ 0xB5 ] ;
var WEBM_CHANNELS = [ 0x9F ] ;
@@ -80,8 +84,11 @@ var OPUS_CHANNELS = 2;
var SPICE_PLAYBACK_CODEC = 'audio/webm; codecs="opus"';
var MAX_CLUSTER_TIME = 1000;
+var EXPECTED_PACKET_DURATION = 10;
var GAP_DETECTION_THRESHOLD = 50;
+var SPICE_VP8_CODEC = 'video/webm; codecs="vp8"';
+
/*----------------------------------------------------------------------------
** EBML utility functions
** These classes can create the binary representation of a webm file
@@ -291,6 +298,34 @@ webm_Audio.prototype =
},
}
+function webm_Video(width, height)
+{
+ this.id = WEBM_VIDEO;
+ this.width = width;
+ this.height = height;
+}
+
+webm_Video.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new DataView(a);
+ at = EBML_write_array(this.id, dv, at);
+ at = EBML_write_u64_data_len(this.buffer_size() - 8 - this.id.length, dv, at);
+ at = EBML_write_u16_value(WEBM_PIXEL_WIDTH, this.width, dv, at)
+ at = EBML_write_u16_value(WEBM_PIXEL_HEIGHT, this.height, dv, at)
+ return at;
+ },
+ buffer_size: function()
+ {
+ return this.id.length + 8 +
+ WEBM_PIXEL_WIDTH.length + 1 + 2 +
+ WEBM_PIXEL_HEIGHT.length + 1 + 2;
+ },
+}
+
+
/* ---------------------------
SeekHead not currently used. Hopefully not needed.
@@ -356,7 +391,7 @@ webm_SeekHead.prototype =
End of Seek Head
*/
-function webm_TrackEntry()
+function webm_AudioTrackEntry()
{
this.id = WEBM_TRACK_ENTRY;
this.number = 1;
@@ -385,7 +420,7 @@ function webm_TrackEntry()
];
}
-webm_TrackEntry.prototype =
+webm_AudioTrackEntry.prototype =
{
to_buffer: function(a, at)
{
@@ -431,6 +466,70 @@ webm_TrackEntry.prototype =
this.audio.buffer_size();
},
}
+
+function webm_VideoTrackEntry(width, height)
+{
+ this.id = WEBM_TRACK_ENTRY;
+ this.number = 1;
+ this.uid = 1;
+ this.type = 1; // Video
+ this.flag_enabled = 1;
+ this.flag_default = 1;
+ this.flag_forced = 1;
+ this.flag_lacing = 0;
+ this.min_cache = 0; // fixme - check
+ this.max_block_addition_id = 0;
+ this.codec_decode_all = 0; // fixme - check
+ this.seek_pre_roll = 0; // 80000000; // fixme - check
+ this.codec_delay = 80000000; // Must match codec_private.preskip
+ this.codec_id = "V_VP8";
+ this.video = new webm_Video(width, height);
+}
+
+webm_VideoTrackEntry.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new DataView(a);
+ at = EBML_write_array(this.id, dv, at);
+ at = EBML_write_u64_data_len(this.buffer_size() - 8 - this.id.length, dv, at);
+ at = EBML_write_u8_value(WEBM_TRACK_NUMBER, this.number, dv, at);
+ at = EBML_write_u8_value(WEBM_TRACK_UID, this.uid, dv, at);
+ at = EBML_write_u8_value(WEBM_FLAG_ENABLED, this.flag_enabled, dv, at);
+ at = EBML_write_u8_value(WEBM_FLAG_DEFAULT, this.flag_default, dv, at);
+ at = EBML_write_u8_value(WEBM_FLAG_FORCED, this.flag_forced, dv, at);
+ at = EBML_write_u8_value(WEBM_FLAG_LACING, this.flag_lacing, dv, at);
+ at = EBML_write_data(WEBM_CODEC_ID, this.codec_id, dv, at);
+ at = EBML_write_u8_value(WEBM_MIN_CACHE, this.min_cache, dv, at);
+ at = EBML_write_u8_value(WEBM_MAX_BLOCK_ADDITION_ID, this.max_block_addition_id, dv, at);
+ at = EBML_write_u8_value(WEBM_CODEC_DECODE_ALL, this.codec_decode_all, dv, at);
+ at = EBML_write_u32_value(WEBM_CODEC_DELAY, this.codec_delay, dv, at);
+ at = EBML_write_u32_value(WEBM_SEEK_PRE_ROLL, this.seek_pre_roll, dv, at);
+ at = EBML_write_u8_value(WEBM_TRACK_TYPE, this.type, dv, at);
+ at = this.video.to_buffer(a, at);
+ return at;
+ },
+ buffer_size: function()
+ {
+ return this.id.length + 8 +
+ WEBM_TRACK_NUMBER.length + 1 + 1 +
+ WEBM_TRACK_UID.length + 1 + 1 +
+ WEBM_FLAG_ENABLED.length + 1 + 1 +
+ WEBM_FLAG_DEFAULT.length + 1 + 1 +
+ WEBM_FLAG_FORCED.length + 1 + 1 +
+ WEBM_FLAG_LACING.length + 1 + 1 +
+ WEBM_CODEC_ID.length + this.codec_id.length + 1 +
+ WEBM_MIN_CACHE.length + 1 + 1 +
+ WEBM_MAX_BLOCK_ADDITION_ID.length + 1 + 1 +
+ WEBM_CODEC_DECODE_ALL.length + 1 + 1 +
+ WEBM_CODEC_DELAY.length + 1 + 4 +
+ WEBM_SEEK_PRE_ROLL.length + 1 + 4 +
+ WEBM_TRACK_TYPE.length + 1 + 1 +
+ this.video.buffer_size();
+ },
+}
+
function webm_Tracks(entry)
{
this.id = WEBM_TRACKS;
@@ -526,9 +625,6 @@ function webm_Header()
this.info = new webm_SegmentInformation;
this.seek_head.track.pos = this.seek_head.info.pos + this.info.buffer_size();
-
- this.track_entry = new webm_TrackEntry;
- this.tracks = new webm_Tracks(this.track_entry);
}
webm_Header.prototype =
@@ -539,7 +635,6 @@ webm_Header.prototype =
at = this.ebml.to_buffer(a, at);
at = this.segment.to_buffer(a, at);
at = this.info.to_buffer(a, at);
- at = this.tracks.to_buffer(a, at);
return at;
},
@@ -547,7 +642,6 @@ webm_Header.prototype =
{
return this.ebml.buffer_size() +
this.segment.buffer_size() +
- this.info.buffer_size() +
- this.tracks.buffer_size();
+ this.info.buffer_size();
},
}
diff --git a/static/js/spice-html5/wire.js b/static/js/spice-html5/wire.js
old mode 100644
new mode 100755
index 7407ce7..2c7f096
--- a/static/js/spice-html5/wire.js
+++ b/static/js/spice-html5/wire.js
@@ -96,7 +96,7 @@ SpiceWireReader.prototype =
this.callback.call(this.sc, mb,
this.saved_msg_header || undefined);
}
-
+
},
request: function(n)
diff --git a/storages/forms.py b/storages/forms.py
index a4a34bf..ca143e7 100644
--- a/storages/forms.py
+++ b/storages/forms.py
@@ -52,7 +52,7 @@ class AddStgPool(forms.Form):
class AddImage(forms.Form):
- name = forms.CharField(max_length=20)
+ name = forms.CharField(max_length=120)
format = forms.ChoiceField(required=True, choices=(('qcow2', 'qcow2 (recommended)'),
('qcow', 'qcow'),
('raw', 'raw')))
@@ -64,14 +64,14 @@ class AddImage(forms.Form):
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
if not have_symbol:
raise forms.ValidationError(_('The image name must not contain any special characters'))
- elif len(name) > 20:
- raise forms.ValidationError(_('The image name must not exceed 20 characters'))
+ elif len(name) > 120:
+ raise forms.ValidationError(_('The image name must not exceed 120 characters'))
return name
class CloneImage(forms.Form):
- name = forms.CharField(max_length=20)
- image = forms.CharField(max_length=20)
+ name = forms.CharField(max_length=120)
+ image = forms.CharField(max_length=120)
convert = forms.BooleanField(required=False)
format = forms.ChoiceField(required=False, choices=(('qcow2', 'qcow2 (recommended)'),
('qcow', 'qcow'),
@@ -83,6 +83,6 @@ class CloneImage(forms.Form):
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
if not have_symbol:
raise forms.ValidationError(_('The image name must not contain any special characters'))
- elif len(name) > 20:
- raise forms.ValidationError(_('The image name must not exceed 20 characters'))
+ elif len(name) > 120:
+ raise forms.ValidationError(_('The image name must not exceed 120 characters'))
return name
diff --git a/storages/templates/storage.html b/storages/templates/storage.html
index 3c50e93..ecc9adc 100644
--- a/storages/templates/storage.html
+++ b/storages/templates/storage.html
@@ -3,223 +3,228 @@
{% load staticfiles %}
{% block title %}{% trans "Storage" %} - {{ pool }}{% endblock %}
{% block style %}
-
+
{% endblock %}
{% block content %}
-
-
-
+
+
+
+
+
+{% include 'errors_block.html' %}
+{% include 'messages_block.html' %}
+
+
+
+
{% trans "Pool name:" %}
+
{% trans "Pool type:" %}
+
{% trans "Pool path:" %}
+
{% trans "Pool status:" %}
+
{% trans "Size:" %} ({{ size|filesizeformat }} / {{ used|filesizeformat }})
+
{% trans "State:" %}
+
{% trans "Autostart:" %}
+
+
+
{{ pool }}
+
{% if not type %}{% trans "None" %}{% else %}{{ type }}{% endif %}
+
{% if not path %}{% trans "None" %}{% else %}{{ path }}{% endif %}
+
{% if not status %}{% trans "None" %}{% else %}{{ status }}{% endif %}
+
{% trans "Usage:" %} {{ percent }}%
+
+
+
+
+
+
+
+
+
+
+ {% if state %}
+
+
+
-
+
- {% include 'errors_block.html' %}
-
-
-
-
{% trans "Pool name:" %}
-
{% trans "Pool type:" %}
-
{% trans "Pool path:" %}
-
{% trans "Pool status:" %}
-
{% trans "Size:" %} ({{ size|filesizeformat }} / {{ used|filesizeformat }})
-
{% trans "State:" %}
-
{% trans "Autostart:" %}
-
-
-
{{ pool }}
-
{% if not type %}{% trans "None" %}{% else %}{{ type }}{% endif %}
-
{% if not path %}{% trans "None" %}{% else %}{{ path }}{% endif %}
-
{% if not status %}{% trans "None" %}{% else %}{{ status }}{% endif %}
-
{% trans "Usage:" %} {{ percent }}%
-
-
+ {% if volumes %}
+
+
+
+
+ # |
+ {% trans "Name" %} |
+ {% trans "Allocated" %} |
+ {% trans "Size" %} |
+ {% trans "Format" %} |
+ {% trans "Action" %} |
+
+
+
+ {% for volume in volumes %}
+
+ {{ forloop.counter }} |
+ {{ volume.name }} |
+ {{ volume.allocation|filesizeformat }} |
+ {{ volume.size|filesizeformat }} |
+ {{ volume.type }} |
+
+
+
+ {% ifnotequal volume.type "iso" %}
+
+ {% else %}
+
+ {% endifnotequal %}
+ |
+
+
-
-
-
-
-
-
-
-
- {% if state %}
-
- {% if volumes %}
-
-
-
-
- # |
- {% trans "Name" %} |
- {% trans "Size" %} |
- {% trans "Format" %} |
- {% trans "Action" %} |
-
-
-
- {% for volume in volumes %}
-
- {{ forloop.counter }} |
- {{ volume.name }} |
- {{ volume.size|filesizeformat }} |
- {{ volume.type }} |
-
-
-
- {% ifnotequal volume.type "iso" %}
-
- {% else %}
-
- {% endifnotequal %}
- |
-
-
- |
-
- {% endfor %}
-
-
-
- {% else %}
-
-
-
- {% trans "Warning:" %} {% trans "Hypervisor doesn't have any Volumes" %}
-
-
- {% endif %}
- {% endif %}
-
+ |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
+
+
+ {% trans "Warning:" %} {% trans "Hypervisor doesn't have any Volumes" %}
+
+ {% endif %}
+ {% endif %}
+
+
+{% include 'pleasewaitdialog.html' %}
{% endblock %}
{% block script %}
-
-
+
-
+ });
+ $(document).on('change', '.image-format', function () {
+ if ($(this).val() == "qcow2") {
+ $('.meta-prealloc').show();
+ } else {
+ $('.meta-prealloc').hide();
+ }
+ });
+
+
{% endblock %}
diff --git a/storages/templates/storages.html b/storages/templates/storages.html
index 4b8dc93..21bca90 100644
--- a/storages/templates/storages.html
+++ b/storages/templates/storages.html
@@ -34,7 +34,7 @@
{% if not storages %}
-
+
{% trans "Warning:" %} {% trans "Hypervisor doesn't have any Storages" %}
diff --git a/storages/views.py b/storages/views.py
index d3f1965..f18cad6 100644
--- a/storages/views.py
+++ b/storages/views.py
@@ -7,7 +7,7 @@ from computes.models import Compute
from storages.forms import AddStgPool, AddImage, CloneImage
from vrtManager.storage import wvmStorage, wvmStorages
from libvirt import libvirtError
-
+from django.contrib import messages
@login_required
def storages(request, compute_id):
@@ -155,6 +155,7 @@ def storage(request, compute_id, pool):
meta_prealloc = True
try:
conn.create_volume(data['name'], data['size'], data['format'], meta_prealloc)
+ messages.success("Image file {} is created successfully".format(data['name']+".img"))
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err)
@@ -166,6 +167,7 @@ def storage(request, compute_id, pool):
try:
vol = conn.get_volume(volname)
vol.delete(0)
+ messages.success(request,_('Volume: {} is deleted.'.format(volname)))
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err.message)
@@ -175,6 +177,7 @@ def storage(request, compute_id, pool):
error_messages.append(error_msg)
else:
handle_uploaded_file(path, request.FILES['file'])
+ messages.success(request, _('ISO: {} is uploaded.'.format(request.FILES['file'])))
return HttpResponseRedirect(request.get_full_path())
if 'cln_volume' in request.POST:
form = CloneImage(request.POST)
@@ -194,6 +197,8 @@ def storage(request, compute_id, pool):
format = None
try:
conn.clone_volume(data['image'], data['name'], format, meta_prealloc)
+ messages.success(request, _("{} image cloned as {} successfully".format(data['image'],
+ data['name'] + ".img")))
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err)
diff --git a/templates/navbar.html b/templates/navbar.html
index 786f4df..6b69be4 100644
--- a/templates/navbar.html
+++ b/templates/navbar.html
@@ -5,7 +5,7 @@