'use strict';
const RP_INIT_CONNECTION = 1;
const RP_UPDATE_DISPLAY_MODE = 2;
const RP_CLOSE_CONNECTION = 3;
const RP_GET_SYSTEM_PALETTE = 4;
const RP_GET_SYSTEM_PALETTE_RESULT = 5;
const RP_CREATE_STATE = 20;
const RP_DELETE_STATE = 21;
const RP_ENABLE_SYNC_DRAWING = 22;
const RP_DISABLE_SYNC_DRAWING = 23;
const RP_INVALIDATE_RECT = 24;
const RP_INVALIDATE_REGION = 25;
const RP_SET_OFFSETS = 40;
const RP_SET_HIGH_COLOR = 41;
const RP_SET_LOW_COLOR = 42;
const RP_SET_PEN_SIZE = 43;
const RP_SET_STROKE_MODE = 44;
const RP_SET_BLENDING_MODE = 45;
const RP_SET_PATTERN = 46;
const RP_SET_DRAWING_MODE = 47;
const RP_SET_FONT = 48;
const RP_SET_TRANSFORM = 49;
const RP_CONSTRAIN_CLIPPING_REGION = 60;
const RP_COPY_RECT_NO_CLIPPING = 61;
const RP_INVERT_RECT = 62;
const RP_DRAW_BITMAP = 63;
const RP_DRAW_BITMAP_RECTS = 64;
const RP_STROKE_ARC = 80;
const RP_STROKE_BEZIER = 81;
const RP_STROKE_ELLIPSE = 82;
const RP_STROKE_POLYGON = 83;
const RP_STROKE_RECT = 84;
const RP_STROKE_ROUND_RECT = 85;
const RP_STROKE_SHAPE = 86;
const RP_STROKE_TRIANGLE = 87;
const RP_STROKE_LINE = 88;
const RP_STROKE_LINE_ARRAY = 89;
const RP_FILL_ARC = 100;
const RP_FILL_BEZIER = 101;
const RP_FILL_ELLIPSE = 102;
const RP_FILL_POLYGON = 103;
const RP_FILL_RECT = 104;
const RP_FILL_ROUND_RECT = 105;
const RP_FILL_SHAPE = 106;
const RP_FILL_TRIANGLE = 107;
const RP_FILL_REGION = 108;
const RP_FILL_ARC_GRADIENT = 120;
const RP_FILL_BEZIER_GRADIENT = 121;
const RP_FILL_ELLIPSE_GRADIENT = 122;
const RP_FILL_POLYGON_GRADIENT = 123;
const RP_FILL_RECT_GRADIENT = 124;
const RP_FILL_ROUND_RECT_GRADIENT = 125;
const RP_FILL_SHAPE_GRADIENT = 126;
const RP_FILL_TRIANGLE_GRADIENT = 127;
const RP_FILL_REGION_GRADIENT = 128;
const RP_STROKE_POINT_COLOR = 140;
const RP_STROKE_LINE_1PX_COLOR = 141;
const RP_STROKE_RECT_1PX_COLOR = 142;
const RP_FILL_RECT_COLOR = 160;
const RP_FILL_REGION_COLOR_NO_CLIPPING = 161;
const RP_DRAW_STRING = 180;
const RP_DRAW_STRING_WITH_OFFSETS = 181;
const RP_DRAW_STRING_RESULT = 182;
const RP_STRING_WIDTH = 183;
const RP_STRING_WIDTH_RESULT = 184;
const RP_READ_BITMAP = 185;
const RP_READ_BITMAP_RESULT = 186;
const RP_SET_CURSOR = 200;
const RP_SET_CURSOR_VISIBLE = 201;
const RP_MOVE_CURSOR_TO = 202;
const RP_MOUSE_MOVED = 220;
const RP_MOUSE_DOWN = 221;
const RP_MOUSE_UP = 222;
const RP_MOUSE_WHEEL_CHANGED = 223;
const RP_KEY_DOWN = 240;
const RP_KEY_UP = 241;
const RP_UNMAPPED_KEY_DOWN = 242;
const RP_UNMAPPED_KEY_UP = 243;
const RP_MODIFIERS_CHANGED = 244;
const B_OP_COPY = 0;
const B_OP_OVER = 1;
const B_OP_ERASE = 2;
const B_OP_INVERT = 3;
const B_OP_ADD = 4;
const B_OP_SUBTRACT = 5;
const B_OP_BLEND = 6;
const B_OP_MIN = 7;
const B_OP_MAX = 8;
const B_OP_SELECT = 9;
const B_OP_ALPHA = 10;
const B_NO_COLOR_SPACE = 0x0000;
const B_RGB32 = 0x0008;
const B_RGBA32 = 0x2008;
const B_RGB24 = 0x0003;
const B_RGB16 = 0x0005;
const B_RGB15 = 0x0010;
const B_RGBA15 = 0x2010;
const B_CMAP8 = 0x0004;
const B_GRAY8 = 0x0002;
const B_GRAY1 = 0x0001;
const B_RGB32_BIG = 0x1008;
const B_RGBA32_BIG = 0x3008;
const B_RGB24_BIG = 0x1003;
const B_RGB16_BIG = 0x1005;
const B_RGB15_BIG = 0x1010;
const B_RGBA15_BIG = 0x3010;
const B_TRANSPARENT_MAGIC_CMAP8 = 0xff;
const B_TRANSPARENT_MAGIC_RGBA15 = 0x39ce;
const B_TRANSPARENT_MAGIC_RGBA15_BIG = 0xce39;
const B_TRANSPARENT_MAGIC_RGBA32 = 0xff777477;
const B_TRANSPARENT_MAGIC_RGBA32_BIG = 0x777477ff;
const B_PIXEL_ALPHA = 0;
const B_CONSTANT_ALPHA = 1;
const B_ALPHA_OVERLAY = 0;
const B_ALPHA_COMPOSITE = 1;
const B_GRADIENT_TYPE_LINEAR = 0;
const B_GRADIENT_TYPE_RADIAL = 1;
const B_GRADIENT_TYPE_RADIAL_FOCUS = 2;
const B_GRADIENT_TYPE_DIAMOND = 3;
const B_GRADIENT_TYPE_CONIC = 4;
const B_GRADIENT_TYPE_NONE = 5;
const B_SHAPE_OP_MOVE_TO = 0x80000000;
const B_SHAPE_OP_CLOSE = 0x40000000;
const B_SHAPE_OP_BEZIER_TO = 0x20000000;
const B_SHAPE_OP_LINE_TO = 0x10000000;
const B_SHAPE_OP_SMALL_ARC_TO_CCW = 0x08000000;
const B_SHAPE_OP_SMALL_ARC_TO_CW = 0x04000000;
const B_SHAPE_OP_LARGE_ARC_TO_CCW = 0x02000000;
const B_SHAPE_OP_LARGE_ARC_TO_CW = 0x01000000;
const B_ROUND_JOIN = 0;
const B_MITER_JOIN = 1;
const B_BEVEL_JOIN = 2;
const B_BUTT_JOIN = 3;
const B_SQUARE_JOIN = 4;
const B_ROUND_CAP = B_ROUND_JOIN;
const B_BUTT_CAP = B_BUTT_JOIN;
const B_SQUARE_CAP = B_SQUARE_JOIN;
const B_DEFAULT_MITER_LIMIT = 10;
const B_FIXED_SPACING = 3;
const B_ITALIC_FACE = 0x0001;
const B_BOLD_FACE = 0x0020;
const B_SHIFT_KEY = 0x00000001;
const B_COMMAND_KEY = 0x00000002;
const B_CONTROL_KEY = 0x00000004;
const B_CAPS_LOCK = 0x00000008;
const B_SCROLL_LOCK = 0x00000010;
const B_NUM_LOCK = 0x00000020;
const B_OPTION_KEY = 0x00000040;
const B_MENU_KEY = 0x00000080;
const B_LEFT_SHIFT_KEY = 0x00000100;
const B_RIGHT_SHIFT_KEY = 0x00000200;
const B_LEFT_COMMAND_KEY = 0x00000400;
const B_RIGHT_COMMAND_KEY = 0x00000800;
const B_LEFT_CONTROL_KEY = 0x00001000;
const B_RIGHT_CONTROL_KEY = 0x00002000;
const B_LEFT_OPTION_KEY = 0x00004000;
const B_RIGHT_OPTION_KEY = 0x00008000;
var gSession;
var gSystemPalette;
function StreamingDataView(buffer, littleEndian, byteOffset, byteLength)
{
this.buffer = buffer;
this.dataView = new DataView(buffer.buffer, byteOffset, byteLength);
this.position = 0;
this.littleEndian = littleEndian;
this.textDecoder = new TextDecoder('utf-8');
this.textEncoder = new TextEncoder();
}
StreamingDataView.prototype.rewind = function()
{
this.position = 0;
}
StreamingDataView.prototype.readInt8 = function()
{
return this.dataView.getInt8(this.position++);
}
StreamingDataView.prototype.readUint8 = function()
{
return this.dataView.getUint8(this.position++);
}
StreamingDataView.prototype.readInt16 = function()
{
var result = this.dataView.getInt16(this.position, this.littleEndian);
this.position += 2;
return result;
}
StreamingDataView.prototype.readUint16 = function()
{
var result = this.dataView.getUint16(this.position, this.littleEndian);
this.position += 2;
return result;
}
StreamingDataView.prototype.readInt32 = function()
{
var result = this.dataView.getInt32(this.position, this.littleEndian);
this.position += 4;
return result;
}
StreamingDataView.prototype.readUint32 = function()
{
var result = this.dataView.getUint32(this.position, this.littleEndian);
this.position += 4;
return result;
}
StreamingDataView.prototype.readFloat32 = function()
{
var result = this.dataView.getFloat32(this.position, this.littleEndian);
this.position += 4;
return result;
}
StreamingDataView.prototype.readFloat64 = function()
{
var result = this.dataView.getFloat64(this.position, this.littleEndian);
this.position += 8;
return result;
}
StreamingDataView.prototype.readString = function(length)
{
var where = this.dataView.byteOffset + this.position;
var part = this.buffer.slice(where, where + length);
var result = this.textDecoder.decode(part);
this.position += length;
return result;
}
StreamingDataView.prototype.readInto = function(typedArray)
{
var where = this.dataView.byteOffset + this.position;
typedArray.set(this.buffer.slice(where, where + typedArray.byteLength));
this.position += typedArray.byteLength;
}
StreamingDataView.prototype.writeInt8 = function(value)
{
this.dataView.setInt8(this.position++, value);
}
StreamingDataView.prototype.writeUint8 = function(value)
{
this.dataView.setUint8(this.position++, value);
}
StreamingDataView.prototype.writeInt16 = function(value)
{
this.dataView.setInt16(this.position, value, this.littleEndian);
this.position += 2;
}
StreamingDataView.prototype.writeUint16 = function(value)
{
this.dataView.setUint16(this.position, value, this.littleEndian);
this.position += 2;
}
StreamingDataView.prototype.writeInt32 = function(value)
{
this.dataView.setInt32(this.position, value, this.littleEndian);
this.position += 4;
}
StreamingDataView.prototype.writeUint32 = function(value)
{
this.dataView.setUint32(this.position, value, this.littleEndian);
this.position += 4;
}
StreamingDataView.prototype.writeFloat32 = function(value)
{
this.dataView.setFloat32(this.position, value, this.littleEndian);
this.position += 4;
}
StreamingDataView.prototype.writeFloat64 = function(value)
{
this.dataView.setFloat64(this.position, value, this.littleEndian);
this.position += 8;
}
StreamingDataView.prototype.writeString = function(string)
{
var encoded = this.textEncoder.encode(string);
this.writeUint32(encoded.length);
this.buffer.set(encoded, this.position);
this.position += encoded.length;
}
StreamingDataView.prototype.setUint32 = function(byteOffset, value)
{
this.dataView.setUint32(byteOffset, value, this.littleEndian);
}
StreamingDataView.prototype.pad = function(length)
{
this.buffer.fill(0, this.position, this.position + length);
this.position += length;
}
function RemotePoint(remoteMessage)
{
if (remoteMessage) {
this.readFrom(remoteMessage);
return;
}
this.x = 0;
this.y = 0;
}
RemotePoint.prototype.readFrom = function(remoteMessage)
{
this.x = remoteMessage.dataView.readFloat32();
this.y = remoteMessage.dataView.readFloat32();
return this;
}
RemotePoint.prototype.writeTo = function(remoteMessage)
{
remoteMessage.dataView.writeFloat32(this.x);
remoteMessage.dataView.writeFloat32(this.y);
return this;
}
function RemoteRect(remoteMessage)
{
if (remoteMessage) {
this.readFrom(remoteMessage);
return;
}
this.left = 0;
this.top = 0;
this.right = -1;
this.bottom = -1;
}
RemoteRect.prototype.readFrom = function(remoteMessage)
{
this.left = remoteMessage.dataView.readFloat32();
this.top = remoteMessage.dataView.readFloat32();
this.right = remoteMessage.dataView.readFloat32();
this.bottom = remoteMessage.dataView.readFloat32();
return this;
}
RemoteRect.prototype.width = function()
{
return this.right - this.left + 1;
}
RemoteRect.prototype.height = function()
{
return this.bottom - this.top + 1;
}
RemoteRect.prototype.integerWidth = function()
{
return Math.ceil(this.right - this.left);
}
RemoteRect.prototype.integerHeight = function()
{
return Math.ceil(this.bottom - this.top);
}
RemoteRect.prototype.centerX = function()
{
return this.left + this.width() / 2;
}
RemoteRect.prototype.centerY = function()
{
return this.top + this.height() / 2;
}
RemoteRect.prototype.apply = function(apply)
{
var left = Math.floor(this.left);
var top = Math.floor(this.top);
var right = Math.ceil(this.right);
var bottom = Math.ceil(this.bottom);
apply(left, top, right - left + 1, bottom - top + 1);
}
RemoteRect.prototype.applyAsEllipse = function(context, which)
{
context.beginPath();
context.ellipse(this.centerX(), this.centerY(), this.width() / 2,
this.height() / 2, 0, Math.PI * 2, false);
which.call(context);
}
function RemoteColor(remoteMessage)
{
if (remoteMessage) {
this.readFrom(remoteMessage);
return;
}
this.red = 0;
this.green = 0;
this.blue = 0;
this.alpha = 0;
}
RemoteColor.prototype.readFrom = function(remoteMessage)
{
this.red = remoteMessage.dataView.readUint8();
this.green = remoteMessage.dataView.readUint8();
this.blue = remoteMessage.dataView.readUint8();
this.alpha = remoteMessage.dataView.readUint8();
return this;
}
RemoteColor.prototype.fromUint32 = function(value)
{
this.red = value & 0xff;
this.green = value >> 8 & 0xff;
this.blue = value >> 16 & 0xff;
this.alpha = value >> 24 & 0xff;
return this;
}
RemoteColor.prototype.toColor = function(unsetAlpha)
{
return 'rgba(' + this.red + ', ' + this.green + ', ' + this.blue + ', '
+ (unsetAlpha ? 1 : this.alpha / 255) + ')';
}
RemoteColor.prototype.toUint32 = function(unsetAlpha)
{
return this.red | this.green << 8 | this.blue << 16
| (unsetAlpha ? 255 : this.alpha) << 24;
}
function RemoteFont(remoteMessage)
{
if (remoteMessage) {
this.readFrom(remoteMessage);
return;
}
this.direction = 0;
this.encoding = 0;
this.flags = 0;
this.spacing = 0;
this.shear = 0;
this.rotation = 0;
this.falseBoldWidth = 0;
this.size = 12;
this.face = 0;
this.family = 0;
this.style = 0;
}
RemoteFont.prototype.readFrom = function(remoteMessage)
{
this.direction = remoteMessage.dataView.readUint8();
this.encoding = remoteMessage.dataView.readUint8();
this.flags = remoteMessage.dataView.readUint32();
this.spacing = remoteMessage.dataView.readUint8();
this.shear = remoteMessage.dataView.readFloat32();
this.rotation = remoteMessage.dataView.readFloat32();
this.falseBoldWidth = remoteMessage.dataView.readFloat32();
this.size = remoteMessage.dataView.readFloat32();
this.face = remoteMessage.dataView.readUint16();
this.family = remoteMessage.dataView.readUint16();
this.style = remoteMessage.dataView.readUint16();
return this;
}
function RemoteTransform(remoteMessage)
{
if (remoteMessage) {
this.readFrom(remoteMessage);
return;
}
this.setIdentity();
}
RemoteTransform.prototype.readFrom = function(remoteMessage)
{
var isIdentity = remoteMessage.dataView.readUint8();
if (isIdentity) {
this.setIdentity();
return;
}
this.sx = remoteMessage.dataView.readFloat64();
this.shy = remoteMessage.dataView.readFloat64();
this.shx = remoteMessage.dataView.readFloat64();
this.sy = remoteMessage.dataView.readFloat64();
this.tx = remoteMessage.dataView.readFloat64();
this.ty = remoteMessage.dataView.readFloat64();
return this;
}
RemoteTransform.prototype.setIdentity = function()
{
this.sx = 1;
this.shy = 0;
this.shx = 0;
this.sy = 1;
this.tx = 0;
this.ty = 0;
return this;
}
RemoteTransform.prototype.isIdentity = function()
{
return this.sx == 1 && this.shy == 0 && this.shx == 0 && this.sy == 1
&& this.tx == 0 && this.ty == 0;
}
RemoteTransform.prototype.apply = function(context)
{
context.transform(this.sx, this.shy, this.shx, this.sy, this.tx,
this.ty);
}
function RemoteBitmap(remoteMessage, unsetAlpha, colorSpace, flags)
{
if (remoteMessage) {
this.readFrom(remoteMessage, unsetAlpha, colorSpace, flags);
return;
}
}
RemoteBitmap.prototype.readFrom = function(remoteMessage, unsetAlpha,
colorSpace, flags)
{
this.width = remoteMessage.dataView.readUint32();
this.height = remoteMessage.dataView.readUint32();
this.bytesPerRow = remoteMessage.dataView.readUint32();
if (colorSpace != undefined) {
this.colorSpace = colorSpace;
this.flags = flags;
} else {
this.colorSpace = remoteMessage.dataView.readUint32();
this.flags = remoteMessage.dataView.readUint32();
}
this.bitsLength = remoteMessage.dataView.readUint32();
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
if (this.width == 0 || this.height == 0)
return;
var context = this.canvas.getContext('2d');
var imageData = context.createImageData(this.width, this.height);
switch (this.colorSpace) {
case B_RGBA32:
remoteMessage.dataView.readInto(imageData.data);
var output = new Uint32Array(imageData.data.buffer);
for (var i = 0; i < imageData.data.length / 4; i++) {
output[i] = (output[i] & 0xff) << 16 | (output[i] >> 16 & 0xff)
| (output[i] & 0xff00ff00);
}
if (unsetAlpha) {
for (var i = 0; i < imageData.data.length / 4; i++)
output[i] |= 0xff000000;
}
break;
case B_RGB32:
remoteMessage.dataView.readInto(imageData.data);
var output = new Uint32Array(imageData.data.buffer);
for (var i = 0; i < imageData.data.length / 4; i++) {
output[i] = (output[i] & 0xff) << 16 | (output[i] >> 16 & 0xff)
| (output[i] & 0xff00) | 0xff000000;
if (!unsetAlpha && output[i] == B_TRANSPARENT_MAGIC_RGBA32)
output[i] &= 0x00ffffff;
}
break;
case B_RGB24:
var line = new Uint8Array(this.bytesPerRow);
var position = 0;
for (var y = 0; y < this.height; y++) {
remoteMessage.dataView.readInto(line);
for (var x = 0; x < this.width; x++) {
imageData.data[position++] = line[x * 3 + 2];
imageData.data[position++] = line[x * 3 + 1];
imageData.data[position++] = line[x * 3 + 0];
imageData.data[position++] = 255;
}
}
break;
case B_RGB16:
var lineBuffer = new Uint8Array(this.bytesPerRow);
var line = new Uint16Array(lineBuffer.buffer);
var position = 0;
for (var y = 0; y < this.height; y++) {
remoteMessage.dataView.readInto(lineBuffer);
for (var x = 0; x < this.width; x++) {
imageData.data[position++] = (line[x] & 0xf800) >> 8;
imageData.data[position++] = (line[x] & 0x07e0) >> 3;
imageData.data[position++] = (line[x] & 0x001f) << 3;
imageData.data[position++] = 255;
}
}
break;
case B_CMAP8:
var line = new Uint8Array(this.bytesPerRow);
var output = new Uint32Array(imageData.data.buffer);
var position = 0;
for (var y = 0; y < this.height; y++) {
remoteMessage.dataView.readInto(line);
for (var x = 0; x < this.width; x++)
output[position++] = gSystemPalette[line[x]];
}
break;
case B_GRAY8:
var source = new Uint8Array(this.bitsLength);
remoteMessage.dataView.readInto(source);
for (var i = 0; i < imageData.data.length / 4; i++) {
imageData.data[i * 4 + 0] = source[i];
imageData.data[i * 4 + 1] = source[i];
imageData.data[i * 4 + 2] = source[i];
imageData.data[i * 4 + 3] = 255;
}
break;
case B_GRAY1:
var source = new Uint8Array(this.bitsLength);
remoteMessage.dataView.readInto(source);
for (var i = 0; i < imageData.data.length / 4; i++) {
var value = (source[Math.floor(i / 8)] >> i % 8) & 1 ? 255 : 0;
imageData.data[i * 4 + 0] = value;
imageData.data[i * 4 + 1] = value;
imageData.data[i * 4 + 2] = value;
imageData.data[i * 4 + 3] = 255;
}
break;
default:
console.warn('color space not implemented: ' + this.colorSpace);
break;
}
context.putImageData(imageData, 0, 0);
return this;
}
function RemotePattern(remoteMessage)
{
this.data = new Uint8Array(8);
if (remoteMessage)
this.readFrom(remoteMessage);
else
this.data.fill(255);
}
RemotePattern.staticCanvas = document.createElement('canvas');
RemotePattern.staticCanvas.width = RemotePattern.staticCanvas.height = 8;
RemotePattern.staticContext = RemotePattern.staticCanvas.getContext('2d');
RemotePattern.staticImageData
= RemotePattern.staticContext.createImageData(8, 8);
RemotePattern.staticPixels
= new Uint32Array(RemotePattern.staticImageData.data.buffer);
RemotePattern.prototype.readFrom = function(remoteMessage)
{
remoteMessage.dataView.readInto(this.data);
return this;
}
RemotePattern.prototype.isSolid = function()
{
var common = this.data[0];
return this.data.every(function(value) { return value == common; });
}
RemotePattern.prototype.toPattern = function(context, lowColor, highColor)
{
for (var i = 0; i < this.data.length * 8; i++) {
RemotePattern.staticPixels[i]
= (this.data[i / 8 | 0] & 1 << 7 - i % 8) == 0
? lowColor : highColor;
}
RemotePattern.staticContext.putImageData(RemotePattern.staticImageData, 0,
0);
return context.createPattern(RemotePattern.staticCanvas, 'repeat');
}
function RemoteGradient(remoteMessage, context, unsetAlpha)
{
if (remoteMessage) {
this.readFrom(remoteMessage, context, unsetAlpha);
return;
}
this.gradient = '#00000000';
}
RemoteGradient.prototype.readFrom = function(remoteMessage, context, unsetAlpha)
{
this.type = remoteMessage.dataView.readUint32();
switch (this.type) {
case B_GRADIENT_TYPE_LINEAR:
var start = new RemotePoint(remoteMessage);
var end = new RemotePoint(remoteMessage);
this.gradient = context.createLinearGradient(start.x, start.y,
end.x, end.y);
break;
case B_GRADIENT_TYPE_RADIAL:
var center = new RemotePoint(remoteMessage);
var radius = remoteMessage.dataView.readFloat32();
this.gradient = context.createRadialGradient(center.x, center.y, 0,
center.x, center.y, radius);
break;
default:
console.warn('gradient type not implemented: ' + this.type);
this.gradient = 'black';
return this;
}
var stopCount = remoteMessage.dataView.readUint32();
for (var i = 0; i < stopCount; i++) {
var color = remoteMessage.readColor(unsetAlpha);
var offset = remoteMessage.dataView.readFloat32() / 255;
this.gradient.addColorStop(offset, color);
}
return this;
}
function RemoteShape(remoteMessage)
{
if (remoteMessage) {
this.readFrom(remoteMessage);
return;
}
this.opCount = 0;
this.ops = [];
this.pointCount = 0;
this.points = [];
}
RemoteShape.prototype.readFrom = function(remoteMessage)
{
this.bounds = new RemoteRect(remoteMessage);
this.opCount = remoteMessage.dataView.readUint32();
this.ops = new Array(this.opCount);
for (var i = 0; i < this.opCount; i++)
this.ops[i] = remoteMessage.dataView.readUint32();
this.pointCount = remoteMessage.dataView.readUint32();
this.points = new Array(this.pointCount);
for (var i = 0; i < this.pointCount; i++)
this.points[i] = new RemotePoint(remoteMessage);
return this;
}
RemoteShape.prototype.play = function(context)
{
var pointIndex = 0;
for (var i = 0; i < this.opCount; i++) {
var op = this.ops[i] & 0xff000000;
var count = this.ops[i] & 0x00ffffff;
if (op & B_SHAPE_OP_MOVE_TO) {
var point = this.points[pointIndex++];
context.moveTo(point.x, point.y);
}
if (op & B_SHAPE_OP_LINE_TO) {
for (var j = 0; j < count; j++) {
var point = this.points[pointIndex++];
context.lineTo(point.x, point.y);
}
}
if (op & B_SHAPE_OP_BEZIER_TO) {
for (var j = 0; j < count / 3; j++) {
var control1 = this.points[pointIndex++];
var control2 = this.points[pointIndex++];
var to = this.points[pointIndex++];
context.bezierCurveTo(control1.x, control1.y, control2.x,
control2.y, to.x, to.y);
}
}
if (op & (B_SHAPE_OP_LARGE_ARC_TO_CW | B_SHAPE_OP_LARGE_ARC_TO_CCW
| B_SHAPE_OP_SMALL_ARC_TO_CW | B_SHAPE_OP_SMALL_ARC_TO_CCW)) {
console.warn('shape op arc to not implemented');
for (var j = 0; j < count / 3; j++)
pointIndex++;
}
if (op & B_SHAPE_OP_CLOSE)
context.closePath();
}
}
function RemoteMessage(socket)
{
this.socket = socket;
}
RemoteMessage.staticRemoteColor = new RemoteColor();
RemoteMessage.prototype.allocate = function(bufferSize)
{
this.buffer = new Uint8Array(bufferSize);
this.dataView = new StreamingDataView(this.buffer, true);
}
RemoteMessage.prototype.ensureBufferSize = function(bufferSize)
{
if (this.buffer.byteLength < bufferSize)
this.allocate(bufferSize);
}
RemoteMessage.prototype.attach = function(buffer, byteOffset)
{
var bytesLeft = buffer.byteLength - byteOffset;
if (bytesLeft < 6)
return false;
this.buffer = buffer;
this.dataView = new StreamingDataView(this.buffer, true, byteOffset);
this.messageCode = this.dataView.readUint16();
this.messageSize = this.dataView.readUint32();
if (this.messageSize < 6)
throw false;
return this.messageSize <= bytesLeft;
}
RemoteMessage.prototype.code = function()
{
return this.messageCode;
}
RemoteMessage.prototype.size = function()
{
return this.messageSize;
}
RemoteMessage.prototype.start = function(code)
{
this.dataView.rewind();
this.dataView.writeUint16(code);
this.dataView.writeUint32(0);
}
RemoteMessage.prototype.flush = function()
{
this.dataView.setUint32(2, this.dataView.position);
this.socket.send(this.buffer.slice(0, this.dataView.position));
}
RemoteMessage.prototype.readColor = function(unsetAlpha)
{
return RemoteMessage.staticRemoteColor.readFrom(this).toColor(unsetAlpha);
}
function RemoteState(session, token)
{
this.session = session;
this.token = token;
this.lowColor = new RemoteColor().fromUint32(0xffffffff);
this.highColor = new RemoteColor().fromUint32(0xff000000);
this.penSize = 1.0;
this.lineCap = 'butt';
this.lineJoin = 'miter';
this.miterLimit = B_DEFAULT_MITER_LIMIT;
this.drawingMode = 'source-over';
this.pattern = new RemotePattern();
this.font = new RemoteFont();
this.transform = new RemoteTransform();
}
RemoteState.prototype.applyContext = function()
{
var context = this.session.context;
if (!this.invalidated && context.currentToken == this.token)
return;
this.session.removeClipping();
if (this.blendModesEnabled && this.constantAlpha)
context.globalAlpha = this.highColor.alpha / 255;
else
context.globalAlpha = 1;
var style;
if (this.pattern.isSolid()) {
style = this.pattern.data[0] == 0 ? this.lowColor : this.highColor;
if (this.invert)
style = style == this.lowColor ? 'transparent' : 'white';
else
style = style.toColor(this.unsetAlpha);
} else {
style = this.pattern.toPattern(context,
this.invert ? 0x00000000 : this.lowColor.toUint32(this.unsetAlpha),
this.invert
? 0xffffffff : this.highColor.toUint32(this.unsetAlpha));
}
context.fillStyle = context.strokeStyle = style;
context.font = (this.font.face & B_ITALIC_FACE ? "italic " : "")
+ (this.font.face & B_BOLD_FACE ? "bold " : "")
+ this.font.size + 'px '
+ (this.font.spacing==B_FIXED_SPACING ? 'monospace' : 'Helvetica');
context.globalCompositeOperation = this.drawingMode;
context.lineWidth = this.penSize;
context.lineCap = this.lineCap;
context.lineJoin = this.lineJoin;
context.miterLimit = this.miterLimit;
context.resetTransform();
this.session.applyClipping(this.clipRects);
if (!this.transform.isIdentity()) {
context.translate(this.xOffset, this.yOffset);
this.transform.apply(context);
context.translate(-this.xOffset, -this.yOffset);
}
context.currentToken = this.token;
this.invalidated = false;
}
RemoteState.prototype.prepareForRect = function()
{
this.session.context.lineJoin = 'miter';
this.session.context.miterLimit = 10;
}
RemoteState.prototype.messageReceived = function(remoteMessage, reply)
{
var context = this.session.context;
switch (remoteMessage.code()) {
case RP_ENABLE_SYNC_DRAWING:
case RP_DISABLE_SYNC_DRAWING:
console.warn('sync drawing en-/disable not implemented');
break;
case RP_SET_LOW_COLOR:
this.lowColor.readFrom(remoteMessage);
this.invalidated = true;
break;
case RP_SET_HIGH_COLOR:
this.highColor.readFrom(remoteMessage);
this.invalidated = true;
break;
case RP_SET_OFFSETS:
this.xOffset = remoteMessage.dataView.readInt32();
this.yOffset = remoteMessage.dataView.readInt32();
this.invalidated = true;
break;
case RP_SET_FONT:
this.font = new RemoteFont(remoteMessage);
this.invalidated = true;
break;
case RP_SET_TRANSFORM:
this.transform = new RemoteTransform(remoteMessage);
this.invalidated = true;
break;
case RP_SET_PATTERN:
this.pattern = new RemotePattern(remoteMessage);
this.invalidated = true;
break;
case RP_SET_PEN_SIZE:
this.penSize = remoteMessage.dataView.readFloat32();
this.invalidated = true;
break;
case RP_SET_STROKE_MODE:
switch (remoteMessage.dataView.readUint32()) {
case B_ROUND_CAP:
this.lineCap = 'round';
break;
case B_BUTT_CAP:
this.lineCap = 'butt';
break;
case B_SQUARE_CAP:
this.lineCap = 'square';
break;
}
var lineJoin = remoteMessage.dataView.readUint32();
switch (lineJoin) {
case B_ROUND_JOIN:
this.lineJoin = 'round';
break;
case B_MITER_JOIN:
this.lineJoin = 'miter';
break;
case B_BEVEL_JOIN:
this.lineJoin = 'bevel';
break;
default:
console.warn('line join not implemented: ' + join);
break;
}
this.miterLimit = remoteMessage.dataView.readFloat32();
this.invalidated = true;
break;
case RP_SET_BLENDING_MODE:
var sourceAlpha = remoteMessage.dataView.readUint32();
this.constantAlpha = sourceAlpha == B_CONSTANT_ALPHA;
if (this.blendModesEnabled)
this.unsetAlpha = this.constantAlpha;
var alphaFunction = remoteMessage.dataView.readUint32();
if (alphaFunction != B_ALPHA_OVERLAY)
console.warn('alpha function not supported: ' + alphaFunction);
this.invalidated = true;
break;
case RP_SET_DRAWING_MODE:
var drawingMode = remoteMessage.dataView.readUint32();
this.unsetAlpha = false;
this.blendModesEnabled = false;
this.invert = false;
switch (drawingMode) {
case B_OP_COPY:
this.unsetAlpha = true;
this.drawingMode = 'source-over';
break;
case B_OP_OVER:
this.drawingMode = 'source-over';
break;
case B_OP_ALPHA:
this.blendModesEnabled = true;
this.unsetAlpha = this.constantAlpha;
this.drawingMode = 'source-over';
break;
case B_OP_BLEND:
this.drawingMode = 'lighter';
break;
case B_OP_MIN:
this.drawingMode = 'darken';
break;
case B_OP_MAX:
this.drawingMode = 'ligthen';
break;
case B_OP_INVERT:
this.drawingMode = 'difference';
this.invert = true;
break;
case B_OP_ADD:
this.drawingMode = 'lighter';
break;
case B_OP_ERASE:
this.drawingMode = 'destination-out';
break;
case B_OP_SUBTRACT:
this.drawingMode = 'difference';
break;
*/
default:
console.warn('drawing mode not implemented: '
+ drawingMode);
this.drawingMode = 'source-over';
break;
}
this.invalidated = true;
break;
case RP_CONSTRAIN_CLIPPING_REGION:
var rectCount = remoteMessage.dataView.readUint32();
this.clipRects = new Array(rectCount);
for (var i = 0; i < rectCount; i++)
this.clipRects[i] = new RemoteRect(remoteMessage);
this.invalidated = true;
break;
case RP_INVERT_RECT:
this.applyContext();
var rect = new RemoteRect(remoteMessage);
context.save();
context.globalCompositeOperation = 'difference';
context.fillStyle = 'white';
this.prepareForRect();
rect.apply(context.fillRect.bind(context));
context.restore();
break;
case RP_DRAW_BITMAP:
this.applyContext();
var bitmapRect = new RemoteRect(remoteMessage);
var viewRect = new RemoteRect(remoteMessage);
var options = remoteMessage.dataView.readUint32();
if (options != 0)
console.warn('bitmap options not supported: ' + options);
var bitmap = new RemoteBitmap(remoteMessage, this.unsetAlpha);
context.drawImage(bitmap.canvas, bitmapRect.left, bitmapRect.top,
bitmapRect.width(), bitmapRect.height(), viewRect.left,
viewRect.top, viewRect.width(), viewRect.height());
break;
case RP_DRAW_BITMAP_RECTS:
this.applyContext();
var options = remoteMessage.dataView.readUint32();
var colorSpace = remoteMessage.dataView.readUint32();
var flags = remoteMessage.dataView.readUint32();
if (options != 0)
console.warn('bitmap options not supported: ' + options);
var rectCount = remoteMessage.dataView.readUint32();
for (var i = 0; i < rectCount; i++) {
var rect = new RemoteRect(remoteMessage);
var bitmap = new RemoteBitmap(remoteMessage, this.unsetAlpha,
colorSpace, flags);
context.drawImage(bitmap.canvas, 0, 0, bitmap.width,
bitmap.height, rect.left, rect.top, rect.width(),
rect.height());
}
break;
case RP_DRAW_STRING:
this.applyContext();
var where = new RemotePoint(remoteMessage);
var length = remoteMessage.dataView.readUint32();
var string = remoteMessage.dataView.readString(length);
context.save();
context.fillStyle = this.highColor.toColor(this.unsetAlpha);
context.fillText(string, where.x, where.y);
var textMetric = context.measureText(string);
where.x += textMetric.width;
context.restore();
reply.start(RP_DRAW_STRING_RESULT);
reply.dataView.writeInt32(this.token);
where.writeTo(reply);
reply.flush();
break;
case RP_DRAW_STRING_WITH_OFFSETS:
this.applyContext();
var length = remoteMessage.dataView.readUint32();
var string = remoteMessage.dataView.readString(length);
context.save();
context.fillStyle = this.highColor.toColor(this.unsetAlpha);
var where;
for (var i = 0; i < string.length; i++) {
where = new RemotePoint(remoteMessage);
context.fillText(string[i], where.x, where.y);
}
var textMetric = context.measureText(string[string.length - 1]);
where.x += textMetric.width;
context.restore();
reply.start(RP_DRAW_STRING_RESULT);
reply.dataView.writeInt32(this.token);
where.writeTo(reply);
reply.flush();
break;
case RP_STRING_WIDTH:
this.applyContext();
var length = remoteMessage.dataView.readUint32();
var string = remoteMessage.dataView.readString(length);
var textMetric = context.measureText(string);
reply.start(RP_STRING_WIDTH_RESULT);
reply.dataView.writeInt32(this.token);
where.writeFloat32(textMetric.width);
reply.flush();
break;
case RP_STROKE_ARC:
case RP_FILL_ARC:
this.applyContext();
var rect = new RemoteRect(remoteMessage);
var startAngle
= remoteMessage.dataView.readFloat32() * Math.PI / 180;
var invertStart = Math.PI * 2 - startAngle;
startAngle += Math.PI / 2;
var span = remoteMessage.dataView.readFloat32() * Math.PI / 180;
var centerX = Math.round(rect.centerX());
var centerY = Math.round(rect.centerY());
var radius = rect.width() / 2;
var maxSpan
= remoteMessage.code() != RP_STROKE_ARC ? Math.PI / 2 : span;
var arcStep = function(max) {
max = Math.min(max, span);
context.beginPath();
context.arc(centerX, centerY, radius, invertStart,
invertStart - max, true);
switch (remoteMessage.code()) {
case RP_STROKE_ARC:
context.stroke();
break;
case RP_FILL_ARC:
context.moveTo(centerX, centerY);
var endAngle = startAngle + max;
context.lineTo(
centerX + radius * Math.sin(startAngle),
centerY + radius * Math.cos(startAngle));
context.lineTo(
centerX + radius * Math.sin(endAngle),
centerY + radius * Math.cos(endAngle));
context.fill();
break;
}
startAngle += max;
invertStart -= max;
span -= max;
};
while (span > 0)
arcStep(maxSpan);
break;
case RP_STROKE_RECT:
case RP_STROKE_ELLIPSE:
case RP_FILL_RECT:
case RP_FILL_ELLIPSE:
this.applyContext();
context.save();
this.prepareForRect();
var rect = new RemoteRect(remoteMessage);
switch (remoteMessage.code()) {
case RP_STROKE_RECT:
rect.apply(context.strokeRect.bind(context));
break;
case RP_STROKE_ELLIPSE:
rect.applyAsEllipse(context, context.stroke);
break;
case RP_FILL_RECT:
rect.apply(context.fillRect.bind(context));
break;
case RP_FILL_ELLIPSE:
rect.applyAsEllipse(context, context.fill);
break;
}
context.restore();
break;
case RP_STROKE_ROUND_RECT:
case RP_FILL_ROUND_RECT:
case RP_FILL_ROUND_RECT_GRADIENT:
this.applyContext();
context.save();
this.prepareForRect();
var rect = new RemoteRect(remoteMessage);
var xRadius = remoteMessage.dataView.readFloat32();
var yRadius = remoteMessage.dataView.readFloat32();
if (remoteMessage.code() == RP_FILL_ROUND_RECT_GRADIENT) {
context.save();
var gradient = new RemoteGradient(remoteMessage, context,
this.unsetAlpha);
context.fillStyle = gradient.gradient;
}
console.warn('round rects not implemented, falling back to rect');
if (remoteMessage.code() == RP_STROKE_ROUND_RECT)
rect.apply(context.strokeRect.bind(context));
else
rect.apply(context.fillRect.bind(context));
if (remoteMessage.code() == RP_FILL_ROUND_RECT_GRADIENT)
context.restore();
context.restore();
break;
case RP_STROKE_LINE:
this.applyContext();
var from = new RemotePoint(remoteMessage);
var to = new RemotePoint(remoteMessage);
context.beginPath();
context.moveTo(from.x, from.y);
context.lineTo(to.x, to.y);
context.stroke();
break;
case RP_STROKE_LINE_ARRAY:
this.applyContext();
context.save();
context.lineCap = 'square';
var numLines = remoteMessage.dataView.readUint32();
for (var i = 0; i < numLines; i++) {
var from = new RemotePoint(remoteMessage);
var to = new RemotePoint(remoteMessage);
context.strokeStyle = remoteMessage.readColor(this.unsetAlpha);
context.beginPath();
context.moveTo(from.x + 0.5, from.y + 0.5);
context.lineTo(to.x + 0.5, to.y + 0.5);
context.stroke();
}
context.restore();
break;
case RP_STROKE_POINT_COLOR:
this.applyContext();
var point = new RemotePoint(remoteMessage);
context.save();
context.fillStyle = remoteMessage.readColor(this.unsetAlpha);
context.fillRect(point.x, point.y, 1, 1);
context.restore();
break;
case RP_STROKE_LINE_1PX_COLOR:
this.applyContext();
var from = new RemotePoint(remoteMessage);
var to = new RemotePoint(remoteMessage);
context.save();
context.strokeStyle = remoteMessage.readColor(this.unsetAlpha);
context.lineWidth = 1;
context.lineCap = 'square';
context.beginPath();
context.moveTo(from.x + 0.5, from.y + 0.5);
context.lineTo(to.x + 0.5, to.y + 0.5);
context.stroke();
context.restore();
break;
case RP_STROKE_RECT_1PX_COLOR:
this.applyContext();
var rect = new RemoteRect(remoteMessage);
context.save();
this.prepareForRect();
context.strokeStyle = remoteMessage.readColor(this.unsetAlpha);
context.lineWidth = 1;
rect.apply(context.strokeRect.bind(context));
context.restore();
break;
case RP_STROKE_SHAPE:
case RP_FILL_SHAPE:
case RP_FILL_SHAPE_GRADIENT:
this.applyContext();
var shape = new RemoteShape(remoteMessage);
var offset = new RemotePoint(remoteMessage);
var scale = remoteMessage.dataView.readFloat32();
context.save();
if (remoteMessage.code() == RP_FILL_SHAPE_GRADIENT) {
var gradient = new RemoteGradient(remoteMessage, context,
this.unsetAlpha);
context.fillStyle = gradient.gradient;
}
context.translate(offset.x + 0.5, offset.y + 0.5);
context.scale(scale, scale);
context.beginPath();
shape.play(context);
if (remoteMessage.code() == RP_STROKE_SHAPE)
context.stroke();
else
context.fill();
context.restore();
break;
case RP_STROKE_TRIANGLE:
case RP_FILL_TRIANGLE:
case RP_FILL_TRIANGLE_GRADIENT:
this.applyContext();
if (remoteMessage.code() == RP_FILL_TRIANGLE_GRADIENT)
context.save();
context.beginPath();
var point = new RemotePoint(remoteMessage);
context.moveTo(point.x + 0.5, point.y + 0.5);
for (var i = 0; i < 2; i++) {
point = new RemotePoint(remoteMessage);
context.lineTo(point.x + 0.5, point.y + 0.5);
}
if (remoteMessage.code() == RP_FILL_TRIANGLE_GRADIENT) {
var unusedBounds = new RemoteRect(remoteMessage);
var gradient = new RemoteGradient(remoteMessage, context,
this.unsetAlpha);
context.fillStyle = gradient.gradient;
}
switch (remoteMessage.code()) {
case RP_STROKE_TRIANGLE:
context.closePath();
context.stroke();
break;
case RP_FILL_TRIANGLE:
context.fill();
break;
case RP_FILL_TRIANGLE_GRADIENT:
context.fill();
context.restore();
break;
}
break;
case RP_FILL_RECT_COLOR:
this.applyContext();
var rect = new RemoteRect(remoteMessage);
context.save();
this.prepareForRect();
context.fillStyle = remoteMessage.readColor(this.unsetAlpha);
rect.apply(context.fillRect.bind(context));
context.restore();
break;
case RP_FILL_RECT_GRADIENT:
case RP_FILL_ELLIPSE_GRADIENT:
this.applyContext();
var rect = new RemoteRect(remoteMessage);
context.save();
this.prepareForRect();
var gradient = new RemoteGradient(remoteMessage, context,
this.unsetAlpha);
context.fillStyle = gradient.gradient;
if (remoteMessage.code() == RP_FILL_RECT_GRADIENT)
rect.apply(context.fillRect.bind(context));
else
rect.applyAsEllipse(context, context.fill);
context.restore();
break;
case RP_FILL_REGION:
case RP_FILL_REGION_GRADIENT:
this.applyContext();
var rectCount = remoteMessage.dataView.readUint32();
var rects = new Array(rectCount);
for (var i = 0; i < rectCount; i++)
rects[i] = new RemoteRect(remoteMessage);
if (remoteMessage.code() == RP_FILL_REGION_GRADIENT) {
context.save();
var gradient = new RemoteGradient(remoteMessage, context,
this.unsetAlpha);
context.fillStyle = gradient.gradient;
}
for (var i = 0; i < rectCount; i++)
rects[i].apply(context.fillRect.bind(context));
if (remoteMessage.code() == RP_FILL_REGION_GRADIENT)
context.restore();
break;
case RP_READ_BITMAP:
var bounds = new RemoteRect(remoteMessage);
var drawCursor = remoteMessage.dataView.readUint8();
if (drawCursor)
console.warn('draw cursor in read bitmap not supported');
var width = bounds.integerWidth() + 1;
var height = bounds.integerHeight() + 1;
var bytesPerPixel = 3;
var bytesPerRow = (width * bytesPerPixel + 3) & ~7;
var padding = bytesPerRow - width * bytesPerPixel;
var bitsLength = height * bytesPerRow;
reply.ensureBufferSize(bitsLength + 1024);
reply.start(RP_READ_BITMAP_RESULT);
reply.dataView.writeInt32(this.token);
reply.dataView.writeInt32(width);
reply.dataView.writeInt32(height);
reply.dataView.writeInt32(bytesPerRow);
reply.dataView.writeUint32(B_RGB24);
reply.dataView.writeUint32(0);
reply.dataView.writeUint32(bitsLength);
var position = 0;
var imageData
= context.getImageData(bounds.left, bounds.top, width, height);
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++, position += 4) {
reply.dataView.writeUint8(imageData.data[position + 2]);
reply.dataView.writeUint8(imageData.data[position + 1]);
reply.dataView.writeUint8(imageData.data[position + 0]);
}
reply.dataView.pad(padding);
}
reply.flush();
break;
default:
console.warn('unhandled message: code: ' + remoteMessage.code()
+ '; size: ' + remoteMessage.size());
break;
}
}
function RemoteDesktopSession(targetElement, width, height, targetAddress,
disconnectCallback)
{
this.websocket = new WebSocket(targetAddress, 'binary');
this.websocket.binaryType = 'arraybuffer';
this.websocket.onopen = this.onOpen.bind(this);
this.websocket.onmessage = this.onMessage.bind(this);
this.websocket.onerror = this.onError.bind(this);
this.websocket.onclose = this.onClose.bind(this);
this.disconnectCallback = disconnectCallback;
this.sendMessage = new RemoteMessage(this.websocket);
this.sendMessage.allocate(1024);
this.receiveMessage = new RemoteMessage();
this.container = document.createElement('div');
this.container.className = 'session';
this.container.style.position = 'relative';
targetElement.appendChild(this.container);
this.canvas = document.createElement('canvas');
this.canvas.className = 'session';
this.canvas.width = width;
this.canvas.height = height;
this.container.appendChild(this.canvas);
this.canvas.tabIndex = 0;
this.canvas.focus();
this.context = this.canvas.getContext('2d', { alpha: false });
this.context.imageSmoothingEnabled = false;
this.cursorVisible = true;
this.cursorPosition = { x: 0, y: 0 };
this.cursorHotspot = { x: 0, y: 0 };
this.states = new Object();
this.modifiers = 0;
this.canvas.onmousemove = this.onMouseMove.bind(this);
this.canvas.onmousedown = this.onMouseDown.bind(this);
this.canvas.onmouseup = this.onMouseUp.bind(this);
this.canvas.onwheel = this.onWheel.bind(this);
this.canvas.onkeydown = this.onKeyDownUp.bind(this);
this.canvas.onkeyup = this.onKeyDownUp.bind(this);
this.canvas.onkeypress = this.onKeyPress.bind(this);
this.canvas.oncontextmenu = function(event) {
event.preventDefault();
};
this.canvas.onblur = function(event) {
event.target.focus();
};
}
RemoteDesktopSession.prototype.onOpen = function(open)
{
console.log('open:', open);
this.init();
}
RemoteDesktopSession.prototype.onMessage = function(message)
{
var data = message.data;
if (this.messageRemainder) {
var combined = new Uint8Array(this.messageRemainder.byteLength
+ data.byteLength);
combined.set(new Uint8Array(this.messageRemainder), 0);
combined.set(new Uint8Array(data), this.messageRemainder.byteLength);
data = combined;
this.messageRemainder = null;
} else
data = new Uint8Array(data);
var byteOffset = 0;
while (true) {
try {
if (!this.receiveMessage.attach(data, byteOffset))
break;
} catch (exception) {
console.error('stream invalid, discarding everything', exception,
this.receiveMessage, data, byteOffset);
return;
}
try {
this.messageReceived(this.receiveMessage, this.sendMessage);
} catch (exception) {
console.error('exception during message processing:', exception);
}
byteOffset += this.receiveMessage.size();
}
if (data.byteLength > byteOffset)
this.messageRemainder = data.slice(byteOffset);
}
RemoteDesktopSession.prototype.messageReceived = function(remoteMessage, reply)
{
switch (remoteMessage.code()) {
case RP_INIT_CONNECTION:
console.log('init connection reply');
this.sendMessage.start(RP_UPDATE_DISPLAY_MODE);
this.sendMessage.dataView.writeUint32(this.canvas.width);
this.sendMessage.dataView.writeUint32(this.canvas.height);
this.sendMessage.flush();
this.sendMessage.start(RP_GET_SYSTEM_PALETTE);
this.sendMessage.flush();
break;
case RP_GET_SYSTEM_PALETTE_RESULT:
var count = remoteMessage.dataView.readUint32();
gSystemPalette = new Uint32Array(count);
var color = new RemoteColor();
for (var i = 0; i < gSystemPalette.length; i++)
gSystemPalette[i] = color.readFrom(remoteMessage).toUint32();
break;
case RP_CREATE_STATE:
var token = remoteMessage.dataView.readInt32();
console.log('create state: ' + token);
if (this.states.hasOwnProperty(token))
console.error('create state for existing token: ' + token);
this.states[token] = new RemoteState(this, token);
break;
case RP_DELETE_STATE:
var token = remoteMessage.dataView.readInt32();
console.log('delete state: ' + token);
if (!this.states.hasOwnProperty(token)) {
console.error('delete state for unknown token: ' + token);
break;
}
delete this.states[token];
break;
case RP_INVALIDATE_RECT:
case RP_INVALIDATE_REGION:
break;
case RP_SET_CURSOR:
this.cursorHotspot = new RemotePoint(remoteMessage);
var bitmap = new RemoteBitmap(remoteMessage);
bitmap.canvas.style.position = 'absolute';
if (this.cursorCanvas)
this.cursorCanvas.remove();
this.cursorCanvas = bitmap.canvas;
this.cursorCanvas.style.pointerEvents = 'none';
this.container.appendChild(this.cursorCanvas);
this.container.style.cursor = 'none';
this.updateCursor();
break;
case RP_MOVE_CURSOR_TO:
this.cursorPosition.x = remoteMessage.dataView.readFloat32();
this.cursorPosition.y = remoteMessage.dataView.readFloat32();
this.updateCursor();
break;
case RP_SET_CURSOR_VISIBLE:
this.cursorVisible = remoteMessage.dataView.readUint8();
if (this.cursorCanvas) {
this.cursorCanvas.style.visibility
= this.cursorVisible ? 'visible' : 'hidden';
}
break;
case RP_COPY_RECT_NO_CLIPPING:
var xOffset = remoteMessage.dataView.readInt32();
var yOffset = remoteMessage.dataView.readInt32();
var rect = new RemoteRect(remoteMessage);
var imageData = this.context.getImageData(rect.left, rect.top,
rect.width(), rect.height());
this.context.putImageData(imageData, rect.left + xOffset,
rect.top + yOffset);
break;
case RP_FILL_REGION_COLOR_NO_CLIPPING:
this.removeClipping();
this.context.currentToken = -1;
this.context.resetTransform();
this.context.globalCompositeOperation = 'source-over';
var rectCount = remoteMessage.dataView.readUint32();
var rects = new Array(rectCount);
for (var i = 0; i < rectCount; i++)
rects[i] = new RemoteRect(remoteMessage);
this.context.fillStyle = remoteMessage.readColor();
for (var i = 0; i < rectCount; i++)
rects[i].apply(this.context.fillRect.bind(this.context));
break;
default:
var token = remoteMessage.dataView.readInt32();
if (!this.states.hasOwnProperty(token)) {
console.warn('no state for token: ' + token);
this.states[token] = new RemoteState(this, token);
}
this.states[token].messageReceived(remoteMessage, reply);
break;
}
}
RemoteDesktopSession.prototype.onError = function(error)
{
console.log('websocket error:', error);
this.onDisconnect(error);
}
RemoteDesktopSession.prototype.onClose = function(close)
{
console.log('websocket close:', close);
this.onDisconnect(close);
}
RemoteDesktopSession.prototype.onDisconnect = function(reason)
{
this.container.remove();
if (this.disconnectCallback)
this.disconnectCallback(reason);
}
RemoteDesktopSession.prototype.applyClipping = function(clipRects)
{
this.removeClipping();
if (!clipRects || clipRects.length == 0)
return;
this.context.save();
this.context.beginPath();
this.context.save();
this.context.lineJoin = 'miter';
this.context.miterLimit = 10;
for (var i = 0; i < clipRects.length; i++)
clipRects[i].apply(this.context.rect.bind(this.context));
this.context.restore();
this.context.clip();
this.clippingApplied = true;
}
RemoteDesktopSession.prototype.removeClipping = function()
{
if (!this.clippingApplied)
return;
this.context.restore();
}
RemoteDesktopSession.prototype.init = function()
{
this.sendMessage.start(RP_INIT_CONNECTION);
this.sendMessage.flush();
}
RemoteDesktopSession.prototype.updateCursor = function()
{
if (!this.cursorVisible || !this.cursorCanvas)
return;
this.cursorCanvas.style.left
= (this.cursorPosition.x - this.cursorHotspot.x) + 'px';
this.cursorCanvas.style.top
= (this.cursorPosition.y - this.cursorHotspot.y) + 'px';
}
RemoteDesktopSession.prototype.onMouseMove = function(event)
{
this.sendMessage.start(RP_MOUSE_MOVED);
this.sendMessage.dataView.writeFloat32(event.offsetX);
this.sendMessage.dataView.writeFloat32(event.offsetY);
this.sendMessage.flush();
event.preventDefault();
}
RemoteDesktopSession.prototype.onMouseDown = function(event)
{
this.canvas.focus();
this.sendMessage.start(RP_MOUSE_DOWN);
this.sendMessage.dataView.writeFloat32(event.offsetX);
this.sendMessage.dataView.writeFloat32(event.offsetY);
this.sendMessage.dataView.writeUint32(event.buttons);
this.sendMessage.dataView.writeUint32(event.detail);
this.sendMessage.flush();
event.preventDefault();
}
RemoteDesktopSession.prototype.onMouseUp = function(event)
{
this.sendMessage.start(RP_MOUSE_UP);
this.sendMessage.dataView.writeFloat32(event.offsetX);
this.sendMessage.dataView.writeFloat32(event.offsetY);
this.sendMessage.dataView.writeUint32(event.buttons);
this.sendMessage.flush();
event.preventDefault();
}
RemoteDesktopSession.prototype.onKeyDownUp = function(event)
{
var keyDown = event.type === 'keydown';
var lockModifier = false;
var modifiersChanged = 0;
switch (event.code) {
case 'ShiftLeft':
modifiersChanged |= B_LEFT_SHIFT_KEY;
if (event.shiftKey == keyDown)
modifiersChanged |= B_SHIFT_KEY;
break;
case 'ShiftRight':
modifiersChanged |= B_RIGHT_SHIFT_KEY;
if (event.shiftKey == keyDown)
modifiersChanged |= B_SHIFT_KEY;
break;
case 'ControlLeft':
modifiersChanged |= B_LEFT_CONTROL_KEY;
if (event.ctrlKey == keyDown)
modifiersChanged |= B_CONTROL_KEY;
break;
case 'ControlRight':
modifiersChanged |= B_RIGHT_CONTROL_KEY;
if (event.ctrlKey == keyDown)
modifiersChanged |= B_CONTROL_KEY;
break;
case 'AltLeft':
modifiersChanged |= B_LEFT_COMMAND_KEY;
if (event.altKey == keyDown)
modifiersChanged |= B_COMMAND_KEY;
break;
case 'AltRight':
modifiersChanged |= B_RIGHT_COMMAND_KEY;
if (event.altKey == keyDown)
modifiersChanged |= B_COMMAND_KEY;
break;
case 'ContextMenu':
modifiersChanged |= B_MENU_KEY;
break;
case 'CapsLock':
modifiersChanged |= B_CAPS_LOCK;
lockModifier = true;
break;
case 'ScrollLock':
modifiersChanged |= B_SCROLL_LOCK;
lockModifier = true;
break;
case 'NumLock':
modifiersChanged |= B_NUM_LOCK;
lockModifier = true;
break;
}
if (modifiersChanged != 0) {
if (lockModifier) {
if (((this.modifiers & modifiersChanged) == 0) == keyDown)
this.modifiers ^= modifiersChanged;
} else {
if (keyDown)
this.modifiers |= modifiersChanged;
else
this.modifiers &= ~modifiersChanged;
}
this.sendMessage.start(RP_MODIFIERS_CHANGED);
this.sendMessage.dataView.writeUint32(this.modifiers);
this.sendMessage.flush();
event.preventDefault();
return;
}
this.sendMessage.start(keyDown ? RP_KEY_DOWN : RP_KEY_UP);
if (event.key.length == 1)
this.sendMessage.dataView.writeString(event.key);
else {
this.sendMessage.dataView.writeUint32(1);
this.sendMessage.dataView.writeUint8(event.keyCode);
}
if (event.keyCode) {
this.sendMessage.dataView.writeUint32(0);
this.sendMessage.dataView.writeUint32(event.keyCode);
}
this.sendMessage.flush();
event.preventDefault();
}
RemoteDesktopSession.prototype.onKeyPress = function(event)
{
this.sendMessage.start(RP_KEY_DOWN);
this.sendMessage.dataView.writeUint32(1);
this.sendMessage.dataView.writeUint8(event.which);
this.sendMessage.flush();
this.sendMessage.start(RP_KEY_UP);
this.sendMessage.dataView.writeUint32(1);
this.sendMessage.dataView.writeUint8(event.which);
this.sendMessage.flush();
event.preventDefault();
}
RemoteDesktopSession.prototype.onWheel = function(event)
{
this.sendMessage.start(RP_MOUSE_WHEEL_CHANGED);
this.sendMessage.dataView.writeFloat32(event.deltaX);
this.sendMessage.dataView.writeFloat32(event.deltaY);
this.sendMessage.flush();
event.preventDefault();
}
function init()
{
var targetAddressInput = document.querySelector('#targetAddress');
var widthInput = document.querySelector('#width');
var heightInput = document.querySelector('#height');
if (localStorage.targetAddress)
targetAddressInput.value = localStorage.targetAddress;
if (localStorage.width)
widthInput.value = localStorage.width;
if (localStorage.height)
heightInput.value = localStorage.height;
var onDisconnect = function(reason) {
document.body.classList.remove('connect');
gSession = undefined;
};
document.querySelector('#connectButton').onclick = function() {
document.body.classList.add('connect');
localStorage.width = widthInput.value;
localStorage.height = heightInput.value;
localStorage.targetAddress = targetAddressInput.value;
gSession = new RemoteDesktopSession(document.body, widthInput.value,
heightInput.value, targetAddressInput.value, onDisconnect);
};
}