Group :: Rede/Acesso Remoto
RPM: spice-html5
Main Changelog Spec Patches Sources Download Gear Bugs e FR Repocop
Patch: spice-html5-gitf9f700e.patch
Download
Download
README | 4 +-
TODO | 2 +-
bitmap.js | 20 +++++----
cursor.js | 2 +-
display.js | 119 +++++++++++++++++++++++++++++++++-------------------
enums.js | 11 ++++-
lz.js | 16 +++++++
main.js | 13 +++++-
playback.js | 106 +++++++++++++++++++++++++++++++---------------
port.js | 85 +++++++++++++++++++++++++++++++++++++
resize.js | 20 +++++++--
simulatecursor.js | 6 +--
spice.css | 1 -
spice.html | 71 ++++++++++++++++++++++---------
spice_auto.html | 15 ++++++-
spiceconn.js | 39 ++++++++++-------
spicedataview.js | 8 ++--
spicemsg.js | 24 +++++++++--
spicetype.js | 2 +-
thirdparty/jsbn.js | 6 +--
thirdparty/prng4.js | 6 +--
thirdparty/rng.js | 8 ++--
thirdparty/rsa.js | 6 +--
utils.js | 12 ++++++
webm.js | 1 +
wire.js | 2 +-
26 files changed, 449 insertions(+), 156 deletions(-)
diff --git a/README b/README
index 89d3747..6443000 100644
--- a/README
+++ b/README
@@ -5,7 +5,7 @@ Instructions and status as of August, 2016.
Requirements:
1. Modern Firefox or Chrome (IE will work, but badly)
-
+
2. A WebSocket proxy
websockify:
@@ -24,7 +24,7 @@ Optional:
With firefox, you can just open file:///your-path-to-spice.html-here
- With Chrome, you have to set a secret config flag to do that, or
+ With Chrome, you have to set a secret config flag to do that, or
serve the files from a web server.
diff --git a/TODO b/TODO
index 4d4b115..64fc326 100644
--- a/TODO
+++ b/TODO
@@ -6,7 +6,7 @@ Medium Tasks:
*only* about messages)
. Change the message processing to be able to handle
- an array of ArrayBuffers so we don't have to
+ an array of ArrayBuffers so we don't have to
use the combine function, which is presumed slow.
. Use the 'real' DataView if we have it
diff --git a/bitmap.js b/bitmap.js
index 03f5127..91278d7 100644
--- a/bitmap.js
+++ b/bitmap.js
@@ -26,25 +26,31 @@
function convert_spice_bitmap_to_web(context, spice_bitmap)
{
var ret;
- var offset, x;
+ var offset, x, src_offset = 0, src_dec = 0;
var u8 = new Uint8Array(spice_bitmap.data);
if (spice_bitmap.format != SPICE_BITMAP_FMT_32BIT &&
spice_bitmap.format != SPICE_BITMAP_FMT_RGBA)
return undefined;
+ if (!(spice_bitmap.flags & SPICE_BITMAP_FLAGS_TOP_DOWN))
+ {
+ src_offset = (spice_bitmap.y - 1 ) * spice_bitmap.stride;
+ src_dec = 2 * spice_bitmap.stride;
+ }
+
ret = context.createImageData(spice_bitmap.x, spice_bitmap.y);
- for (offset = 0; offset < (spice_bitmap.y * spice_bitmap.stride); )
- for (x = 0; x < spice_bitmap.x; x++, offset += 4)
+ for (offset = 0; offset < (spice_bitmap.y * spice_bitmap.stride); src_offset -= src_dec)
+ for (x = 0; x < spice_bitmap.x; x++, offset += 4, src_offset += 4)
{
- ret.data[offset + 0 ] = u8[offset + 2];
- ret.data[offset + 1 ] = u8[offset + 1];
- ret.data[offset + 2 ] = u8[offset + 0];
+ ret.data[offset + 0 ] = u8[src_offset + 2];
+ ret.data[offset + 1 ] = u8[src_offset + 1];
+ ret.data[offset + 2 ] = u8[src_offset + 0];
// FIXME - We effectively treat all images as having SPICE_IMAGE_FLAGS_HIGH_BITS_SET
if (spice_bitmap.format == SPICE_BITMAP_FMT_32BIT)
ret.data[offset + 3] = 255;
else
- ret.data[offset + 3] = u8[offset];
+ ret.data[offset + 3] = u8[src_offset];
}
return ret;
diff --git a/cursor.js b/cursor.js
index 296fbde..d3f4d55 100644
--- a/cursor.js
+++ b/cursor.js
@@ -118,7 +118,7 @@ SpiceCursorConn.prototype.process_channel_message = function(msg)
SpiceCursorConn.prototype.set_cursor = function(cursor)
{
var pngstr = create_rgba_png(cursor.header.height, cursor.header.width, cursor.data);
- var curstr = 'url(data:image/png,' + pngstr + ') ' +
+ var curstr = 'url(data:image/png,' + pngstr + ') ' +
cursor.header.hot_spot_x + ' ' + cursor.header.hot_spot_y + ", default";
var screen = document.getElementById(this.parent.screen_id);
screen.style.cursor = 'auto';
diff --git a/display.js b/display.js
index 12fbab0..f6c74f5 100644
--- a/display.js
+++ b/display.js
@@ -142,7 +142,7 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
{ base: draw_copy.base,
src_area: draw_copy.data.src_area,
image_data: this.cache[draw_copy.data.src_bitmap.descriptor.id],
- tag: "copycache." + draw_copy.data.src_bitmap.descriptor.id,
+ tag: "copycache." + draw_copy.data.src_bitmap.descriptor.id,
has_alpha: true, /* FIXME - may want this to be false... */
descriptor : draw_copy.data.src_bitmap.descriptor
});
@@ -200,7 +200,7 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
tmpstr += qdv[i].toString(16);
}
- img.o =
+ img.o =
{ base: draw_copy.base,
tag: "jpeg." + draw_copy.data.src_bitmap.surface_id,
descriptor : draw_copy.data.src_bitmap.descriptor,
@@ -233,7 +233,7 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
tmpstr += qdv[i].toString(16);
}
- img.o =
+ img.o =
{ base: draw_copy.base,
tag: "jpeg." + draw_copy.data.src_bitmap.surface_id,
descriptor : draw_copy.data.src_bitmap.descriptor,
@@ -265,7 +265,7 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
draw_copy.data.src_bitmap.bitmap);
if (! source_img)
{
- this.log_warn("FIXME: Unable to interpret bitmap of format: " +
+ this.log_warn("FIXME: Unable to interpret bitmap of format: " +
draw_copy.data.src_bitmap.bitmap.format);
return false;
}
@@ -288,14 +288,11 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
return false;
}
- if (draw_copy.data.src_bitmap.lz_rgb.top_down != 1)
- this.log_warn("FIXME: Implement non top down support for lz_rgb");
-
var source_img = convert_spice_lz_to_web(canvas.context,
draw_copy.data.src_bitmap.lz_rgb);
if (! source_img)
{
- this.log_warn("FIXME: Unable to interpret bitmap of type: " +
+ this.log_warn("FIXME: Unable to interpret bitmap of type: " +
draw_copy.data.src_bitmap.lz_rgb.type);
return false;
}
@@ -359,7 +356,7 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
draw_fill.base.box.bottom - draw_fill.base.box.top);
document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
}
-
+
this.surfaces[draw_fill.base.surface_id].draw_count++;
}
@@ -484,9 +481,9 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
this.surfaces = [];
var m = new SpiceMsgSurfaceCreate(msg.data);
- DEBUG > 1 && console.log(this.type + ": MsgSurfaceCreate id " + m.surface.surface_id
+ DEBUG > 1 && console.log(this.type + ": MsgSurfaceCreate id " + m.surface.surface_id
+ "; " + m.surface.width + "x" + m.surface.height
- + "; format " + m.surface.format
+ + "; format " + m.surface.format
+ "; flags " + m.surface.flags);
if (m.surface.format != SPICE_SURFACE_FMT_32_xRGB &&
m.surface.format != SPICE_SURFACE_FMT_32_ARGB)
@@ -535,7 +532,10 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
if (msg.type == SPICE_MSG_DISPLAY_STREAM_CREATE)
{
var m = new SpiceMsgDisplayStreamCreate(msg.data);
- DEBUG > 1 && console.log(this.type + ": MsgStreamCreate id" + m.id);
+ STREAM_DEBUG > 0 && console.log(this.type + ": MsgStreamCreate id" + m.id + "; type " + m.codec_type +
+ "; width " + m.stream_width + "; height " + m.stream_height +
+ "; left " + m.dest.left + "; top " + m.dest.top
+ );
if (!this.streams)
this.streams = new Array();
if (this.streams[m.id])
@@ -567,14 +567,21 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
media.addEventListener('sourceended', handle_video_source_ended, false);
media.addEventListener('sourceclosed', handle_video_source_closed, false);
- this.streams[m.id].video = v;
- this.streams[m.id].media = media;
+ var s = this.streams[m.id];
+ s.video = v;
+ s.media = media;
+ s.queue = new Array();
+ s.start_time = 0;
+ s.cluster_time = 0;
+ s.append_okay = false;
- media.stream = this.streams[m.id];
+ media.stream = s;
media.spiceconn = this;
- v.spice_stream = this.streams[m.id];
+ v.spice_stream = s;
}
- else if (m.codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG)
+ else if (m.codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG)
+ this.streams[m.id].frames_loading = 0;
+ else
console.log("Unhandled stream codec: "+m.codec_type);
return true;
}
@@ -623,7 +630,7 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
if (msg.type == SPICE_MSG_DISPLAY_STREAM_CLIP)
{
var m = new SpiceMsgDisplayStreamClip(msg.data);
- DEBUG > 1 && console.log(this.type + ": MsgStreamClip id" + m.id);
+ STREAM_DEBUG > 1 && console.log(this.type + ": MsgStreamClip id" + m.id);
this.streams[m.id].clip = m.clip;
return true;
}
@@ -631,7 +638,7 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg)
if (msg.type == SPICE_MSG_DISPLAY_STREAM_DESTROY)
{
var m = new SpiceMsgDisplayStreamDestroy(msg.data);
- DEBUG > 1 && console.log(this.type + ": MsgStreamDestroy id" + m.id);
+ STREAM_DEBUG > 0 && console.log(this.type + ": MsgStreamDestroy id" + m.id);
if (this.streams[m.id].codec_type == SPICE_VIDEO_CODEC_TYPE_VP8)
{
@@ -745,7 +752,7 @@ SpiceDisplayConn.prototype.draw_copy_helper = function(o)
SpiceDisplayConn.prototype.log_draw = function(prefix, draw)
{
var str = prefix + "." + draw.base.surface_id + "." + this.surfaces[draw.base.surface_id].draw_count + ": ";
- str += "base.box " + draw.base.box.left + ", " + draw.base.box.top + " to " +
+ str += "base.box " + draw.base.box.left + ", " + draw.base.box.top + " to " +
draw.base.box.right + ", " + draw.base.box.bottom;
str += "; clip.type " + draw.base.clip.type;
@@ -762,13 +769,19 @@ SpiceDisplayConn.prototype.log_draw = function(prefix, draw)
str += "; src_bitmap type " + draw.data.src_bitmap.descriptor.type + ", flags " + draw.data.src_bitmap.descriptor.flags;
if (draw.data.src_bitmap.surface_id !== undefined)
str += "; src_bitmap surface_id " + draw.data.src_bitmap.surface_id;
+ if (draw.data.src_bitmap.bitmap)
+ str += "; BITMAP format " + draw.data.src_bitmap.bitmap.format +
+ "; flags " + draw.data.src_bitmap.bitmap.flags +
+ "; x " + draw.data.src_bitmap.bitmap.x +
+ "; y " + draw.data.src_bitmap.bitmap.y +
+ "; stride " + draw.data.src_bitmap.bitmap.stride ;
if (draw.data.src_bitmap.quic)
- str += "; QUIC type " + draw.data.src_bitmap.quic.type +
- "; width " + draw.data.src_bitmap.quic.width +
+ str += "; QUIC type " + draw.data.src_bitmap.quic.type +
+ "; width " + draw.data.src_bitmap.quic.width +
"; height " + draw.data.src_bitmap.quic.height ;
if (draw.data.src_bitmap.lz_rgb)
str += "; LZ_RGB length " + draw.data.src_bitmap.lz_rgb.length +
- "; magic " + draw.data.src_bitmap.lz_rgb.magic +
+ "; magic " + draw.data.src_bitmap.lz_rgb.magic +
"; version 0x" + draw.data.src_bitmap.lz_rgb.version.toString(16) +
"; type " + draw.data.src_bitmap.lz_rgb.type +
"; width " + draw.data.src_bitmap.lz_rgb.width +
@@ -876,6 +889,9 @@ function handle_draw_jpeg_onload()
var temp_canvas = null;
var context;
+ if (this.o.sc.streams[this.o.id])
+ this.o.sc.streams[this.o.id].frames_loading--;
+
/*------------------------------------------------------------
** FIXME:
** The helper should be extended to be able to handle actual HtmlImageElements
@@ -885,7 +901,7 @@ function handle_draw_jpeg_onload()
{
// This can happen; if the jpeg image loads after our surface
// has been destroyed (e.g. open a menu, close it quickly),
- // we'll find we have no surface.
+ // we'll find we have no surface.
DEBUG > 2 && this.o.sc.log_info("Discarding jpeg; presumed lost surface " + this.o.base.surface_id);
temp_canvas = document.createElement("canvas");
temp_canvas.setAttribute('width', this.o.base.box.right);
@@ -904,16 +920,16 @@ function handle_draw_jpeg_onload()
t.putImageData(this.alpha_img, 0, 0);
t.globalCompositeOperation = 'source-in';
t.drawImage(this, 0, 0);
-
+
context.drawImage(c, this.o.base.box.left, this.o.base.box.top);
- if (this.o.descriptor &&
+ if (this.o.descriptor &&
(this.o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
{
if (! ("cache" in this.o.sc))
this.o.sc.cache = {};
- this.o.sc.cache[this.o.descriptor.id] =
+ this.o.sc.cache[this.o.descriptor.id] =
t.getImageData(0, 0,
this.alpha_img.width,
this.alpha_img.height);
@@ -925,15 +941,16 @@ function handle_draw_jpeg_onload()
// Give the Garbage collector a clue to recycle this; avoids
// fairly massive memory leaks during video playback
- this.src = null;
+ this.onload = undefined;
+ this.src = EMPTY_GIF_IMAGE;
- if (this.o.descriptor &&
+ if (this.o.descriptor &&
(this.o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
{
if (! ("cache" in this.o.sc))
this.o.sc.cache = {};
- this.o.sc.cache[this.o.descriptor.id] =
+ this.o.sc.cache[this.o.descriptor.id] =
context.getImageData(this.o.base.box.left, this.o.base.box.top,
this.o.base.box.right - this.o.base.box.left,
this.o.base.box.bottom - this.o.base.box.top);
@@ -955,13 +972,16 @@ function handle_draw_jpeg_onload()
this.o.sc.surfaces[this.o.base.surface_id].draw_count++;
}
- if ("report" in this.o.sc.streams[this.o.id])
- process_stream_data_report(this.o.sc, this.o.id, this.o.msg_mmtime, this.o.msg_mmtime - this.o.sc.parent.relative_now())
+ if (this.o.sc.streams[this.o.id] && "report" in this.o.sc.streams[this.o.id])
+ process_stream_data_report(this.o.sc, this.o.id, this.o.msg_mmtime, this.o.msg_mmtime - this.o.sc.parent.relative_now());
}
function process_mjpeg_stream_data(sc, m, time_until_due)
{
- if (time_until_due < 0)
+ /* If we are currently processing an mjpeg frame when a new one arrives,
+ and the new one is 'late', drop the new frame. This helps the browsers
+ keep up, and provides rate control feedback as well */
+ if (time_until_due < 0 && sc.streams[m.base.id].frames_loading > 0)
{
if ("report" in sc.streams[m.base.id])
sc.streams[m.base.id].report.num_drops++;
@@ -992,6 +1012,8 @@ function process_mjpeg_stream_data(sc, m, time_until_due)
};
img.onload = handle_draw_jpeg_onload;
img.src = tmpstr;
+
+ sc.streams[m.base.id].frames_loading++;
}
function process_stream_data_report(sc, id, msg_mmtime, time_until_due)
@@ -1035,10 +1057,6 @@ function handle_video_source_open(e)
s.spiceconn = p;
s.stream = stream;
- stream.queue = new Array();
- stream.start_time = 0;
- stream.cluster_time = 0;
-
listen_for_video_events(stream);
var h = new webm_Header();
@@ -1102,6 +1120,24 @@ function handle_append_video_buffer_done(e)
{
stream.append_okay = true;
}
+
+ if (!stream.video)
+ {
+ if (STREAM_DEBUG > 0)
+ console.log("Stream id " + stream.id + " received updateend after video is gone.");
+ return;
+ }
+
+ if (stream.video.buffered.length > 0 &&
+ stream.video.currentTime < stream.video.buffered.start(stream.video.buffered.length - 1))
+ {
+ console.log("Video appears to have fallen behind; advancing to " +
+ stream.video.buffered.start(stream.video.buffered.length - 1));
+ stream.video.currentTime = stream.video.buffered.start(stream.video.buffered.length - 1);
+ }
+
+ if (STREAM_DEBUG > 1)
+ console.log(stream.video.currentTime + ":id " + stream.id + " updateend " + dump_media_element(stream.video));
}
function handle_video_buffer_error(e)
@@ -1153,13 +1189,9 @@ function new_video_cluster(stream, msg)
function process_video_stream_data(stream, msg)
{
- if (! stream.source_buffer)
- return true;
-
if (stream.start_time == 0)
{
stream.start_time = msg.base.multi_media_time;
- stream.video.play();
new_video_cluster(stream, msg);
}
@@ -1185,7 +1217,7 @@ function video_handle_event_debug(e)
if (STREAM_DEBUG > 1 && s.source_buffer)
console.log(" source_buffer " + dump_source_buffer(s.source_buffer));
- if (STREAM_DEBUG > 0 || s.queue.length > 1)
+ if (STREAM_DEBUG > 1 || s.queue.length > 1)
console.log(' queue len ' + s.queue.length + '; append_okay: ' + s.append_okay);
}
@@ -1203,10 +1235,11 @@ function listen_for_video_events(stream)
var video_1_events = [
"loadstart", "suspend", "emptied", "stalled", "loadedmetadata", "loadeddata", "canplay",
"canplaythrough", "playing", "waiting", "seeking", "seeked", "ended", "durationchange",
- "timeupdate", "play", "pause", "ratechange"
+ "play", "pause", "ratechange"
];
var video_2_events = [
+ "timeupdate",
"progress",
"resize",
"volumechange"
diff --git a/enums.js b/enums.js
index 3ef36dc..b6e013c 100644
--- a/enums.js
+++ b/enums.js
@@ -166,6 +166,15 @@ var SPICE_MSG_PLAYBACK_VOLUME = 105;
var SPICE_MSG_PLAYBACK_MUTE = 106;
var SPICE_MSG_PLAYBACK_LATENCY = 107;
+var SPICE_MSG_SPICEVMC_DATA = 101;
+var SPICE_MSG_PORT_INIT = 201;
+var SPICE_MSG_PORT_EVENT = 202;
+var SPICE_MSG_END_PORT = 203;
+
+var SPICE_MSGC_SPICEVMC_DATA = 101;
+var SPICE_MSGC_PORT_EVENT = 201;
+var SPICE_MSGC_END_PORT = 202;
+
var SPICE_PLAYBACK_CAP_CELT_0_5_1 = 0;
var SPICE_PLAYBACK_CAP_VOLUME = 1;
var SPICE_PLAYBACK_CAP_LATENCY = 2;
@@ -264,7 +273,7 @@ var SPICE_MOUSE_BUTTON_MASK_LEFT = (1 << 0),
SPICE_MOUSE_BUTTON_MASK_MIDDLE = (1 << 1),
SPICE_MOUSE_BUTTON_MASK_RIGHT = (1 << 2),
SPICE_MOUSE_BUTTON_MASK_MASK = 0x7;
-
+
var SPICE_MOUSE_BUTTON_INVALID = 0;
var SPICE_MOUSE_BUTTON_LEFT = 1;
var SPICE_MOUSE_BUTTON_MIDDLE = 2;
diff --git a/lz.js b/lz.js
index 4292eac..53c1141 100644
--- a/lz.js
+++ b/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/main.js b/main.js
index afe69bf..6976f9c 100644
--- a/main.js
+++ b/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,6 +59,7 @@ 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);
@@ -144,7 +145,13 @@ 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 (chans.channels[i].id == 0) {
+ this.display = new SpiceDisplayConn(conn);
+ } else {
+ this.log_warn("The spice-html5 client does not handle multiple heads.");
+ }
+ }
else if (chans.channels[i].type == SPICE_CHANNEL_INPUTS)
{
this.inputs = new SpiceInputsConn(conn);
@@ -154,6 +161,8 @@ 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
{
if (! ("extra_channels" in this))
diff --git a/playback.js b/playback.js
index 53b6983..5af9233 100644
--- a/playback.js
+++ b/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);
@@ -91,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.
- PLAYBACK_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);
}
- if (! this.source_buffer)
- return true;
+ /* 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.
- /* 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.
+ 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.
- 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.
+ 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;
-
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;
}
@@ -157,7 +158,22 @@ SpicePlaybackConn.prototype.process_channel_message = function(msg)
if (msg.type == SPICE_MSG_PLAYBACK_STOP)
{
- return true;
+ 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)
@@ -205,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);
@@ -273,6 +289,28 @@ function handle_source_closed(e)
p.log_err('Audio source unexpectedly closed.');
}
+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;
@@ -282,7 +320,7 @@ function handle_append_buffer_done(e)
if (p.queue.length > 0)
{
- var mb = p.queue.shift();
+ var mb = condense_playback_queue(p.queue);
playback_append_buffer(p, mb);
}
else
diff --git a/port.js b/port.js
new file mode 100644
index 0000000..ee22073
--- /dev/null
+++ b/port.js
@@ -0,0 +1,85 @@
+"use strict";
+/*
+ Copyright (C) 2016 by Oliver Gutierrez <ogutsua@gmail.com>
+ Miroslav Chodil <mchodil@redhat.com>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** 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/resize.js b/resize.js
index f5410d3..51fb1cc 100644
--- a/resize.js
+++ b/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/simulatecursor.js b/simulatecursor.js
index b1fce06..ffd9089 100644
--- a/simulatecursor.js
+++ b/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/spice.css b/spice.css
index 5d092ba..ee1b2f3 100644
--- a/spice.css
+++ b/spice.css
@@ -115,4 +115,3 @@ body
.spice-message-error {
color: red;
}
-
diff --git a/spice.html b/spice.html
index f2f9ed0..7abfcff 100644
--- a/spice.html
+++ b/spice.html
@@ -28,26 +28,27 @@
<head>
<title>Spice Javascript client</title>
- <script src="spicearraybuffer.js"></script>
- <script src="enums.js"></script>
- <script src="atKeynames.js"></script>
- <script src="utils.js"></script>
- <script src="png.js"></script>
- <script src="lz.js"></script>
- <script src="quic.js"></script>
- <script src="bitmap.js"></script>
- <script src="spicedataview.js"></script>
- <script src="spicetype.js"></script>
- <script src="spicemsg.js"></script>
- <script src="wire.js"></script>
- <script src="spiceconn.js"></script>
- <script src="display.js"></script>
- <script src="main.js"></script>
- <script src="inputs.js"></script>
+ <script src="spicearraybuffer.js"></script>
+ <script src="enums.js"></script>
+ <script src="atKeynames.js"></script>
+ <script src="utils.js"></script>
+ <script src="png.js"></script>
+ <script src="lz.js"></script>
+ <script src="quic.js"></script>
+ <script src="bitmap.js"></script>
+ <script src="spicedataview.js"></script>
+ <script src="spicetype.js"></script>
+ <script src="spicemsg.js"></script>
+ <script src="wire.js"></script>
+ <script src="spiceconn.js"></script>
+ <script src="display.js"></script>
+ <script src="port.js"></script>
+ <script src="main.js"></script>
+ <script src="inputs.js"></script>
<script src="webm.js"></script>
<script src="playback.js"></script>
<script src="simulatecursor.js"></script>
- <script src="cursor.js"></script>
+ <script src="cursor.js"></script>
<script src="thirdparty/jsbn.js"></script>
<script src="thirdparty/rsa.js"></script>
<script src="thirdparty/prng4.js"></script>
@@ -71,8 +72,8 @@
{
var host, port, password, scheme = "ws://", uri;
- host = document.getElementById("host").value;
- port = document.getElementById("port").value;
+ host = document.getElementById("host").value;
+ port = document.getElementById("port").value;
password = document.getElementById("password").value;
@@ -92,7 +93,7 @@
try
{
- sc = new SpiceMainConn({uri: uri, screen_id: "spice-screen", dump_id: "debug-div",
+ sc = new SpiceMainConn({uri: uri, screen_id: "spice-screen", dump_id: "debug-div",
message_id: "message-div", password: password, onerror: spice_error, onagent: agent_connected });
}
catch (e)
@@ -142,6 +143,35 @@
}
}
+ function toggle_console()
+ {
+ var checkbox = document.getElementById('show_console');
+ var m = document.getElementById('message-div');
+
+ if (checkbox.checked)
+ {
+ m.style.display = 'block';
+ }
+ else
+ {
+ m.style.display = 'none';
+ }
+
+ window.addEventListener('resize', handle_resize);
+ resize_helper(sc);
+ }
+ /* SPICE port event listeners
+ window.addEventListener('spice-port-data', function(event) {
+ // Here we convert data to text, but really we can obtain binary data also
+ var msg_text = arraybuffer_to_str(new Uint8Array(event.detail.data));
+ DEBUG > 0 && console.log('SPICE port', event.detail.channel.portName, 'message text:', msg_text);
+ });
+
+ window.addEventListener('spice-port-event', function(event) {
+ DEBUG > 0 && console.log('SPICE port', event.detail.channel.portName, 'event data:', event.detail.spiceEvent);
+ });
+ */
+
</script>
</head>
@@ -153,6 +183,7 @@
<label for="host">Host:</label> <input type='text' id='host' value='localhost'> <!-- localhost -->
<label for="port">Port:</label> <input type='text' id='port' value='5959'>
<label for="password">Password:</label> <input type='password' id='password' value=''>
+ <label for="show_console">Show console </label><input type="checkbox" id="show_console" value="1" onchange="toggle_console()" checked>
<button id="connectButton" onclick="connect();">Start</button>
</div>
diff --git a/spice_auto.html b/spice_auto.html
index 9aae118..2f04fc9 100644
--- a/spice_auto.html
+++ b/spice_auto.html
@@ -28,7 +28,7 @@
<head>
<title>Spice Javascript client</title>
- <script src="spicearraybuffer.js"></script>
+ <script src="spicearraybuffer.js"></script>
<script src="enums.js"></script>
<script src="atKeynames.js"></script>
<script src="utils.js"></script>
@@ -42,6 +42,7 @@
<script src="wire.js"></script>
<script src="spiceconn.js"></script>
<script src="display.js"></script>
+ <script src="port.js"></script>
<script src="main.js"></script>
<script src="inputs.js"></script>
<script src="webm.js"></script>
@@ -182,6 +183,18 @@
}
}
+ /* SPICE port event listeners
+ window.addEventListener('spice-port-data', function(event) {
+ // Here we convert data to text, but really we can obtain binary data also
+ var msg_text = arraybuffer_to_str(new Uint8Array(event.detail.data));
+ DEBUG > 0 && console.log('SPICE port', event.detail.channel.portName, 'message text:', msg_text);
+ });
+
+ window.addEventListener('spice-port-event', function(event) {
+ DEBUG > 0 && console.log('SPICE port', event.detail.channel.portName, 'event data:', event.detail.spiceEvent);
+ });
+ */
+
connect();
</script>
diff --git a/spiceconn.js b/spiceconn.js
index 41d2fa3..33e7388 100644
--- a/spiceconn.js
+++ b/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,28 +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)
- msg.channel_caps.push(
- (1 << SPICE_DISPLAY_CAP_SIZED_STREAM) |
- (1 << SPICE_DISPLAY_CAP_STREAM_REPORT) |
- (1 << SPICE_DISPLAY_CAP_MULTI_CODEC) |
- (1 << SPICE_DISPLAY_CAP_CODEC_MJPEG) |
- (1 << SPICE_DISPLAY_CAP_CODEC_VP8)
- );
+ {
+ 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();
@@ -188,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)
diff --git a/spicedataview.js b/spicedataview.js
index 800df03..719d968 100644
--- a/spicedataview.js
+++ b/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/spicemsg.js b/spicemsg.js
index db6625a..3619996 100644
--- a/spicemsg.js
+++ b/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
{
@@ -1278,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/spicetype.js b/spicetype.js
index 951b277..e145abc 100644
--- a/spicetype.js
+++ b/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/thirdparty/jsbn.js b/thirdparty/jsbn.js
index 9b9476e..d88ec54 100644
--- a/thirdparty/jsbn.js
+++ b/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/thirdparty/prng4.js b/thirdparty/prng4.js
index 4715372..ef3efd6 100644
--- a/thirdparty/prng4.js
+++ b/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/thirdparty/rng.js b/thirdparty/rng.js
index 829a23c..efbf382 100644
--- a/thirdparty/rng.js
+++ b/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/thirdparty/rsa.js b/thirdparty/rsa.js
index 1bbf249..ea0e45b 100644
--- a/thirdparty/rsa.js
+++ b/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/utils.js b/utils.js
index 9093a24..7aeefdb 100644
--- a/utils.js
+++ b/utils.js
@@ -27,6 +27,11 @@ 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
@@ -100,6 +105,13 @@ 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 dependent keycodes match to what scancodes.
diff --git a/webm.js b/webm.js
index 8faa8e7..789da14 100644
--- a/webm.js
+++ b/webm.js
@@ -84,6 +84,7 @@ 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"';
diff --git a/wire.js b/wire.js
index 7407ce7..2c7f096 100644
--- a/wire.js
+++ b/wire.js
@@ -96,7 +96,7 @@ SpiceWireReader.prototype =
this.callback.call(this.sc, mb,
this.saved_msg_header || undefined);
}
-
+
},
request: function(n)