From b05a252d7c994592d59a9831ebef2b1e03a71028 Mon Sep 17 00:00:00 2001 From: catborise Date: Wed, 8 Aug 2018 14:00:35 +0300 Subject: [PATCH] spice-html5 updated. --- console/templates/console-spice.html | 7 +- static/js/spice-html5/atKeynames.js | 0 static/js/spice-html5/bitmap.js | 20 +- static/js/spice-html5/cursor.js | 20 +- static/js/spice-html5/display.js | 530 +++++++++++++++++-- static/js/spice-html5/enums.js | 31 +- static/js/spice-html5/filexfer.js | 2 +- static/js/spice-html5/inputs.js | 0 static/js/spice-html5/jsbn.js | 589 ---------------------- static/js/spice-html5/lz.js | 16 + static/js/spice-html5/main.js | 83 ++- static/js/spice-html5/playback.js | 196 +++++-- static/js/spice-html5/png.js | 0 static/js/spice-html5/port.js | 85 ++++ static/js/spice-html5/prng4.js | 79 --- static/js/spice-html5/quic.js | 1 + static/js/spice-html5/resize.js | 20 +- static/js/spice-html5/rng.js | 102 ---- static/js/spice-html5/rsa.js | 146 ------ static/js/spice-html5/sha1.js | 346 ------------- static/js/spice-html5/simulatecursor.js | 6 +- static/js/spice-html5/spice.css | 117 +++++ static/js/spice-html5/spicearraybuffer.js | 0 static/js/spice-html5/spiceconn.js | 55 +- static/js/spice-html5/spicedataview.js | 8 +- static/js/spice-html5/spicemsg.js | 101 +++- static/js/spice-html5/spicetype.js | 2 +- static/js/spice-html5/thirdparty/jsbn.js | 6 +- static/js/spice-html5/thirdparty/prng4.js | 6 +- static/js/spice-html5/thirdparty/rng.js | 8 +- static/js/spice-html5/thirdparty/rsa.js | 6 +- static/js/spice-html5/thirdparty/sha1.js | 0 static/js/spice-html5/ticket.js | 0 static/js/spice-html5/utils.js | 71 ++- static/js/spice-html5/webm.js | 110 +++- static/js/spice-html5/wire.js | 2 +- 36 files changed, 1356 insertions(+), 1415 deletions(-) mode change 100644 => 100755 static/js/spice-html5/atKeynames.js mode change 100644 => 100755 static/js/spice-html5/bitmap.js mode change 100644 => 100755 static/js/spice-html5/cursor.js mode change 100644 => 100755 static/js/spice-html5/display.js mode change 100644 => 100755 static/js/spice-html5/enums.js mode change 100644 => 100755 static/js/spice-html5/filexfer.js mode change 100644 => 100755 static/js/spice-html5/inputs.js delete mode 100644 static/js/spice-html5/jsbn.js mode change 100644 => 100755 static/js/spice-html5/lz.js mode change 100644 => 100755 static/js/spice-html5/main.js mode change 100644 => 100755 static/js/spice-html5/playback.js mode change 100644 => 100755 static/js/spice-html5/png.js create mode 100755 static/js/spice-html5/port.js delete mode 100644 static/js/spice-html5/prng4.js mode change 100644 => 100755 static/js/spice-html5/quic.js mode change 100644 => 100755 static/js/spice-html5/resize.js delete mode 100644 static/js/spice-html5/rng.js delete mode 100644 static/js/spice-html5/rsa.js delete mode 100644 static/js/spice-html5/sha1.js mode change 100644 => 100755 static/js/spice-html5/simulatecursor.js create mode 100755 static/js/spice-html5/spice.css mode change 100644 => 100755 static/js/spice-html5/spicearraybuffer.js mode change 100644 => 100755 static/js/spice-html5/spiceconn.js mode change 100644 => 100755 static/js/spice-html5/spicedataview.js mode change 100644 => 100755 static/js/spice-html5/spicemsg.js mode change 100644 => 100755 static/js/spice-html5/spicetype.js mode change 100644 => 100755 static/js/spice-html5/thirdparty/jsbn.js mode change 100644 => 100755 static/js/spice-html5/thirdparty/prng4.js mode change 100644 => 100755 static/js/spice-html5/thirdparty/rng.js mode change 100644 => 100755 static/js/spice-html5/thirdparty/rsa.js mode change 100644 => 100755 static/js/spice-html5/thirdparty/sha1.js mode change 100644 => 100755 static/js/spice-html5/ticket.js mode change 100644 => 100755 static/js/spice-html5/utils.js mode change 100644 => 100755 static/js/spice-html5/webm.js mode change 100644 => 100755 static/js/spice-html5/wire.js diff --git a/console/templates/console-spice.html b/console/templates/console-spice.html index 1a1cbf8..d19a8f3 100644 --- a/console/templates/console-spice.html +++ b/console/templates/console-spice.html @@ -31,6 +31,9 @@ + + + {% endblock %} {% block content %} @@ -61,7 +64,7 @@ { console.log(e); disconnect(); - if (e.message != undefined) { + if (e.message !== undefined) { log_error(e.message); } else { @@ -73,6 +76,7 @@ log_info(msg); } + function connect(uri,password) { // If a token variable is passed in, set the parameter in a cookie. @@ -90,6 +94,7 @@ { sc = new SpiceMainConn({uri: uri, password: password, screen_id: "spice_container", onsuccess: spice_success, onerror: spice_error, onagent: agent_connected }); + } catch (e) { diff --git a/static/js/spice-html5/atKeynames.js b/static/js/spice-html5/atKeynames.js old mode 100644 new mode 100755 diff --git a/static/js/spice-html5/bitmap.js b/static/js/spice-html5/bitmap.js old mode 100644 new mode 100755 index 03f5127..91278d7 --- a/static/js/spice-html5/bitmap.js +++ b/static/js/spice-html5/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/static/js/spice-html5/cursor.js b/static/js/spice-html5/cursor.js old mode 100644 new mode 100755 index 71e941d..d3f4d55 --- a/static/js/spice-html5/cursor.js +++ b/static/js/spice-html5/cursor.js @@ -73,6 +73,12 @@ SpiceCursorConn.prototype.process_channel_message = function(msg) return true; } + if (msg.type == SPICE_MSG_CURSOR_MOVE) + { + this.known_unimplemented(msg.type, "Cursor Move"); + return true; + } + if (msg.type == SPICE_MSG_CURSOR_HIDE) { DEBUG > 1 && console.log("SpiceMsgCursorHide"); @@ -80,6 +86,12 @@ SpiceCursorConn.prototype.process_channel_message = function(msg) return true; } + if (msg.type == SPICE_MSG_CURSOR_TRAIL) + { + this.known_unimplemented(msg.type, "Cursor Trail"); + return true; + } + if (msg.type == SPICE_MSG_CURSOR_RESET) { DEBUG > 1 && console.log("SpiceMsgCursorReset"); @@ -87,6 +99,12 @@ SpiceCursorConn.prototype.process_channel_message = function(msg) return true; } + if (msg.type == SPICE_MSG_CURSOR_INVAL_ONE) + { + this.known_unimplemented(msg.type, "Cursor Inval One"); + return true; + } + if (msg.type == SPICE_MSG_CURSOR_INVAL_ALL) { DEBUG > 1 && console.log("SpiceMsgCursorInvalAll"); @@ -100,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/static/js/spice-html5/display.js b/static/js/spice-html5/display.js old mode 100644 new mode 100755 index 814ada6..7719b23 --- a/static/js/spice-html5/display.js +++ b/static/js/spice-html5/display.js @@ -62,6 +62,12 @@ function SpiceDisplayConn() SpiceDisplayConn.prototype = Object.create(SpiceConn.prototype); SpiceDisplayConn.prototype.process_channel_message = function(msg) { + if (msg.type == SPICE_MSG_DISPLAY_MODE) + { + this.known_unimplemented(msg.type, "Display Mode"); + return true; + } + if (msg.type == SPICE_MSG_DISPLAY_MARK) { // FIXME - DISPLAY_MARK not implemented (may be hard or impossible) @@ -136,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 }); @@ -171,8 +177,6 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg) has_alpha: this.surfaces[draw_copy.data.src_bitmap.surface_id].format == SPICE_SURFACE_FMT_32_xRGB ? false : true, descriptor : draw_copy.data.src_bitmap.descriptor }); - - return true; } else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_JPEG) { @@ -196,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, @@ -229,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, @@ -261,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; } @@ -284,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; } @@ -355,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++; } @@ -366,6 +367,60 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg) return true; } + if (msg.type == SPICE_MSG_DISPLAY_DRAW_OPAQUE) + { + this.known_unimplemented(msg.type, "Display Draw Opaque"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_DRAW_BLEND) + { + this.known_unimplemented(msg.type, "Display Draw Blend"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_DRAW_BLACKNESS) + { + this.known_unimplemented(msg.type, "Display Draw Blackness"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_DRAW_WHITENESS) + { + this.known_unimplemented(msg.type, "Display Draw Whiteness"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_DRAW_INVERS) + { + this.known_unimplemented(msg.type, "Display Draw Invers"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_DRAW_ROP3) + { + this.known_unimplemented(msg.type, "Display Draw ROP3"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_DRAW_STROKE) + { + this.known_unimplemented(msg.type, "Display Draw Stroke"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_DRAW_TRANSPARENT) + { + this.known_unimplemented(msg.type, "Display Draw Transparent"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND) + { + this.known_unimplemented(msg.type, "Display Draw Alpha Blend"); + return true; + } + if (msg.type == SPICE_MSG_DISPLAY_COPY_BITS) { var copy_bits = new SpiceMsgDisplayCopyBits(msg.data); @@ -402,6 +457,18 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg) return true; } + if (msg.type == SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS) + { + this.known_unimplemented(msg.type, "Display Inval All Pixmaps"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_INVAL_PALETTE) + { + this.known_unimplemented(msg.type, "Display Inval Palette"); + return true; + } + if (msg.type == SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES) { this.known_unimplemented(msg.type, "Inval All Palettes"); @@ -414,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) @@ -465,58 +532,105 @@ 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]) - console.log("Stream already exists"); + console.log("Stream " + m.id + " already exists"); else this.streams[m.id] = m; - if (m.codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG) + + if (m.codec_type == SPICE_VIDEO_CODEC_TYPE_VP8) + { + var media = new MediaSource(); + var v = document.createElement("video"); + v.src = window.URL.createObjectURL(media); + + v.setAttribute('autoplay', true); + v.setAttribute('width', m.stream_width); + v.setAttribute('height', m.stream_height); + + var left = m.dest.left; + var top = m.dest.top; + if (this.surfaces[m.surface_id] !== undefined) + { + left += this.surfaces[m.surface_id].canvas.offsetLeft; + top += this.surfaces[m.surface_id].canvas.offsetTop; + } + document.getElementById(this.parent.screen_id).appendChild(v); + v.setAttribute('style', "position: absolute; top:" + top + "px; left:" + left + "px;"); + + media.addEventListener('sourceopen', handle_video_source_open, false); + media.addEventListener('sourceended', handle_video_source_ended, false); + media.addEventListener('sourceclosed', handle_video_source_closed, false); + + 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 = s; + media.spiceconn = this; + v.spice_stream = s; + } + 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; } - if (msg.type == SPICE_MSG_DISPLAY_STREAM_DATA) + if (msg.type == SPICE_MSG_DISPLAY_STREAM_DATA || + msg.type == SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) { - var m = new SpiceMsgDisplayStreamData(msg.data); + var m; + if (msg.type == SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) + m = new SpiceMsgDisplayStreamDataSized(msg.data); + else + m = new SpiceMsgDisplayStreamData(msg.data); + if (!this.streams[m.base.id]) { console.log("no stream for data"); return false; } + + var time_until_due = m.base.multi_media_time - this.parent.relative_now(); + if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_MJPEG) + process_mjpeg_stream_data(this, m, time_until_due); + + if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_VP8) + process_video_stream_data(this.streams[m.base.id], m); + + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT) + { + var m = new SpiceMsgDisplayStreamActivateReport(msg.data); + + var report = new SpiceMsgcDisplayStreamReport(m.stream_id, m.unique_id); + if (this.streams[m.stream_id]) { - var tmpstr = "data:image/jpeg,"; - var img = new Image; - var i; - for (i = 0; i < m.data.length; i++) - { - tmpstr += '%'; - if (m.data[i] < 16) - tmpstr += '0'; - tmpstr += m.data[i].toString(16); - } - var strm_base = new SpiceMsgDisplayBase(); - strm_base.surface_id = this.streams[m.base.id].surface_id; - strm_base.box = this.streams[m.base.id].dest; - strm_base.clip = this.streams[m.base.id].clip; - img.o = - { base: strm_base, - tag: "mjpeg." + m.base.id, - descriptor: null, - sc : this, - }; - img.onload = handle_draw_jpeg_onload; - img.src = tmpstr; + this.streams[m.stream_id].report = report; + this.streams[m.stream_id].max_window_size = m.max_window_size; + this.streams[m.stream_id].timeout_ms = m.timeout_ms } + return true; } 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; } @@ -524,10 +638,25 @@ 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) + { + document.getElementById(this.parent.screen_id).removeChild(this.streams[m.id].video); + this.streams[m.id].source_buffer = null; + this.streams[m.id].media = null; + this.streams[m.id].video = null; + } this.streams[m.id] = undefined; return true; } + + if (msg.type == SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL) + { + this.known_unimplemented(msg.type, "Display Stream Destroy All"); + return true; + } + if (msg.type == SPICE_MSG_DISPLAY_INVAL_LIST) { var m = new SpiceMsgDisplayInvalList(msg.data); @@ -539,6 +668,18 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg) return true; } + if (msg.type == SPICE_MSG_DISPLAY_MONITORS_CONFIG) + { + this.known_unimplemented(msg.type, "Display Monitors Config"); + return true; + } + + if (msg.type == SPICE_MSG_DISPLAY_DRAW_COMPOSITE) + { + this.known_unimplemented(msg.type, "Display Draw Composite"); + return true; + } + return false; } @@ -611,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; @@ -629,12 +770,12 @@ SpiceDisplayConn.prototype.log_draw = function(prefix, draw) if (draw.data.src_bitmap.surface_id !== undefined) str += "; src_bitmap surface_id " + draw.data.src_bitmap.surface_id; 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 + @@ -742,6 +883,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 @@ -751,7 +895,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); @@ -770,16 +914,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); @@ -791,15 +935,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); @@ -820,4 +965,283 @@ function handle_draw_jpeg_onload() this.o.sc.surfaces[this.o.base.surface_id].draw_count++; } + + 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 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++; + return; + } + + var tmpstr = "data:image/jpeg,"; + var img = new Image; + var i; + for (i = 0; i < m.data.length; i++) + { + tmpstr += '%'; + if (m.data[i] < 16) + tmpstr += '0'; + tmpstr += m.data[i].toString(16); + } + var strm_base = new SpiceMsgDisplayBase(); + strm_base.surface_id = sc.streams[m.base.id].surface_id; + strm_base.box = m.dest || sc.streams[m.base.id].dest; + strm_base.clip = sc.streams[m.base.id].clip; + img.o = + { base: strm_base, + tag: "mjpeg." + m.base.id, + descriptor: null, + sc : sc, + id : m.base.id, + msg_mmtime : m.base.multi_media_time, + }; + 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) +{ + sc.streams[id].report.num_frames++; + if (sc.streams[id].report.start_frame_mm_time == 0) + sc.streams[id].report.start_frame_mm_time = msg_mmtime; + + if (sc.streams[id].report.num_frames > sc.streams[id].max_window_size || + (msg_mmtime - sc.streams[id].report.start_frame_mm_time) > sc.streams[id].timeout_ms) + { + sc.streams[id].report.end_frame_mm_time = msg_mmtime; + sc.streams[id].report.last_frame_delay = time_until_due; + + var msg = new SpiceMiniData(); + msg.build_msg(SPICE_MSGC_DISPLAY_STREAM_REPORT, sc.streams[id].report); + sc.send_msg(msg); + + sc.streams[id].report.start_frame_mm_time = 0; + sc.streams[id].report.num_frames = 0; + sc.streams[id].report.num_drops = 0; + } +} + +function handle_video_source_open(e) +{ + var stream = this.stream; + var p = this.spiceconn; + + if (stream.source_buffer) + return; + + var s = this.addSourceBuffer(SPICE_VP8_CODEC); + if (! s) + { + p.log_err('Codec ' + SPICE_VP8_CODEC + ' not available.'); + return; + } + + stream.source_buffer = s; + s.spiceconn = p; + s.stream = stream; + + listen_for_video_events(stream); + + var h = new webm_Header(); + var te = new webm_VideoTrackEntry(this.stream.stream_width, this.stream.stream_height); + var t = new webm_Tracks(te); + + var mb = new ArrayBuffer(h.buffer_size() + t.buffer_size()) + + var b = h.to_buffer(mb); + t.to_buffer(mb, b); + + s.addEventListener('error', handle_video_buffer_error, false); + s.addEventListener('updateend', handle_append_video_buffer_done, false); + + append_video_buffer(s, mb); +} + +function handle_video_source_ended(e) +{ + var p = this.spiceconn; + p.log_err('Video source unexpectedly ended.'); +} + +function handle_video_source_closed(e) +{ + var p = this.spiceconn; + p.log_err('Video source unexpectedly closed.'); +} + +function append_video_buffer(sb, mb) +{ + try + { + sb.stream.append_okay = false; + sb.appendBuffer(mb); + } + catch (e) + { + var p = sb.spiceconn; + p.log_err("Error invoking appendBuffer: " + e.message); + } +} + +function handle_append_video_buffer_done(e) +{ + var stream = this.stream; + + if (stream.current_frame && "report" in stream) + { + var sc = this.stream.media.spiceconn; + var t = this.stream.current_frame.msg_mmtime; + process_stream_data_report(sc, stream.id, t, t - sc.parent.relative_now()); + } + + if (stream.queue.length > 0) + { + stream.current_frame = stream.queue.shift(); + append_video_buffer(stream.source_buffer, stream.current_frame.mb); + } + else + { + 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) +{ + var p = this.spiceconn; + p.log_err('source_buffer error ' + e.message); +} + +function push_or_queue(stream, msg, mb) +{ + var frame = + { + msg_mmtime : msg.base.multi_media_time, + }; + + if (stream.append_okay) + { + stream.current_frame = frame; + append_video_buffer(stream.source_buffer, mb); + } + else + { + frame.mb = mb; + stream.queue.push(frame); + } +} + +function video_simple_block(stream, msg, keyframe) +{ + var simple = new webm_SimpleBlock(msg.base.multi_media_time - stream.cluster_time, msg.data, keyframe); + var mb = new ArrayBuffer(simple.buffer_size()); + simple.to_buffer(mb); + + push_or_queue(stream, msg, mb); +} + +function new_video_cluster(stream, msg) +{ + stream.cluster_time = msg.base.multi_media_time; + var c = new webm_Cluster(stream.cluster_time - stream.start_time, msg.data); + + var mb = new ArrayBuffer(c.buffer_size()); + c.to_buffer(mb); + + push_or_queue(stream, msg, mb); + + video_simple_block(stream, msg, true); +} + +function process_video_stream_data(stream, msg) +{ + if (stream.start_time == 0) + { + stream.start_time = msg.base.multi_media_time; + new_video_cluster(stream, msg); + } + + else if (msg.base.multi_media_time - stream.cluster_time >= MAX_CLUSTER_TIME) + new_video_cluster(stream, msg); + else + video_simple_block(stream, msg, false); +} + +function video_handle_event_debug(e) +{ + var s = this.spice_stream; + if (s.video) + { + if (STREAM_DEBUG > 0 || s.video.buffered.len > 1) + console.log(s.video.currentTime + ":id " + s.id + " event " + e.type + + dump_media_element(s.video)); + } + + if (STREAM_DEBUG > 1 && s.media) + console.log(" media_source " + dump_media_source(s.media)); + + if (STREAM_DEBUG > 1 && s.source_buffer) + console.log(" source_buffer " + dump_source_buffer(s.source_buffer)); + + if (STREAM_DEBUG > 1 || s.queue.length > 1) + console.log(' queue len ' + s.queue.length + '; append_okay: ' + s.append_okay); +} + +function video_debug_listen_for_one_event(name) +{ + this.addEventListener(name, video_handle_event_debug); +} + +function listen_for_video_events(stream) +{ + var video_0_events = [ + "abort", "error" + ]; + + var video_1_events = [ + "loadstart", "suspend", "emptied", "stalled", "loadedmetadata", "loadeddata", "canplay", + "canplaythrough", "playing", "waiting", "seeking", "seeked", "ended", "durationchange", + "play", "pause", "ratechange" + ]; + + var video_2_events = [ + "timeupdate", + "progress", + "resize", + "volumechange" + ]; + + video_0_events.forEach(video_debug_listen_for_one_event, stream.video); + if (STREAM_DEBUG > 0) + video_1_events.forEach(video_debug_listen_for_one_event, stream.video); + if (STREAM_DEBUG > 1) + video_2_events.forEach(video_debug_listen_for_one_event, stream.video); } diff --git a/static/js/spice-html5/enums.js b/static/js/spice-html5/enums.js old mode 100644 new mode 100755 index 17d77cb..b6e013c --- a/static/js/spice-html5/enums.js +++ b/static/js/spice-html5/enums.js @@ -127,8 +127,13 @@ var SPICE_MSG_DISPLAY_DRAW_TRANSPARENT = 312; var SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND = 313; var SPICE_MSG_DISPLAY_SURFACE_CREATE = 314; var SPICE_MSG_DISPLAY_SURFACE_DESTROY = 315; +var SPICE_MSG_DISPLAY_STREAM_DATA_SIZED = 316; +var SPICE_MSG_DISPLAY_MONITORS_CONFIG = 317; +var SPICE_MSG_DISPLAY_DRAW_COMPOSITE = 318; +var SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT = 319; var SPICE_MSGC_DISPLAY_INIT = 101; +var SPICE_MSGC_DISPLAY_STREAM_REPORT = 102; var SPICE_MSG_INPUTS_INIT = 101; var SPICE_MSG_INPUTS_KEY_MODIFIERS = 102; @@ -161,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; @@ -171,6 +185,18 @@ var SPICE_MAIN_CAP_NAME_AND_UUID = 1; var SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS = 2; var SPICE_MAIN_CAP_SEAMLESS_MIGRATE = 3; +var SPICE_DISPLAY_CAP_SIZED_STREAM = 0; +var SPICE_DISPLAY_CAP_MONITORS_CONFIG = 1; +var SPICE_DISPLAY_CAP_COMPOSITE = 2; +var SPICE_DISPLAY_CAP_A8_SURFACE = 3; +var SPICE_DISPLAY_CAP_STREAM_REPORT = 4; +var SPICE_DISPLAY_CAP_LZ4_COMPRESSION = 5; +var SPICE_DISPLAY_CAP_PREF_COMPRESSION = 6; +var SPICE_DISPLAY_CAP_GL_SCANOUT = 7; +var SPICE_DISPLAY_CAP_MULTI_CODEC = 8; +var SPICE_DISPLAY_CAP_CODEC_MJPEG = 9; +var SPICE_DISPLAY_CAP_CODEC_VP8 = 10; + var SPICE_AUDIO_DATA_MODE_INVALID = 0; var SPICE_AUDIO_DATA_MODE_RAW = 1; var SPICE_AUDIO_DATA_MODE_CELT_0_5_1 = 2; @@ -188,6 +214,8 @@ var SPICE_CHANNEL_RECORD = 6; var SPICE_CHANNEL_TUNNEL = 7; var SPICE_CHANNEL_SMARTCARD = 8; var SPICE_CHANNEL_USBREDIR = 9; +var SPICE_CHANNEL_PORT = 10; +var SPICE_CHANNEL_WEBDAV = 11; var SPICE_SURFACE_FLAGS_PRIMARY = (1 << 0); @@ -245,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; @@ -310,6 +338,7 @@ var SPICE_CURSOR_TYPE_ALPHA = 0, SPICE_CURSOR_TYPE_COLOR32 = 6; var SPICE_VIDEO_CODEC_TYPE_MJPEG = 1; +var SPICE_VIDEO_CODEC_TYPE_VP8 = 2; var VD_AGENT_PROTOCOL = 1; var VD_AGENT_MAX_DATA_SIZE = 2048; diff --git a/static/js/spice-html5/filexfer.js b/static/js/spice-html5/filexfer.js old mode 100644 new mode 100755 index beabfd8..d850db2 --- a/static/js/spice-html5/filexfer.js +++ b/static/js/spice-html5/filexfer.js @@ -81,7 +81,7 @@ function handle_file_drop(e) e.preventDefault(); for (var i = files.length - 1; i >= 0; i--) { - if (files[i].type); // do not copy a directory + if (files[i].type) // do not copy a directory sc.file_xfer_start(files[i]); } diff --git a/static/js/spice-html5/inputs.js b/static/js/spice-html5/inputs.js old mode 100644 new mode 100755 diff --git a/static/js/spice-html5/jsbn.js b/static/js/spice-html5/jsbn.js deleted file mode 100644 index 9b9476e..0000000 --- a/static/js/spice-html5/jsbn.js +++ /dev/null @@ -1,589 +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. - */ - - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -function BigInteger(a,b,c) { - if(a != null) - if("number" == typeof a) this.fromNumber(a,b,c); - else if(b == null && "string" != typeof a) this.fromString(a,256); - else this.fromString(a,b); -} - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { this.fromRadix(s,b); return; } - this.t = 0; - this.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); - } - else - this[this.t-1] |= x<= this.DB) sh -= this.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - if(this.s < 0) return "-"+this.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return this.toRadix(b); - var km = (1< 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)