5456 lines
175 KiB
JavaScript
5456 lines
175 KiB
JavaScript
import "./chunk-V4OQ3NZ2.js";
|
|
|
|
// node_modules/@dushixiang/guacamole-common-js/dist/esm/guacamole-common.js
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.ArrayBufferReader = function(stream) {
|
|
var guac_reader = this;
|
|
stream.onblob = function(data) {
|
|
var arrayBuffer, bufferView;
|
|
if (Uint8Array.fromBase64) {
|
|
bufferView = Uint8Array.fromBase64(data);
|
|
arrayBuffer = bufferView.buffer;
|
|
} else {
|
|
var binary = window.atob(data);
|
|
arrayBuffer = new ArrayBuffer(binary.length);
|
|
bufferView = new Uint8Array(arrayBuffer);
|
|
for (var i = 0; i < binary.length; i++)
|
|
bufferView[i] = binary.charCodeAt(i);
|
|
}
|
|
if (guac_reader.ondata)
|
|
guac_reader.ondata(arrayBuffer);
|
|
};
|
|
stream.onend = function() {
|
|
if (guac_reader.onend)
|
|
guac_reader.onend();
|
|
};
|
|
this.ondata = null;
|
|
this.onend = null;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.ArrayBufferWriter = function(stream) {
|
|
var guac_writer = this;
|
|
stream.onack = function(status) {
|
|
if (guac_writer.onack)
|
|
guac_writer.onack(status);
|
|
};
|
|
function __send_blob(bytes) {
|
|
var binary = "";
|
|
for (var i = 0; i < bytes.byteLength; i++)
|
|
binary += String.fromCharCode(bytes[i]);
|
|
stream.sendBlob(window.btoa(binary));
|
|
}
|
|
this.blobLength = Guacamole.ArrayBufferWriter.DEFAULT_BLOB_LENGTH;
|
|
this.sendData = function(data) {
|
|
var bytes = new Uint8Array(data);
|
|
if (bytes.length <= guac_writer.blobLength)
|
|
__send_blob(bytes);
|
|
else {
|
|
for (var offset = 0; offset < bytes.length; offset += guac_writer.blobLength)
|
|
__send_blob(bytes.subarray(offset, offset + guac_writer.blobLength));
|
|
}
|
|
};
|
|
this.sendEnd = function() {
|
|
stream.sendEnd();
|
|
};
|
|
this.onack = null;
|
|
};
|
|
Guacamole.ArrayBufferWriter.DEFAULT_BLOB_LENGTH = 6048;
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.AudioContextFactory = {
|
|
/**
|
|
* A singleton instance of a Web Audio API AudioContext object, or null if
|
|
* no instance has yes been created. This property may be manually set if
|
|
* you wish to supply your own AudioContext instance, but care must be
|
|
* taken to do so as early as possible. Assignments to this property will
|
|
* not retroactively affect the value returned by previous calls to
|
|
* getAudioContext().
|
|
*
|
|
* @type {AudioContext}
|
|
*/
|
|
"singleton": null,
|
|
/**
|
|
* Returns a singleton instance of a Web Audio API AudioContext object.
|
|
*
|
|
* @return {AudioContext}
|
|
* A singleton instance of a Web Audio API AudioContext object, or null
|
|
* if the Web Audio API is not supported.
|
|
*/
|
|
"getAudioContext": function getAudioContext() {
|
|
var AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
if (AudioContext) {
|
|
try {
|
|
if (!Guacamole.AudioContextFactory.singleton)
|
|
Guacamole.AudioContextFactory.singleton = new AudioContext();
|
|
return Guacamole.AudioContextFactory.singleton;
|
|
} catch (e) {
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.AudioPlayer = function AudioPlayer() {
|
|
this.sync = function sync() {
|
|
};
|
|
};
|
|
Guacamole.AudioPlayer.isSupportedType = function isSupportedType(mimetype) {
|
|
return Guacamole.RawAudioPlayer.isSupportedType(mimetype);
|
|
};
|
|
Guacamole.AudioPlayer.getSupportedTypes = function getSupportedTypes() {
|
|
return Guacamole.RawAudioPlayer.getSupportedTypes();
|
|
};
|
|
Guacamole.AudioPlayer.getInstance = function getInstance(stream, mimetype) {
|
|
if (Guacamole.RawAudioPlayer.isSupportedType(mimetype))
|
|
return new Guacamole.RawAudioPlayer(stream, mimetype);
|
|
return null;
|
|
};
|
|
Guacamole.RawAudioPlayer = function RawAudioPlayer(stream, mimetype) {
|
|
var format = Guacamole.RawAudioFormat.parse(mimetype);
|
|
var context = Guacamole.AudioContextFactory.getAudioContext();
|
|
var nextPacketTime = context.currentTime;
|
|
var reader = new Guacamole.ArrayBufferReader(stream);
|
|
var MIN_SPLIT_SIZE = 0.02;
|
|
var maxLatency = 0.3;
|
|
var SampleArray = format.bytesPerSample === 1 ? window.Int8Array : window.Int16Array;
|
|
var maxSampleValue = format.bytesPerSample === 1 ? 128 : 32768;
|
|
var packetQueue = [];
|
|
var joinAudioPackets = function joinAudioPackets2(packets) {
|
|
if (packets.length <= 1)
|
|
return packets[0];
|
|
var totalLength = 0;
|
|
packets.forEach(function addPacketLengths(packet) {
|
|
totalLength += packet.length;
|
|
});
|
|
var offset = 0;
|
|
var joined = new SampleArray(totalLength);
|
|
packets.forEach(function appendPacket(packet) {
|
|
joined.set(packet, offset);
|
|
offset += packet.length;
|
|
});
|
|
return joined;
|
|
};
|
|
var splitAudioPacket = function splitAudioPacket2(data) {
|
|
var minValue = Number.MAX_VALUE;
|
|
var optimalSplitLength = data.length;
|
|
var samples = Math.floor(data.length / format.channels);
|
|
var minSplitSamples = Math.floor(format.rate * MIN_SPLIT_SIZE);
|
|
var start = Math.max(
|
|
format.channels * minSplitSamples,
|
|
format.channels * (samples - minSplitSamples)
|
|
);
|
|
for (var offset = start; offset < data.length; offset += format.channels) {
|
|
var totalValue = 0;
|
|
for (var channel = 0; channel < format.channels; channel++) {
|
|
totalValue += Math.abs(data[offset + channel]);
|
|
}
|
|
if (totalValue <= minValue) {
|
|
optimalSplitLength = offset + format.channels;
|
|
minValue = totalValue;
|
|
}
|
|
}
|
|
if (optimalSplitLength === data.length)
|
|
return [data];
|
|
return [
|
|
new SampleArray(data.buffer.slice(0, optimalSplitLength * format.bytesPerSample)),
|
|
new SampleArray(data.buffer.slice(optimalSplitLength * format.bytesPerSample))
|
|
];
|
|
};
|
|
var pushAudioPacket = function pushAudioPacket2(data) {
|
|
packetQueue.push(new SampleArray(data));
|
|
};
|
|
var shiftAudioPacket = function shiftAudioPacket2() {
|
|
var data = joinAudioPackets(packetQueue);
|
|
if (!data)
|
|
return null;
|
|
packetQueue = splitAudioPacket(data);
|
|
data = packetQueue.shift();
|
|
return data;
|
|
};
|
|
var toAudioBuffer = function toAudioBuffer2(data) {
|
|
var samples = data.length / format.channels;
|
|
var packetTime = context.currentTime;
|
|
if (nextPacketTime < packetTime)
|
|
nextPacketTime = packetTime;
|
|
var audioBuffer = context.createBuffer(format.channels, samples, format.rate);
|
|
for (var channel = 0; channel < format.channels; channel++) {
|
|
var audioData = audioBuffer.getChannelData(channel);
|
|
var offset = channel;
|
|
for (var i = 0; i < samples; i++) {
|
|
audioData[i] = data[offset] / maxSampleValue;
|
|
offset += format.channels;
|
|
}
|
|
}
|
|
return audioBuffer;
|
|
};
|
|
reader.ondata = function playReceivedAudio(data) {
|
|
pushAudioPacket(new SampleArray(data));
|
|
var packet = shiftAudioPacket();
|
|
if (!packet)
|
|
return;
|
|
var packetTime = context.currentTime;
|
|
if (nextPacketTime < packetTime)
|
|
nextPacketTime = packetTime;
|
|
var source = context.createBufferSource();
|
|
source.connect(context.destination);
|
|
if (!source.start)
|
|
source.start = source.noteOn;
|
|
source.buffer = toAudioBuffer(packet);
|
|
source.start(nextPacketTime);
|
|
nextPacketTime += packet.length / format.channels / format.rate;
|
|
};
|
|
this.sync = function sync() {
|
|
var now = context.currentTime;
|
|
nextPacketTime = Math.min(nextPacketTime, now + maxLatency);
|
|
};
|
|
};
|
|
Guacamole.RawAudioPlayer.prototype = new Guacamole.AudioPlayer();
|
|
Guacamole.RawAudioPlayer.isSupportedType = function isSupportedType2(mimetype) {
|
|
if (!Guacamole.AudioContextFactory.getAudioContext())
|
|
return false;
|
|
return Guacamole.RawAudioFormat.parse(mimetype) !== null;
|
|
};
|
|
Guacamole.RawAudioPlayer.getSupportedTypes = function getSupportedTypes2() {
|
|
if (!Guacamole.AudioContextFactory.getAudioContext())
|
|
return [];
|
|
return [
|
|
"audio/L8",
|
|
"audio/L16"
|
|
];
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.AudioRecorder = function AudioRecorder() {
|
|
this.onclose = null;
|
|
this.onerror = null;
|
|
};
|
|
Guacamole.AudioRecorder.isSupportedType = function isSupportedType3(mimetype) {
|
|
return Guacamole.RawAudioRecorder.isSupportedType(mimetype);
|
|
};
|
|
Guacamole.AudioRecorder.getSupportedTypes = function getSupportedTypes3() {
|
|
return Guacamole.RawAudioRecorder.getSupportedTypes();
|
|
};
|
|
Guacamole.AudioRecorder.getInstance = function getInstance2(stream, mimetype) {
|
|
if (Guacamole.RawAudioRecorder.isSupportedType(mimetype))
|
|
return new Guacamole.RawAudioRecorder(stream, mimetype);
|
|
return null;
|
|
};
|
|
Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
|
var recorder = this;
|
|
var BUFFER_SIZE = 2048;
|
|
var LANCZOS_WINDOW_SIZE = 3;
|
|
var format = Guacamole.RawAudioFormat.parse(mimetype);
|
|
var context = Guacamole.AudioContextFactory.getAudioContext();
|
|
if (!navigator.mediaDevices)
|
|
navigator.mediaDevices = {};
|
|
if (!navigator.mediaDevices.getUserMedia)
|
|
navigator.mediaDevices.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia).bind(navigator);
|
|
var writer = new Guacamole.ArrayBufferWriter(stream);
|
|
var SampleArray = format.bytesPerSample === 1 ? window.Int8Array : window.Int16Array;
|
|
var maxSampleValue = format.bytesPerSample === 1 ? 128 : 32768;
|
|
var readSamples = 0;
|
|
var writtenSamples = 0;
|
|
var mediaStream = null;
|
|
var source = null;
|
|
var processor = null;
|
|
var sinc = function sinc2(x) {
|
|
if (x === 0)
|
|
return 1;
|
|
var piX = Math.PI * x;
|
|
return Math.sin(piX) / piX;
|
|
};
|
|
var lanczos = function lanczos2(x, a) {
|
|
if (-a < x && x < a)
|
|
return sinc(x) * sinc(x / a);
|
|
return 0;
|
|
};
|
|
var interpolateSample = function getValueAt(audioData, t) {
|
|
var index = (audioData.length - 1) * t;
|
|
var start = Math.floor(index) - LANCZOS_WINDOW_SIZE + 1;
|
|
var end = Math.floor(index) + LANCZOS_WINDOW_SIZE;
|
|
var sum = 0;
|
|
for (var i = start; i <= end; i++) {
|
|
sum += (audioData[i] || 0) * lanczos(index - i, LANCZOS_WINDOW_SIZE);
|
|
}
|
|
return sum;
|
|
};
|
|
var toSampleArray = function toSampleArray2(audioBuffer) {
|
|
var inSamples = audioBuffer.length;
|
|
readSamples += inSamples;
|
|
var expectedWrittenSamples = Math.round(readSamples * format.rate / audioBuffer.sampleRate);
|
|
var outSamples = expectedWrittenSamples - writtenSamples;
|
|
writtenSamples += outSamples;
|
|
var data = new SampleArray(outSamples * format.channels);
|
|
for (var channel = 0; channel < format.channels; channel++) {
|
|
var audioData = audioBuffer.getChannelData(channel);
|
|
var offset = channel;
|
|
for (var i = 0; i < outSamples; i++) {
|
|
data[offset] = interpolateSample(audioData, i / (outSamples - 1)) * maxSampleValue;
|
|
offset += format.channels;
|
|
}
|
|
}
|
|
return data;
|
|
};
|
|
var streamReceived = function streamReceived2(stream2) {
|
|
processor = context.createScriptProcessor(BUFFER_SIZE, format.channels, format.channels);
|
|
processor.connect(context.destination);
|
|
processor.onaudioprocess = function processAudio(e) {
|
|
writer.sendData(toSampleArray(e.inputBuffer).buffer);
|
|
};
|
|
source = context.createMediaStreamSource(stream2);
|
|
source.connect(processor);
|
|
if (context.state === "suspended")
|
|
context.resume();
|
|
mediaStream = stream2;
|
|
};
|
|
var streamDenied = function streamDenied2() {
|
|
writer.sendEnd();
|
|
if (recorder.onerror)
|
|
recorder.onerror();
|
|
};
|
|
var beginAudioCapture = function beginAudioCapture2() {
|
|
var promise = navigator.mediaDevices.getUserMedia({
|
|
"audio": true
|
|
}, streamReceived, streamDenied);
|
|
if (promise && promise.then)
|
|
promise.then(streamReceived, streamDenied);
|
|
};
|
|
var stopAudioCapture = function stopAudioCapture2() {
|
|
if (source)
|
|
source.disconnect();
|
|
if (processor)
|
|
processor.disconnect();
|
|
if (mediaStream) {
|
|
var tracks = mediaStream.getTracks();
|
|
for (var i = 0; i < tracks.length; i++)
|
|
tracks[i].stop();
|
|
}
|
|
processor = null;
|
|
source = null;
|
|
mediaStream = null;
|
|
writer.sendEnd();
|
|
};
|
|
writer.onack = function audioStreamAcknowledged(status) {
|
|
if (status.code === Guacamole.Status.Code.SUCCESS && !mediaStream)
|
|
beginAudioCapture();
|
|
else {
|
|
stopAudioCapture();
|
|
writer.onack = null;
|
|
if (status.code === Guacamole.Status.Code.RESOURCE_CLOSED) {
|
|
if (recorder.onclose)
|
|
recorder.onclose();
|
|
} else {
|
|
if (recorder.onerror)
|
|
recorder.onerror();
|
|
}
|
|
}
|
|
};
|
|
};
|
|
Guacamole.RawAudioRecorder.prototype = new Guacamole.AudioRecorder();
|
|
Guacamole.RawAudioRecorder.isSupportedType = function isSupportedType4(mimetype) {
|
|
if (!Guacamole.AudioContextFactory.getAudioContext())
|
|
return false;
|
|
return Guacamole.RawAudioFormat.parse(mimetype) !== null;
|
|
};
|
|
Guacamole.RawAudioRecorder.getSupportedTypes = function getSupportedTypes4() {
|
|
if (!Guacamole.AudioContextFactory.getAudioContext())
|
|
return [];
|
|
return [
|
|
"audio/L8",
|
|
"audio/L16"
|
|
];
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.BlobReader = function(stream, mimetype) {
|
|
var guac_reader = this;
|
|
var length = 0;
|
|
var blob_builder;
|
|
if (window.BlobBuilder) blob_builder = new BlobBuilder();
|
|
else if (window.WebKitBlobBuilder) blob_builder = new WebKitBlobBuilder();
|
|
else if (window.MozBlobBuilder) blob_builder = new MozBlobBuilder();
|
|
else
|
|
blob_builder = new (function() {
|
|
var blobs = [];
|
|
this.append = function(data) {
|
|
blobs.push(new Blob([data], { "type": mimetype }));
|
|
};
|
|
this.getBlob = function() {
|
|
return new Blob(blobs, { "type": mimetype });
|
|
};
|
|
})();
|
|
stream.onblob = function(data) {
|
|
var binary = window.atob(data);
|
|
var arrayBuffer = new ArrayBuffer(binary.length);
|
|
var bufferView = new Uint8Array(arrayBuffer);
|
|
for (var i = 0; i < binary.length; i++)
|
|
bufferView[i] = binary.charCodeAt(i);
|
|
blob_builder.append(arrayBuffer);
|
|
length += arrayBuffer.byteLength;
|
|
if (guac_reader.onprogress)
|
|
guac_reader.onprogress(arrayBuffer.byteLength);
|
|
stream.sendAck("OK", 0);
|
|
};
|
|
stream.onend = function() {
|
|
if (guac_reader.onend)
|
|
guac_reader.onend();
|
|
};
|
|
this.getLength = function() {
|
|
return length;
|
|
};
|
|
this.getBlob = function() {
|
|
return blob_builder.getBlob();
|
|
};
|
|
this.onprogress = null;
|
|
this.onend = null;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.BlobWriter = function BlobWriter(stream) {
|
|
var guacWriter = this;
|
|
var arrayBufferWriter = new Guacamole.ArrayBufferWriter(stream);
|
|
arrayBufferWriter.onack = function(status) {
|
|
if (guacWriter.onack)
|
|
guacWriter.onack(status);
|
|
};
|
|
var slice = function slice2(blob, start, end) {
|
|
var sliceImplementation = (blob.slice || blob.webkitSlice || blob.mozSlice).bind(blob);
|
|
var length = end - start;
|
|
if (length !== end) {
|
|
var sliceResult = sliceImplementation(start, length);
|
|
if (sliceResult.size === length)
|
|
return sliceResult;
|
|
}
|
|
return sliceImplementation(start, end);
|
|
};
|
|
this.sendBlob = function sendBlob(blob) {
|
|
var offset = 0;
|
|
var reader = new FileReader();
|
|
var readNextChunk = function readNextChunk2() {
|
|
if (offset >= blob.size) {
|
|
if (guacWriter.oncomplete)
|
|
guacWriter.oncomplete(blob);
|
|
return;
|
|
}
|
|
var chunk = slice(blob, offset, offset + arrayBufferWriter.blobLength);
|
|
offset += arrayBufferWriter.blobLength;
|
|
reader.readAsArrayBuffer(chunk);
|
|
};
|
|
reader.onload = function chunkLoadComplete() {
|
|
arrayBufferWriter.sendData(reader.result);
|
|
arrayBufferWriter.onack = function sendMoreChunks(status) {
|
|
if (guacWriter.onack)
|
|
guacWriter.onack(status);
|
|
if (status.isError())
|
|
return;
|
|
if (guacWriter.onprogress)
|
|
guacWriter.onprogress(blob, offset - arrayBufferWriter.blobLength);
|
|
readNextChunk();
|
|
};
|
|
};
|
|
reader.onerror = function chunkLoadFailed() {
|
|
if (guacWriter.onerror)
|
|
guacWriter.onerror(blob, offset, reader.error);
|
|
};
|
|
readNextChunk();
|
|
};
|
|
this.sendEnd = function sendEnd() {
|
|
arrayBufferWriter.sendEnd();
|
|
};
|
|
this.onack = null;
|
|
this.onerror = null;
|
|
this.onprogress = null;
|
|
this.oncomplete = null;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Client = function(tunnel) {
|
|
var guac_client = this;
|
|
var currentState = Guacamole.Client.State.IDLE;
|
|
var currentTimestamp = 0;
|
|
var KEEP_ALIVE_FREQUENCY = 5e3;
|
|
var keepAliveTimeout = null;
|
|
var lastSentKeepAlive = 0;
|
|
var lineCap = {
|
|
0: "butt",
|
|
1: "round",
|
|
2: "square"
|
|
};
|
|
var lineJoin = {
|
|
0: "bevel",
|
|
1: "miter",
|
|
2: "round"
|
|
};
|
|
var display = new Guacamole.Display();
|
|
var layers = {};
|
|
var audioPlayers = {};
|
|
var videoPlayers = {};
|
|
var parsers = [];
|
|
var streams = [];
|
|
var objects = [];
|
|
var stream_indices = new Guacamole.IntegerPool();
|
|
var output_streams = [];
|
|
function setState(state) {
|
|
if (state != currentState) {
|
|
currentState = state;
|
|
if (guac_client.onstatechange)
|
|
guac_client.onstatechange(currentState);
|
|
}
|
|
}
|
|
function isConnected() {
|
|
return currentState == Guacamole.Client.State.CONNECTED || currentState == Guacamole.Client.State.WAITING;
|
|
}
|
|
this.exportState = function exportState(callback) {
|
|
var state = {
|
|
"currentState": currentState,
|
|
"currentTimestamp": currentTimestamp,
|
|
"layers": {}
|
|
};
|
|
var layersSnapshot = {};
|
|
for (var key in layers) {
|
|
layersSnapshot[key] = layers[key];
|
|
}
|
|
display.flush(function populateLayers() {
|
|
for (var key2 in layersSnapshot) {
|
|
var index = parseInt(key2);
|
|
var layer = layersSnapshot[key2];
|
|
var exportLayer = {
|
|
"width": layer.width,
|
|
"height": layer.height
|
|
};
|
|
if (layer.width && layer.height) {
|
|
var canvas = layer.toCanvas();
|
|
exportLayer.url = canvas.toDataURL("image/png");
|
|
}
|
|
if (index > 0) {
|
|
exportLayer.x = layer.x;
|
|
exportLayer.y = layer.y;
|
|
exportLayer.z = layer.z;
|
|
exportLayer.alpha = layer.alpha;
|
|
exportLayer.matrix = layer.matrix;
|
|
exportLayer.parent = getLayerIndex(layer.parent);
|
|
}
|
|
state.layers[key2] = exportLayer;
|
|
}
|
|
callback(state);
|
|
});
|
|
};
|
|
this.importState = function importState(state, callback) {
|
|
var key;
|
|
var index;
|
|
currentState = state.currentState;
|
|
currentTimestamp = state.currentTimestamp;
|
|
display.cancel();
|
|
for (key in layers) {
|
|
index = parseInt(key);
|
|
if (index > 0)
|
|
layers[key].dispose();
|
|
}
|
|
layers = {};
|
|
for (key in state.layers) {
|
|
index = parseInt(key);
|
|
var importLayer = state.layers[key];
|
|
var layer = getLayer(index);
|
|
display.resize(layer, importLayer.width, importLayer.height);
|
|
if (importLayer.url) {
|
|
display.setChannelMask(layer, Guacamole.Layer.SRC);
|
|
display.draw(layer, 0, 0, importLayer.url);
|
|
}
|
|
if (index > 0 && importLayer.parent >= 0) {
|
|
var parent = getLayer(importLayer.parent);
|
|
display.move(layer, parent, importLayer.x, importLayer.y, importLayer.z);
|
|
display.shade(layer, importLayer.alpha);
|
|
var matrix = importLayer.matrix;
|
|
display.distort(
|
|
layer,
|
|
matrix[0],
|
|
matrix[1],
|
|
matrix[2],
|
|
matrix[3],
|
|
matrix[4],
|
|
matrix[5]
|
|
);
|
|
}
|
|
}
|
|
display.flush(callback);
|
|
};
|
|
this.getDisplay = function() {
|
|
return display;
|
|
};
|
|
this.sendSize = function(width, height) {
|
|
if (!isConnected())
|
|
return;
|
|
tunnel.sendMessage("size", width, height);
|
|
};
|
|
this.sendKeyEvent = function(pressed, keysym) {
|
|
if (!isConnected())
|
|
return;
|
|
tunnel.sendMessage("key", keysym, pressed);
|
|
};
|
|
this.sendMouseState = function sendMouseState(mouseState, applyDisplayScale) {
|
|
if (!isConnected())
|
|
return;
|
|
var x = mouseState.x;
|
|
var y = mouseState.y;
|
|
if (applyDisplayScale) {
|
|
x /= display.getScale();
|
|
y /= display.getScale();
|
|
}
|
|
display.moveCursor(
|
|
Math.floor(x),
|
|
Math.floor(y)
|
|
);
|
|
var buttonMask = 0;
|
|
if (mouseState.left) buttonMask |= 1;
|
|
if (mouseState.middle) buttonMask |= 2;
|
|
if (mouseState.right) buttonMask |= 4;
|
|
if (mouseState.up) buttonMask |= 8;
|
|
if (mouseState.down) buttonMask |= 16;
|
|
tunnel.sendMessage("mouse", Math.floor(x), Math.floor(y), buttonMask);
|
|
};
|
|
this.sendTouchState = function sendTouchState(touchState, applyDisplayScale) {
|
|
if (!isConnected())
|
|
return;
|
|
var x = touchState.x;
|
|
var y = touchState.y;
|
|
if (applyDisplayScale) {
|
|
x /= display.getScale();
|
|
y /= display.getScale();
|
|
}
|
|
tunnel.sendMessage(
|
|
"touch",
|
|
touchState.id,
|
|
Math.floor(x),
|
|
Math.floor(y),
|
|
Math.floor(touchState.radiusX),
|
|
Math.floor(touchState.radiusY),
|
|
touchState.angle,
|
|
touchState.force
|
|
);
|
|
};
|
|
this.createOutputStream = function createOutputStream() {
|
|
var index = stream_indices.next();
|
|
var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index);
|
|
return stream;
|
|
};
|
|
this.createAudioStream = function(mimetype) {
|
|
var stream = guac_client.createOutputStream();
|
|
tunnel.sendMessage("audio", stream.index, mimetype);
|
|
return stream;
|
|
};
|
|
this.createFileStream = function(mimetype, filename) {
|
|
var stream = guac_client.createOutputStream();
|
|
tunnel.sendMessage("file", stream.index, mimetype, filename);
|
|
return stream;
|
|
};
|
|
this.createPipeStream = function(mimetype, name) {
|
|
var stream = guac_client.createOutputStream();
|
|
tunnel.sendMessage("pipe", stream.index, mimetype, name);
|
|
return stream;
|
|
};
|
|
this.createClipboardStream = function(mimetype) {
|
|
var stream = guac_client.createOutputStream();
|
|
tunnel.sendMessage("clipboard", stream.index, mimetype);
|
|
return stream;
|
|
};
|
|
this.createArgumentValueStream = function createArgumentValueStream(mimetype, name) {
|
|
var stream = guac_client.createOutputStream();
|
|
tunnel.sendMessage("argv", stream.index, mimetype, name);
|
|
return stream;
|
|
};
|
|
this.createObjectOutputStream = function createObjectOutputStream(index, mimetype, name) {
|
|
var stream = guac_client.createOutputStream();
|
|
tunnel.sendMessage("put", index, stream.index, mimetype, name);
|
|
return stream;
|
|
};
|
|
this.requestObjectInputStream = function requestObjectInputStream(index, name) {
|
|
if (!isConnected())
|
|
return;
|
|
tunnel.sendMessage("get", index, name);
|
|
};
|
|
this.sendAck = function(index, message, code) {
|
|
if (!isConnected())
|
|
return;
|
|
tunnel.sendMessage("ack", index, message, code);
|
|
};
|
|
this.sendBlob = function(index, data) {
|
|
if (!isConnected())
|
|
return;
|
|
tunnel.sendMessage("blob", index, data);
|
|
};
|
|
this.endStream = function(index) {
|
|
if (!isConnected())
|
|
return;
|
|
tunnel.sendMessage("end", index);
|
|
if (output_streams[index]) {
|
|
stream_indices.free(index);
|
|
delete output_streams[index];
|
|
}
|
|
};
|
|
this.onstatechange = null;
|
|
this.onname = null;
|
|
this.onerror = null;
|
|
this.onmsg = null;
|
|
this.onjoin = null;
|
|
this.onleave = null;
|
|
this.onaudio = null;
|
|
this.onvideo = null;
|
|
this.onmultitouch = null;
|
|
this.onargv = null;
|
|
this.onclipboard = null;
|
|
this.onfile = null;
|
|
this.onfilesystem = null;
|
|
this.onpipe = null;
|
|
this.onrequired = null;
|
|
this.onsync = null;
|
|
var getLayer = function getLayer2(index) {
|
|
var layer = layers[index];
|
|
if (!layer) {
|
|
if (index === 0)
|
|
layer = display.getDefaultLayer();
|
|
else if (index > 0)
|
|
layer = display.createLayer();
|
|
else
|
|
layer = display.createBuffer();
|
|
layers[index] = layer;
|
|
}
|
|
return layer;
|
|
};
|
|
var getLayerIndex = function getLayerIndex2(layer) {
|
|
if (!layer)
|
|
return null;
|
|
for (var key in layers) {
|
|
if (layer === layers[key])
|
|
return parseInt(key);
|
|
}
|
|
return null;
|
|
};
|
|
function getParser(index) {
|
|
var parser = parsers[index];
|
|
if (parser == null) {
|
|
parser = parsers[index] = new Guacamole.Parser();
|
|
parser.oninstruction = tunnel.oninstruction;
|
|
}
|
|
return parser;
|
|
}
|
|
var layerPropertyHandlers = {
|
|
"miter-limit": function(layer, value) {
|
|
display.setMiterLimit(layer, parseFloat(value));
|
|
},
|
|
"multi-touch": function layerSupportsMultiTouch(layer, value) {
|
|
if (guac_client.onmultitouch && layer instanceof Guacamole.Display.VisibleLayer)
|
|
guac_client.onmultitouch(layer, parseInt(value));
|
|
}
|
|
};
|
|
var instructionHandlers = {
|
|
"ack": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var reason = parameters[1];
|
|
var code = parseInt(parameters[2]);
|
|
var stream = output_streams[stream_index];
|
|
if (stream) {
|
|
if (stream.onack)
|
|
stream.onack(new Guacamole.Status(code, reason));
|
|
if (code >= 256 && output_streams[stream_index] === stream) {
|
|
stream_indices.free(stream_index);
|
|
delete output_streams[stream_index];
|
|
}
|
|
}
|
|
},
|
|
"arc": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
var x = parseInt(parameters[1]);
|
|
var y = parseInt(parameters[2]);
|
|
var radius = parseInt(parameters[3]);
|
|
var startAngle = parseFloat(parameters[4]);
|
|
var endAngle = parseFloat(parameters[5]);
|
|
var negative = parseInt(parameters[6]);
|
|
display.arc(layer, x, y, radius, startAngle, endAngle, negative != 0);
|
|
},
|
|
"argv": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var mimetype = parameters[1];
|
|
var name = parameters[2];
|
|
if (guac_client.onargv) {
|
|
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
|
|
guac_client.onargv(stream, mimetype, name);
|
|
} else
|
|
guac_client.sendAck(stream_index, "Receiving argument values unsupported", 256);
|
|
},
|
|
"audio": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var mimetype = parameters[1];
|
|
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
|
|
var audioPlayer = null;
|
|
if (guac_client.onaudio)
|
|
audioPlayer = guac_client.onaudio(stream, mimetype);
|
|
if (!audioPlayer)
|
|
audioPlayer = Guacamole.AudioPlayer.getInstance(stream, mimetype);
|
|
if (audioPlayer) {
|
|
audioPlayers[stream_index] = audioPlayer;
|
|
guac_client.sendAck(stream_index, "OK", 0);
|
|
} else
|
|
guac_client.sendAck(stream_index, "BAD TYPE", 783);
|
|
},
|
|
"blob": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var data = parameters[1];
|
|
var stream = streams[stream_index];
|
|
if (stream && stream.onblob)
|
|
stream.onblob(data);
|
|
},
|
|
"body": function handleBody(parameters) {
|
|
var objectIndex = parseInt(parameters[0]);
|
|
var object = objects[objectIndex];
|
|
var streamIndex = parseInt(parameters[1]);
|
|
var mimetype = parameters[2];
|
|
var name = parameters[3];
|
|
if (object && object.onbody) {
|
|
var stream = streams[streamIndex] = new Guacamole.InputStream(guac_client, streamIndex);
|
|
object.onbody(stream, mimetype, name);
|
|
} else
|
|
guac_client.sendAck(streamIndex, "Receipt of body unsupported", 256);
|
|
},
|
|
"cfill": function(parameters) {
|
|
var channelMask = parseInt(parameters[0]);
|
|
var layer = getLayer(parseInt(parameters[1]));
|
|
var r = parseInt(parameters[2]);
|
|
var g = parseInt(parameters[3]);
|
|
var b = parseInt(parameters[4]);
|
|
var a = parseInt(parameters[5]);
|
|
display.setChannelMask(layer, channelMask);
|
|
display.fillColor(layer, r, g, b, a);
|
|
},
|
|
"clip": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
display.clip(layer);
|
|
},
|
|
"clipboard": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var mimetype = parameters[1];
|
|
if (guac_client.onclipboard) {
|
|
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
|
|
guac_client.onclipboard(stream, mimetype);
|
|
} else
|
|
guac_client.sendAck(stream_index, "Clipboard unsupported", 256);
|
|
},
|
|
"close": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
display.close(layer);
|
|
},
|
|
"copy": function(parameters) {
|
|
var srcL = getLayer(parseInt(parameters[0]));
|
|
var srcX = parseInt(parameters[1]);
|
|
var srcY = parseInt(parameters[2]);
|
|
var srcWidth = parseInt(parameters[3]);
|
|
var srcHeight = parseInt(parameters[4]);
|
|
var channelMask = parseInt(parameters[5]);
|
|
var dstL = getLayer(parseInt(parameters[6]));
|
|
var dstX = parseInt(parameters[7]);
|
|
var dstY = parseInt(parameters[8]);
|
|
display.setChannelMask(dstL, channelMask);
|
|
display.copy(
|
|
srcL,
|
|
srcX,
|
|
srcY,
|
|
srcWidth,
|
|
srcHeight,
|
|
dstL,
|
|
dstX,
|
|
dstY
|
|
);
|
|
},
|
|
"cstroke": function(parameters) {
|
|
var channelMask = parseInt(parameters[0]);
|
|
var layer = getLayer(parseInt(parameters[1]));
|
|
var cap = lineCap[parseInt(parameters[2])];
|
|
var join = lineJoin[parseInt(parameters[3])];
|
|
var thickness = parseInt(parameters[4]);
|
|
var r = parseInt(parameters[5]);
|
|
var g = parseInt(parameters[6]);
|
|
var b = parseInt(parameters[7]);
|
|
var a = parseInt(parameters[8]);
|
|
display.setChannelMask(layer, channelMask);
|
|
display.strokeColor(layer, cap, join, thickness, r, g, b, a);
|
|
},
|
|
"cursor": function(parameters) {
|
|
var cursorHotspotX = parseInt(parameters[0]);
|
|
var cursorHotspotY = parseInt(parameters[1]);
|
|
var srcL = getLayer(parseInt(parameters[2]));
|
|
var srcX = parseInt(parameters[3]);
|
|
var srcY = parseInt(parameters[4]);
|
|
var srcWidth = parseInt(parameters[5]);
|
|
var srcHeight = parseInt(parameters[6]);
|
|
display.setCursor(
|
|
cursorHotspotX,
|
|
cursorHotspotY,
|
|
srcL,
|
|
srcX,
|
|
srcY,
|
|
srcWidth,
|
|
srcHeight
|
|
);
|
|
},
|
|
"curve": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
var cp1x = parseInt(parameters[1]);
|
|
var cp1y = parseInt(parameters[2]);
|
|
var cp2x = parseInt(parameters[3]);
|
|
var cp2y = parseInt(parameters[4]);
|
|
var x = parseInt(parameters[5]);
|
|
var y = parseInt(parameters[6]);
|
|
display.curveTo(layer, cp1x, cp1y, cp2x, cp2y, x, y);
|
|
},
|
|
"disconnect": function handleDisconnect(parameters) {
|
|
guac_client.disconnect();
|
|
},
|
|
"dispose": function(parameters) {
|
|
var layer_index = parseInt(parameters[0]);
|
|
if (layer_index > 0) {
|
|
var layer = getLayer(layer_index);
|
|
display.dispose(layer);
|
|
delete layers[layer_index];
|
|
} else if (layer_index < 0)
|
|
delete layers[layer_index];
|
|
},
|
|
"distort": function(parameters) {
|
|
var layer_index = parseInt(parameters[0]);
|
|
var a = parseFloat(parameters[1]);
|
|
var b = parseFloat(parameters[2]);
|
|
var c = parseFloat(parameters[3]);
|
|
var d = parseFloat(parameters[4]);
|
|
var e = parseFloat(parameters[5]);
|
|
var f = parseFloat(parameters[6]);
|
|
if (layer_index >= 0) {
|
|
var layer = getLayer(layer_index);
|
|
display.distort(layer, a, b, c, d, e, f);
|
|
}
|
|
},
|
|
"error": function(parameters) {
|
|
var reason = parameters[0];
|
|
var code = parseInt(parameters[1]);
|
|
if (guac_client.onerror)
|
|
guac_client.onerror(new Guacamole.Status(code, reason));
|
|
guac_client.disconnect();
|
|
},
|
|
"end": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var stream = streams[stream_index];
|
|
if (stream) {
|
|
if (stream.onend)
|
|
stream.onend();
|
|
delete streams[stream_index];
|
|
}
|
|
},
|
|
"file": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var mimetype = parameters[1];
|
|
var filename = parameters[2];
|
|
if (guac_client.onfile) {
|
|
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
|
|
guac_client.onfile(stream, mimetype, filename);
|
|
} else
|
|
guac_client.sendAck(stream_index, "File transfer unsupported", 256);
|
|
},
|
|
"filesystem": function handleFilesystem(parameters) {
|
|
var objectIndex = parseInt(parameters[0]);
|
|
var name = parameters[1];
|
|
if (guac_client.onfilesystem) {
|
|
var object = objects[objectIndex] = new Guacamole.Object(guac_client, objectIndex);
|
|
guac_client.onfilesystem(object, name);
|
|
}
|
|
},
|
|
"identity": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
display.setTransform(layer, 1, 0, 0, 1, 0, 0);
|
|
},
|
|
"img": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var channelMask = parseInt(parameters[1]);
|
|
var layer = getLayer(parseInt(parameters[2]));
|
|
var mimetype = parameters[3];
|
|
var x = parseInt(parameters[4]);
|
|
var y = parseInt(parameters[5]);
|
|
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
|
|
display.setChannelMask(layer, channelMask);
|
|
display.drawStream(layer, x, y, stream, mimetype);
|
|
},
|
|
"jpeg": function(parameters) {
|
|
var channelMask = parseInt(parameters[0]);
|
|
var layer = getLayer(parseInt(parameters[1]));
|
|
var x = parseInt(parameters[2]);
|
|
var y = parseInt(parameters[3]);
|
|
var data = parameters[4];
|
|
display.setChannelMask(layer, channelMask);
|
|
display.draw(layer, x, y, "data:image/jpeg;base64," + data);
|
|
},
|
|
"lfill": function(parameters) {
|
|
var channelMask = parseInt(parameters[0]);
|
|
var layer = getLayer(parseInt(parameters[1]));
|
|
var srcLayer = getLayer(parseInt(parameters[2]));
|
|
display.setChannelMask(layer, channelMask);
|
|
display.fillLayer(layer, srcLayer);
|
|
},
|
|
"line": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
var x = parseInt(parameters[1]);
|
|
var y = parseInt(parameters[2]);
|
|
display.lineTo(layer, x, y);
|
|
},
|
|
"lstroke": function(parameters) {
|
|
var channelMask = parseInt(parameters[0]);
|
|
var layer = getLayer(parseInt(parameters[1]));
|
|
var srcLayer = getLayer(parseInt(parameters[2]));
|
|
display.setChannelMask(layer, channelMask);
|
|
display.strokeLayer(layer, srcLayer);
|
|
},
|
|
"mouse": function handleMouse(parameters) {
|
|
var x = parseInt(parameters[0]);
|
|
var y = parseInt(parameters[1]);
|
|
display.showCursor(true);
|
|
display.moveCursor(x, y);
|
|
},
|
|
"move": function(parameters) {
|
|
var layer_index = parseInt(parameters[0]);
|
|
var parent_index = parseInt(parameters[1]);
|
|
var x = parseInt(parameters[2]);
|
|
var y = parseInt(parameters[3]);
|
|
var z = parseInt(parameters[4]);
|
|
if (layer_index > 0 && parent_index >= 0) {
|
|
var layer = getLayer(layer_index);
|
|
var parent = getLayer(parent_index);
|
|
display.move(layer, parent, x, y, z);
|
|
}
|
|
},
|
|
"msg": function(parameters) {
|
|
var userID;
|
|
var username;
|
|
var allowDefault = true;
|
|
var msgid = parseInt(parameters[0]);
|
|
if (guac_client.onmsg) {
|
|
allowDefault = guac_client.onmsg(msgid, parameters.slice(1));
|
|
if (allowDefault === void 0)
|
|
allowDefault = true;
|
|
}
|
|
if (allowDefault) {
|
|
switch (msgid) {
|
|
case Guacamole.Client.Message.USER_JOINED:
|
|
userID = parameters[1];
|
|
username = parameters[2];
|
|
if (guac_client.onjoin)
|
|
guac_client.onjoin(userID, username);
|
|
break;
|
|
case Guacamole.Client.Message.USER_LEFT:
|
|
userID = parameters[1];
|
|
username = parameters[2];
|
|
if (guac_client.onleave)
|
|
guac_client.onleave(userID, username);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
"name": function(parameters) {
|
|
if (guac_client.onname) guac_client.onname(parameters[0]);
|
|
},
|
|
"nest": function(parameters) {
|
|
var parser = getParser(parseInt(parameters[0]));
|
|
parser.receive(parameters[1]);
|
|
},
|
|
"pipe": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var mimetype = parameters[1];
|
|
var name = parameters[2];
|
|
if (guac_client.onpipe) {
|
|
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
|
|
guac_client.onpipe(stream, mimetype, name);
|
|
} else
|
|
guac_client.sendAck(stream_index, "Named pipes unsupported", 256);
|
|
},
|
|
"png": function(parameters) {
|
|
var channelMask = parseInt(parameters[0]);
|
|
var layer = getLayer(parseInt(parameters[1]));
|
|
var x = parseInt(parameters[2]);
|
|
var y = parseInt(parameters[3]);
|
|
var data = parameters[4];
|
|
display.setChannelMask(layer, channelMask);
|
|
display.draw(layer, x, y, "data:image/png;base64," + data);
|
|
},
|
|
"pop": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
display.pop(layer);
|
|
},
|
|
"push": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
display.push(layer);
|
|
},
|
|
"rect": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
var x = parseInt(parameters[1]);
|
|
var y = parseInt(parameters[2]);
|
|
var w = parseInt(parameters[3]);
|
|
var h = parseInt(parameters[4]);
|
|
display.rect(layer, x, y, w, h);
|
|
},
|
|
"required": function required(parameters) {
|
|
if (guac_client.onrequired) guac_client.onrequired(parameters);
|
|
},
|
|
"reset": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
display.reset(layer);
|
|
},
|
|
"set": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
var name = parameters[1];
|
|
var value = parameters[2];
|
|
var handler = layerPropertyHandlers[name];
|
|
if (handler)
|
|
handler(layer, value);
|
|
},
|
|
"shade": function(parameters) {
|
|
var layer_index = parseInt(parameters[0]);
|
|
var a = parseInt(parameters[1]);
|
|
if (layer_index >= 0) {
|
|
var layer = getLayer(layer_index);
|
|
display.shade(layer, a);
|
|
}
|
|
},
|
|
"size": function(parameters) {
|
|
var layer_index = parseInt(parameters[0]);
|
|
var layer = getLayer(layer_index);
|
|
var width = parseInt(parameters[1]);
|
|
var height = parseInt(parameters[2]);
|
|
display.resize(layer, width, height);
|
|
},
|
|
"start": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
var x = parseInt(parameters[1]);
|
|
var y = parseInt(parameters[2]);
|
|
display.moveTo(layer, x, y);
|
|
},
|
|
"sync": function(parameters) {
|
|
var timestamp = parseInt(parameters[0]);
|
|
var frames = parameters[1] ? parseInt(parameters[1]) : 0;
|
|
display.flush(function displaySyncComplete() {
|
|
for (var index in audioPlayers) {
|
|
var audioPlayer = audioPlayers[index];
|
|
if (audioPlayer)
|
|
audioPlayer.sync();
|
|
}
|
|
if (timestamp !== currentTimestamp) {
|
|
tunnel.sendMessage("sync", timestamp);
|
|
currentTimestamp = timestamp;
|
|
}
|
|
}, timestamp, frames);
|
|
if (currentState === Guacamole.Client.State.WAITING)
|
|
setState(Guacamole.Client.State.CONNECTED);
|
|
if (guac_client.onsync)
|
|
guac_client.onsync(timestamp, frames);
|
|
},
|
|
"transfer": function(parameters) {
|
|
var srcL = getLayer(parseInt(parameters[0]));
|
|
var srcX = parseInt(parameters[1]);
|
|
var srcY = parseInt(parameters[2]);
|
|
var srcWidth = parseInt(parameters[3]);
|
|
var srcHeight = parseInt(parameters[4]);
|
|
var function_index = parseInt(parameters[5]);
|
|
var dstL = getLayer(parseInt(parameters[6]));
|
|
var dstX = parseInt(parameters[7]);
|
|
var dstY = parseInt(parameters[8]);
|
|
if (function_index === 3)
|
|
display.put(
|
|
srcL,
|
|
srcX,
|
|
srcY,
|
|
srcWidth,
|
|
srcHeight,
|
|
dstL,
|
|
dstX,
|
|
dstY
|
|
);
|
|
else if (function_index !== 5)
|
|
display.transfer(
|
|
srcL,
|
|
srcX,
|
|
srcY,
|
|
srcWidth,
|
|
srcHeight,
|
|
dstL,
|
|
dstX,
|
|
dstY,
|
|
Guacamole.Client.DefaultTransferFunction[function_index]
|
|
);
|
|
},
|
|
"transform": function(parameters) {
|
|
var layer = getLayer(parseInt(parameters[0]));
|
|
var a = parseFloat(parameters[1]);
|
|
var b = parseFloat(parameters[2]);
|
|
var c = parseFloat(parameters[3]);
|
|
var d = parseFloat(parameters[4]);
|
|
var e = parseFloat(parameters[5]);
|
|
var f = parseFloat(parameters[6]);
|
|
display.transform(layer, a, b, c, d, e, f);
|
|
},
|
|
"undefine": function handleUndefine(parameters) {
|
|
var objectIndex = parseInt(parameters[0]);
|
|
var object = objects[objectIndex];
|
|
if (object && object.onundefine)
|
|
object.onundefine();
|
|
},
|
|
"video": function(parameters) {
|
|
var stream_index = parseInt(parameters[0]);
|
|
var layer = getLayer(parseInt(parameters[1]));
|
|
var mimetype = parameters[2];
|
|
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
|
|
var videoPlayer = null;
|
|
if (guac_client.onvideo)
|
|
videoPlayer = guac_client.onvideo(stream, layer, mimetype);
|
|
if (!videoPlayer)
|
|
videoPlayer = Guacamole.VideoPlayer.getInstance(stream, layer, mimetype);
|
|
if (videoPlayer) {
|
|
videoPlayers[stream_index] = videoPlayer;
|
|
guac_client.sendAck(stream_index, "OK", 0);
|
|
} else
|
|
guac_client.sendAck(stream_index, "BAD TYPE", 783);
|
|
}
|
|
};
|
|
var sendKeepAlive = function sendKeepAlive2() {
|
|
tunnel.sendMessage("nop");
|
|
lastSentKeepAlive = (/* @__PURE__ */ new Date()).getTime();
|
|
};
|
|
var scheduleKeepAlive = function scheduleKeepAlive2() {
|
|
window.clearTimeout(keepAliveTimeout);
|
|
var currentTime = (/* @__PURE__ */ new Date()).getTime();
|
|
var keepAliveDelay = Math.max(lastSentKeepAlive + KEEP_ALIVE_FREQUENCY - currentTime, 0);
|
|
if (keepAliveDelay > 0)
|
|
keepAliveTimeout = window.setTimeout(sendKeepAlive, keepAliveDelay);
|
|
else
|
|
sendKeepAlive();
|
|
};
|
|
var stopKeepAlive = function stopKeepAlive2() {
|
|
window.clearTimeout(keepAliveTimeout);
|
|
};
|
|
tunnel.oninstruction = function(opcode, parameters) {
|
|
var handler = instructionHandlers[opcode];
|
|
if (handler)
|
|
handler(parameters);
|
|
scheduleKeepAlive();
|
|
};
|
|
this.disconnect = function() {
|
|
if (currentState != Guacamole.Client.State.DISCONNECTED && currentState != Guacamole.Client.State.DISCONNECTING) {
|
|
setState(Guacamole.Client.State.DISCONNECTING);
|
|
stopKeepAlive();
|
|
tunnel.sendMessage("disconnect");
|
|
tunnel.disconnect();
|
|
setState(Guacamole.Client.State.DISCONNECTED);
|
|
}
|
|
};
|
|
this.connect = function(data) {
|
|
setState(Guacamole.Client.State.CONNECTING);
|
|
try {
|
|
tunnel.connect(data);
|
|
} catch (status) {
|
|
setState(Guacamole.Client.State.IDLE);
|
|
throw status;
|
|
}
|
|
scheduleKeepAlive();
|
|
setState(Guacamole.Client.State.WAITING);
|
|
};
|
|
};
|
|
Guacamole.Client.State = {
|
|
/**
|
|
* The client is idle, with no active connection.
|
|
*
|
|
* @type number
|
|
*/
|
|
"IDLE": 0,
|
|
/**
|
|
* The client is in the process of establishing a connection.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CONNECTING": 1,
|
|
/**
|
|
* The client is waiting on further information or a remote server to
|
|
* establish the connection.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"WAITING": 2,
|
|
/**
|
|
* The client is actively connected to a remote server.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CONNECTED": 3,
|
|
/**
|
|
* The client is in the process of disconnecting from the remote server.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"DISCONNECTING": 4,
|
|
/**
|
|
* The client has completed the connection and is no longer connected.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"DISCONNECTED": 5
|
|
};
|
|
Guacamole.Client.DefaultTransferFunction = {
|
|
/* BLACK */
|
|
0: function(src, dst) {
|
|
dst.red = dst.green = dst.blue = 0;
|
|
},
|
|
/* WHITE */
|
|
15: function(src, dst) {
|
|
dst.red = dst.green = dst.blue = 255;
|
|
},
|
|
/* SRC */
|
|
3: function(src, dst) {
|
|
dst.red = src.red;
|
|
dst.green = src.green;
|
|
dst.blue = src.blue;
|
|
dst.alpha = src.alpha;
|
|
},
|
|
/* DEST (no-op) */
|
|
5: function(src, dst) {
|
|
},
|
|
/* Invert SRC */
|
|
12: function(src, dst) {
|
|
dst.red = 255 & ~src.red;
|
|
dst.green = 255 & ~src.green;
|
|
dst.blue = 255 & ~src.blue;
|
|
dst.alpha = src.alpha;
|
|
},
|
|
/* Invert DEST */
|
|
10: function(src, dst) {
|
|
dst.red = 255 & ~dst.red;
|
|
dst.green = 255 & ~dst.green;
|
|
dst.blue = 255 & ~dst.blue;
|
|
},
|
|
/* AND */
|
|
1: function(src, dst) {
|
|
dst.red = src.red & dst.red;
|
|
dst.green = src.green & dst.green;
|
|
dst.blue = src.blue & dst.blue;
|
|
},
|
|
/* NAND */
|
|
14: function(src, dst) {
|
|
dst.red = 255 & ~(src.red & dst.red);
|
|
dst.green = 255 & ~(src.green & dst.green);
|
|
dst.blue = 255 & ~(src.blue & dst.blue);
|
|
},
|
|
/* OR */
|
|
7: function(src, dst) {
|
|
dst.red = src.red | dst.red;
|
|
dst.green = src.green | dst.green;
|
|
dst.blue = src.blue | dst.blue;
|
|
},
|
|
/* NOR */
|
|
8: function(src, dst) {
|
|
dst.red = 255 & ~(src.red | dst.red);
|
|
dst.green = 255 & ~(src.green | dst.green);
|
|
dst.blue = 255 & ~(src.blue | dst.blue);
|
|
},
|
|
/* XOR */
|
|
6: function(src, dst) {
|
|
dst.red = src.red ^ dst.red;
|
|
dst.green = src.green ^ dst.green;
|
|
dst.blue = src.blue ^ dst.blue;
|
|
},
|
|
/* XNOR */
|
|
9: function(src, dst) {
|
|
dst.red = 255 & ~(src.red ^ dst.red);
|
|
dst.green = 255 & ~(src.green ^ dst.green);
|
|
dst.blue = 255 & ~(src.blue ^ dst.blue);
|
|
},
|
|
/* AND inverted source */
|
|
4: function(src, dst) {
|
|
dst.red = 255 & (~src.red & dst.red);
|
|
dst.green = 255 & (~src.green & dst.green);
|
|
dst.blue = 255 & (~src.blue & dst.blue);
|
|
},
|
|
/* OR inverted source */
|
|
13: function(src, dst) {
|
|
dst.red = 255 & (~src.red | dst.red);
|
|
dst.green = 255 & (~src.green | dst.green);
|
|
dst.blue = 255 & (~src.blue | dst.blue);
|
|
},
|
|
/* AND inverted destination */
|
|
2: function(src, dst) {
|
|
dst.red = 255 & (src.red & ~dst.red);
|
|
dst.green = 255 & (src.green & ~dst.green);
|
|
dst.blue = 255 & (src.blue & ~dst.blue);
|
|
},
|
|
/* OR inverted destination */
|
|
11: function(src, dst) {
|
|
dst.red = 255 & (src.red | ~dst.red);
|
|
dst.green = 255 & (src.green | ~dst.green);
|
|
dst.blue = 255 & (src.blue | ~dst.blue);
|
|
}
|
|
};
|
|
Guacamole.Client.Message = {
|
|
/**
|
|
* A client message that indicates that a user has joined an existing
|
|
* connection. This message expects a single additional argument - the
|
|
* name of the user who has joined the connection.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"USER_JOINED": 1,
|
|
/**
|
|
* A client message that indicates that a user has left an existing
|
|
* connection. This message expects a single additional argument - the
|
|
* name of the user who has left the connection.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"USER_LEFT": 2
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.DataURIReader = function(stream, mimetype) {
|
|
var guac_reader = this;
|
|
var uri = "data:" + mimetype + ";base64,";
|
|
stream.onblob = function dataURIReaderBlob(data) {
|
|
uri += data;
|
|
};
|
|
stream.onend = function dataURIReaderEnd() {
|
|
if (guac_reader.onend)
|
|
guac_reader.onend();
|
|
};
|
|
this.getURI = function getURI() {
|
|
return uri;
|
|
};
|
|
this.onend = null;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Display = function() {
|
|
var guac_display = this;
|
|
var displayWidth = 0;
|
|
var displayHeight = 0;
|
|
var displayScale = 1;
|
|
var display = document.createElement("div");
|
|
display.style.position = "relative";
|
|
display.style.width = displayWidth + "px";
|
|
display.style.height = displayHeight + "px";
|
|
display.style.transformOrigin = display.style.webkitTransformOrigin = display.style.MozTransformOrigin = display.style.OTransformOrigin = display.style.msTransformOrigin = "0 0";
|
|
var default_layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight);
|
|
var cursor = new Guacamole.Display.VisibleLayer(0, 0);
|
|
cursor.setChannelMask(Guacamole.Layer.SRC);
|
|
display.appendChild(default_layer.getElement());
|
|
display.appendChild(cursor.getElement());
|
|
var bounds = document.createElement("div");
|
|
bounds.style.position = "relative";
|
|
bounds.style.width = displayWidth * displayScale + "px";
|
|
bounds.style.height = displayHeight * displayScale + "px";
|
|
bounds.appendChild(display);
|
|
this.cursorHotspotX = 0;
|
|
this.cursorHotspotY = 0;
|
|
this.cursorX = 0;
|
|
this.cursorY = 0;
|
|
this.statisticWindow = 0;
|
|
this.onresize = null;
|
|
this.oncursor = null;
|
|
this.onstatistics = null;
|
|
var tasks = [];
|
|
var frames = [];
|
|
var syncFlush = function syncFlush2() {
|
|
var localTimestamp = 0;
|
|
var remoteTimestamp = 0;
|
|
var renderedLogicalFrames = 0;
|
|
var rendered_frames = 0;
|
|
while (rendered_frames < frames.length) {
|
|
var frame = frames[rendered_frames];
|
|
if (!frame.isReady())
|
|
break;
|
|
frame.flush();
|
|
localTimestamp = frame.localTimestamp;
|
|
remoteTimestamp = frame.remoteTimestamp;
|
|
renderedLogicalFrames += frame.logicalFrames;
|
|
rendered_frames++;
|
|
}
|
|
frames.splice(0, rendered_frames);
|
|
if (rendered_frames)
|
|
notifyFlushed(localTimestamp, remoteTimestamp, renderedLogicalFrames);
|
|
};
|
|
var statistics = [];
|
|
var notifyFlushed = function notifyFlushed2(localTimestamp, remoteTimestamp, logicalFrames) {
|
|
if (!guac_display.statisticWindow)
|
|
return;
|
|
var current = (/* @__PURE__ */ new Date()).getTime();
|
|
for (var first = 0; first < statistics.length; first++) {
|
|
if (current - statistics[first].timestamp <= guac_display.statisticWindow)
|
|
break;
|
|
}
|
|
statistics.splice(0, first - 1);
|
|
statistics.push({
|
|
localTimestamp,
|
|
remoteTimestamp,
|
|
timestamp: current,
|
|
frames: logicalFrames
|
|
});
|
|
var statDuration = (statistics[statistics.length - 1].timestamp - statistics[0].timestamp) / 1e3;
|
|
var remoteDuration = (statistics[statistics.length - 1].remoteTimestamp - statistics[0].remoteTimestamp) / 1e3;
|
|
var localFrames = statistics.length;
|
|
var remoteFrames = statistics.reduce(function sumFrames(prev, stat) {
|
|
return prev + stat.frames;
|
|
}, 0);
|
|
var drops = statistics.reduce(function sumDrops(prev, stat) {
|
|
return prev + Math.max(0, stat.frames - 1);
|
|
}, 0);
|
|
var stats = new Guacamole.Display.Statistics({
|
|
processingLag: current - localTimestamp,
|
|
desktopFps: remoteDuration && remoteFrames ? remoteFrames / remoteDuration : null,
|
|
clientFps: statDuration ? localFrames / statDuration : null,
|
|
serverFps: remoteDuration ? localFrames / remoteDuration : null,
|
|
dropRate: remoteDuration ? drops / remoteDuration : null
|
|
});
|
|
if (guac_display.onstatistics)
|
|
guac_display.onstatistics(stats);
|
|
};
|
|
function __flush_frames() {
|
|
syncFlush();
|
|
}
|
|
var Frame = function Frame2(callback, tasks2, timestamp, logicalFrames) {
|
|
this.localTimestamp = (/* @__PURE__ */ new Date()).getTime();
|
|
this.remoteTimestamp = timestamp || this.localTimestamp;
|
|
this.logicalFrames = logicalFrames || 0;
|
|
this.cancel = function cancel() {
|
|
callback = null;
|
|
tasks2.forEach(function cancelTask(task) {
|
|
task.cancel();
|
|
});
|
|
tasks2 = [];
|
|
};
|
|
this.isReady = function() {
|
|
for (var i = 0; i < tasks2.length; i++) {
|
|
if (tasks2[i].blocked)
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
this.flush = function() {
|
|
for (var i = 0; i < tasks2.length; i++)
|
|
tasks2[i].execute();
|
|
if (callback) callback();
|
|
};
|
|
};
|
|
function Task(taskHandler, blocked) {
|
|
var task = this;
|
|
this.blocked = blocked;
|
|
this.cancel = function cancel() {
|
|
task.blocked = false;
|
|
taskHandler = null;
|
|
};
|
|
this.unblock = function() {
|
|
if (task.blocked) {
|
|
task.blocked = false;
|
|
if (frames.length)
|
|
__flush_frames();
|
|
}
|
|
};
|
|
this.execute = function() {
|
|
if (taskHandler) taskHandler();
|
|
};
|
|
}
|
|
function scheduleTask(handler, blocked) {
|
|
var task = new Task(handler, blocked);
|
|
tasks.push(task);
|
|
return task;
|
|
}
|
|
this.getElement = function() {
|
|
return bounds;
|
|
};
|
|
this.getWidth = function() {
|
|
return displayWidth;
|
|
};
|
|
this.getHeight = function() {
|
|
return displayHeight;
|
|
};
|
|
this.getDefaultLayer = function() {
|
|
return default_layer;
|
|
};
|
|
this.getCursorLayer = function() {
|
|
return cursor;
|
|
};
|
|
this.createLayer = function() {
|
|
var layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight);
|
|
layer.move(default_layer, 0, 0, 0);
|
|
return layer;
|
|
};
|
|
this.createBuffer = function() {
|
|
var buffer = new Guacamole.Layer(0, 0);
|
|
buffer.autosize = 1;
|
|
return buffer;
|
|
};
|
|
this.flush = function(callback, timestamp, logicalFrames) {
|
|
frames.push(new Frame(callback, tasks, timestamp, logicalFrames));
|
|
tasks = [];
|
|
__flush_frames();
|
|
};
|
|
this.cancel = function cancel() {
|
|
frames.forEach(function cancelFrame(frame) {
|
|
frame.cancel();
|
|
});
|
|
frames = [];
|
|
tasks.forEach(function cancelTask(task) {
|
|
task.cancel();
|
|
});
|
|
tasks = [];
|
|
};
|
|
this.setCursor = function(hotspotX, hotspotY, layer, srcx, srcy, srcw, srch) {
|
|
scheduleTask(function __display_set_cursor() {
|
|
guac_display.cursorHotspotX = hotspotX;
|
|
guac_display.cursorHotspotY = hotspotY;
|
|
cursor.resize(srcw, srch);
|
|
cursor.copy(layer, srcx, srcy, srcw, srch, 0, 0);
|
|
guac_display.moveCursor(guac_display.cursorX, guac_display.cursorY);
|
|
if (guac_display.oncursor)
|
|
guac_display.oncursor(cursor.toCanvas(), hotspotX, hotspotY);
|
|
});
|
|
};
|
|
this.showCursor = function(shown) {
|
|
var element = cursor.getElement();
|
|
var parent = element.parentNode;
|
|
if (shown === false) {
|
|
if (parent)
|
|
parent.removeChild(element);
|
|
} else if (parent !== display)
|
|
display.appendChild(element);
|
|
};
|
|
this.moveCursor = function(x, y) {
|
|
cursor.translate(
|
|
x - guac_display.cursorHotspotX,
|
|
y - guac_display.cursorHotspotY
|
|
);
|
|
guac_display.cursorX = x;
|
|
guac_display.cursorY = y;
|
|
};
|
|
this.resize = function(layer, width, height) {
|
|
scheduleTask(function __display_resize() {
|
|
layer.resize(width, height);
|
|
if (layer === default_layer) {
|
|
displayWidth = width;
|
|
displayHeight = height;
|
|
display.style.width = displayWidth + "px";
|
|
display.style.height = displayHeight + "px";
|
|
bounds.style.width = displayWidth * displayScale + "px";
|
|
bounds.style.height = displayHeight * displayScale + "px";
|
|
if (guac_display.onresize)
|
|
guac_display.onresize(width, height);
|
|
}
|
|
});
|
|
};
|
|
this.drawImage = function(layer, x, y, image) {
|
|
scheduleTask(function __display_drawImage() {
|
|
layer.drawImage(x, y, image);
|
|
});
|
|
};
|
|
this.drawBlob = function(layer, x, y, blob) {
|
|
var task;
|
|
if (window.createImageBitmap) {
|
|
var bitmap;
|
|
task = scheduleTask(function drawImageBitmap() {
|
|
layer.drawImage(x, y, bitmap);
|
|
}, true);
|
|
window.createImageBitmap(blob).then(function bitmapLoaded(decoded) {
|
|
bitmap = decoded;
|
|
task.unblock();
|
|
});
|
|
} else {
|
|
var url = URL.createObjectURL(blob);
|
|
task = scheduleTask(function __display_drawBlob() {
|
|
if (image.width && image.height)
|
|
layer.drawImage(x, y, image);
|
|
URL.revokeObjectURL(url);
|
|
}, true);
|
|
var image = new Image();
|
|
image.onload = task.unblock;
|
|
image.onerror = task.unblock;
|
|
image.src = url;
|
|
}
|
|
};
|
|
this.drawStream = function drawStream(layer, x, y, stream, mimetype) {
|
|
if (window.ImageDecoder && window.ReadableStream) {
|
|
var imageDecoder = new ImageDecoder({
|
|
type: mimetype,
|
|
data: stream.toReadableStream()
|
|
});
|
|
var decodedFrame = null;
|
|
var task = scheduleTask(function drawImageBitmap() {
|
|
layer.drawImage(x, y, decodedFrame);
|
|
}, true);
|
|
imageDecoder.decode({ completeFramesOnly: true }).then(function bitmapLoaded(result) {
|
|
decodedFrame = result.image;
|
|
task.unblock();
|
|
});
|
|
} else {
|
|
var reader = new Guacamole.DataURIReader(stream, mimetype);
|
|
reader.onend = function drawImageDataURI() {
|
|
guac_display.draw(layer, x, y, reader.getURI());
|
|
};
|
|
}
|
|
};
|
|
this.draw = function(layer, x, y, url) {
|
|
var task = scheduleTask(function __display_draw() {
|
|
if (image.width && image.height)
|
|
layer.drawImage(x, y, image);
|
|
}, true);
|
|
var image = new Image();
|
|
image.onload = task.unblock;
|
|
image.onerror = task.unblock;
|
|
image.src = url;
|
|
};
|
|
this.play = function(layer, mimetype, duration, url) {
|
|
var video = document.createElement("video");
|
|
video.type = mimetype;
|
|
video.src = url;
|
|
video.addEventListener("play", function() {
|
|
function render_callback() {
|
|
layer.drawImage(0, 0, video);
|
|
if (!video.ended)
|
|
window.setTimeout(render_callback, 20);
|
|
}
|
|
render_callback();
|
|
}, false);
|
|
scheduleTask(video.play);
|
|
};
|
|
this.transfer = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y, transferFunction) {
|
|
scheduleTask(function __display_transfer() {
|
|
dstLayer.transfer(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction);
|
|
});
|
|
};
|
|
this.put = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) {
|
|
scheduleTask(function __display_put() {
|
|
dstLayer.put(srcLayer, srcx, srcy, srcw, srch, x, y);
|
|
});
|
|
};
|
|
this.copy = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) {
|
|
scheduleTask(function __display_copy() {
|
|
dstLayer.copy(srcLayer, srcx, srcy, srcw, srch, x, y);
|
|
});
|
|
};
|
|
this.moveTo = function(layer, x, y) {
|
|
scheduleTask(function __display_moveTo() {
|
|
layer.moveTo(x, y);
|
|
});
|
|
};
|
|
this.lineTo = function(layer, x, y) {
|
|
scheduleTask(function __display_lineTo() {
|
|
layer.lineTo(x, y);
|
|
});
|
|
};
|
|
this.arc = function(layer, x, y, radius, startAngle, endAngle, negative) {
|
|
scheduleTask(function __display_arc() {
|
|
layer.arc(x, y, radius, startAngle, endAngle, negative);
|
|
});
|
|
};
|
|
this.curveTo = function(layer, cp1x, cp1y, cp2x, cp2y, x, y) {
|
|
scheduleTask(function __display_curveTo() {
|
|
layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
|
});
|
|
};
|
|
this.close = function(layer) {
|
|
scheduleTask(function __display_close() {
|
|
layer.close();
|
|
});
|
|
};
|
|
this.rect = function(layer, x, y, w, h) {
|
|
scheduleTask(function __display_rect() {
|
|
layer.rect(x, y, w, h);
|
|
});
|
|
};
|
|
this.clip = function(layer) {
|
|
scheduleTask(function __display_clip() {
|
|
layer.clip();
|
|
});
|
|
};
|
|
this.strokeColor = function(layer, cap, join, thickness, r, g, b, a) {
|
|
scheduleTask(function __display_strokeColor() {
|
|
layer.strokeColor(cap, join, thickness, r, g, b, a);
|
|
});
|
|
};
|
|
this.fillColor = function(layer, r, g, b, a) {
|
|
scheduleTask(function __display_fillColor() {
|
|
layer.fillColor(r, g, b, a);
|
|
});
|
|
};
|
|
this.strokeLayer = function(layer, cap, join, thickness, srcLayer) {
|
|
scheduleTask(function __display_strokeLayer() {
|
|
layer.strokeLayer(cap, join, thickness, srcLayer);
|
|
});
|
|
};
|
|
this.fillLayer = function(layer, srcLayer) {
|
|
scheduleTask(function __display_fillLayer() {
|
|
layer.fillLayer(srcLayer);
|
|
});
|
|
};
|
|
this.push = function(layer) {
|
|
scheduleTask(function __display_push() {
|
|
layer.push();
|
|
});
|
|
};
|
|
this.pop = function(layer) {
|
|
scheduleTask(function __display_pop() {
|
|
layer.pop();
|
|
});
|
|
};
|
|
this.reset = function(layer) {
|
|
scheduleTask(function __display_reset() {
|
|
layer.reset();
|
|
});
|
|
};
|
|
this.setTransform = function(layer, a, b, c, d, e, f) {
|
|
scheduleTask(function __display_setTransform() {
|
|
layer.setTransform(a, b, c, d, e, f);
|
|
});
|
|
};
|
|
this.transform = function(layer, a, b, c, d, e, f) {
|
|
scheduleTask(function __display_transform() {
|
|
layer.transform(a, b, c, d, e, f);
|
|
});
|
|
};
|
|
this.setChannelMask = function(layer, mask) {
|
|
scheduleTask(function __display_setChannelMask() {
|
|
layer.setChannelMask(mask);
|
|
});
|
|
};
|
|
this.setMiterLimit = function(layer, limit) {
|
|
scheduleTask(function __display_setMiterLimit() {
|
|
layer.setMiterLimit(limit);
|
|
});
|
|
};
|
|
this.dispose = function dispose(layer) {
|
|
scheduleTask(function disposeLayer() {
|
|
layer.dispose();
|
|
});
|
|
};
|
|
this.distort = function distort(layer, a, b, c, d, e, f) {
|
|
scheduleTask(function distortLayer() {
|
|
layer.distort(a, b, c, d, e, f);
|
|
});
|
|
};
|
|
this.move = function move(layer, parent, x, y, z) {
|
|
scheduleTask(function moveLayer() {
|
|
layer.move(parent, x, y, z);
|
|
});
|
|
};
|
|
this.shade = function shade(layer, alpha) {
|
|
scheduleTask(function shadeLayer() {
|
|
layer.shade(alpha);
|
|
});
|
|
};
|
|
this.scale = function(scale) {
|
|
display.style.transform = display.style.WebkitTransform = display.style.MozTransform = display.style.OTransform = display.style.msTransform = "scale(" + scale + "," + scale + ")";
|
|
displayScale = scale;
|
|
bounds.style.width = displayWidth * displayScale + "px";
|
|
bounds.style.height = displayHeight * displayScale + "px";
|
|
};
|
|
this.getScale = function() {
|
|
return displayScale;
|
|
};
|
|
this.flatten = function() {
|
|
var canvas = document.createElement("canvas");
|
|
canvas.width = default_layer.width;
|
|
canvas.height = default_layer.height;
|
|
var context = canvas.getContext("2d");
|
|
function get_children(layer) {
|
|
var children = [];
|
|
for (var index in layer.children)
|
|
children.push(layer.children[index]);
|
|
children.sort(function children_comparator(a, b) {
|
|
var diff = a.z - b.z;
|
|
if (diff !== 0)
|
|
return diff;
|
|
var a_element = a.getElement();
|
|
var b_element = b.getElement();
|
|
var position = b_element.compareDocumentPosition(a_element);
|
|
if (position & Node.DOCUMENT_POSITION_PRECEDING) return -1;
|
|
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return 1;
|
|
return 0;
|
|
});
|
|
return children;
|
|
}
|
|
function draw_layer(layer, x, y) {
|
|
if (layer.width > 0 && layer.height > 0) {
|
|
var initial_alpha = context.globalAlpha;
|
|
context.globalAlpha *= layer.alpha / 255;
|
|
context.drawImage(layer.getCanvas(), x, y);
|
|
var children = get_children(layer);
|
|
for (var i = 0; i < children.length; i++) {
|
|
var child = children[i];
|
|
draw_layer(child, x + child.x, y + child.y);
|
|
}
|
|
context.globalAlpha = initial_alpha;
|
|
}
|
|
}
|
|
draw_layer(default_layer, 0, 0);
|
|
return canvas;
|
|
};
|
|
};
|
|
Guacamole.Display.VisibleLayer = function(width, height) {
|
|
Guacamole.Layer.apply(this, [width, height]);
|
|
var layer = this;
|
|
this.__unique_id = Guacamole.Display.VisibleLayer.__next_id++;
|
|
this.alpha = 255;
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.z = 0;
|
|
this.matrix = [1, 0, 0, 1, 0, 0];
|
|
this.parent = null;
|
|
this.children = {};
|
|
var canvas = layer.getCanvas();
|
|
canvas.style.position = "absolute";
|
|
canvas.style.left = "0px";
|
|
canvas.style.top = "0px";
|
|
var div = document.createElement("div");
|
|
div.appendChild(canvas);
|
|
div.style.width = width + "px";
|
|
div.style.height = height + "px";
|
|
div.style.position = "absolute";
|
|
div.style.left = "0px";
|
|
div.style.top = "0px";
|
|
div.style.overflow = "hidden";
|
|
var __super_resize = this.resize;
|
|
this.resize = function(width2, height2) {
|
|
div.style.width = width2 + "px";
|
|
div.style.height = height2 + "px";
|
|
__super_resize(width2, height2);
|
|
};
|
|
this.getElement = function() {
|
|
return div;
|
|
};
|
|
var translate = "translate(0px, 0px)";
|
|
var matrix = "matrix(1, 0, 0, 1, 0, 0)";
|
|
this.translate = function(x, y) {
|
|
layer.x = x;
|
|
layer.y = y;
|
|
translate = "translate(" + x + "px," + y + "px)";
|
|
div.style.transform = div.style.WebkitTransform = div.style.MozTransform = div.style.OTransform = div.style.msTransform = translate + " " + matrix;
|
|
};
|
|
this.move = function(parent, x, y, z) {
|
|
if (layer.parent !== parent) {
|
|
if (layer.parent)
|
|
delete layer.parent.children[layer.__unique_id];
|
|
layer.parent = parent;
|
|
parent.children[layer.__unique_id] = layer;
|
|
var parent_element = parent.getElement();
|
|
parent_element.appendChild(div);
|
|
}
|
|
layer.translate(x, y);
|
|
layer.z = z;
|
|
div.style.zIndex = z;
|
|
};
|
|
this.shade = function(a) {
|
|
layer.alpha = a;
|
|
div.style.opacity = a / 255;
|
|
};
|
|
this.dispose = function() {
|
|
if (layer.parent) {
|
|
delete layer.parent.children[layer.__unique_id];
|
|
layer.parent = null;
|
|
}
|
|
if (div.parentNode)
|
|
div.parentNode.removeChild(div);
|
|
};
|
|
this.distort = function(a, b, c, d, e, f) {
|
|
layer.matrix = [a, b, c, d, e, f];
|
|
matrix = /* a c e
|
|
* b d f
|
|
* 0 0 1
|
|
*/
|
|
"matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")";
|
|
div.style.transform = div.style.WebkitTransform = div.style.MozTransform = div.style.OTransform = div.style.msTransform = translate + " " + matrix;
|
|
};
|
|
};
|
|
Guacamole.Display.VisibleLayer.__next_id = 0;
|
|
Guacamole.Display.Statistics = function Statistics(template) {
|
|
template = template || {};
|
|
this.processingLag = template.processingLag;
|
|
this.desktopFps = template.desktopFps;
|
|
this.serverFps = template.serverFps;
|
|
this.clientFps = template.clientFps;
|
|
this.dropRate = template.dropRate;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Event = function Event(type) {
|
|
this.type = type;
|
|
this.timestamp = (/* @__PURE__ */ new Date()).getTime();
|
|
this.getAge = function getAge() {
|
|
return (/* @__PURE__ */ new Date()).getTime() - this.timestamp;
|
|
};
|
|
this.invokeLegacyHandler = function invokeLegacyHandler(eventTarget) {
|
|
};
|
|
};
|
|
Guacamole.Event.DOMEvent = function DOMEvent(type, events) {
|
|
Guacamole.Event.call(this, type);
|
|
events = events || [];
|
|
if (!Array.isArray(events))
|
|
events = [events];
|
|
this.preventDefault = function preventDefault() {
|
|
events.forEach(function applyPreventDefault(event) {
|
|
if (event.preventDefault) event.preventDefault();
|
|
event.returnValue = false;
|
|
});
|
|
};
|
|
this.stopPropagation = function stopPropagation() {
|
|
events.forEach(function applyStopPropagation(event) {
|
|
event.stopPropagation();
|
|
});
|
|
};
|
|
};
|
|
Guacamole.Event.DOMEvent.cancelEvent = function cancelEvent(event) {
|
|
event.stopPropagation();
|
|
if (event.preventDefault) event.preventDefault();
|
|
event.returnValue = false;
|
|
};
|
|
Guacamole.Event.Target = function Target() {
|
|
var listeners = {};
|
|
this.on = function on(type, listener) {
|
|
var relevantListeners = listeners[type];
|
|
if (!relevantListeners)
|
|
listeners[type] = relevantListeners = [];
|
|
relevantListeners.push(listener);
|
|
};
|
|
this.onEach = function onEach(types, listener) {
|
|
types.forEach(function addListener(type) {
|
|
this.on(type, listener);
|
|
}, this);
|
|
};
|
|
this.dispatch = function dispatch(event) {
|
|
event.invokeLegacyHandler(this);
|
|
var relevantListeners = listeners[event.type];
|
|
if (relevantListeners) {
|
|
for (var i = 0; i < relevantListeners.length; i++) {
|
|
relevantListeners[i](event, this);
|
|
}
|
|
}
|
|
};
|
|
this.off = function off(type, listener) {
|
|
var relevantListeners = listeners[type];
|
|
if (!relevantListeners)
|
|
return false;
|
|
for (var i = 0; i < relevantListeners.length; i++) {
|
|
if (relevantListeners[i] === listener) {
|
|
relevantListeners.splice(i, 1);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
this.offEach = function offEach(types, listener) {
|
|
var changed = false;
|
|
types.forEach(function removeListener(type) {
|
|
changed |= this.off(type, listener);
|
|
}, this);
|
|
return changed;
|
|
};
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.InputSink = function InputSink() {
|
|
var sink = this;
|
|
var field = document.createElement("textarea");
|
|
field.style.position = "fixed";
|
|
field.style.outline = "none";
|
|
field.style.border = "none";
|
|
field.style.margin = "0";
|
|
field.style.padding = "0";
|
|
field.style.height = "0";
|
|
field.style.width = "0";
|
|
field.style.left = "0";
|
|
field.style.bottom = "0";
|
|
field.style.resize = "none";
|
|
field.style.background = "transparent";
|
|
field.style.color = "transparent";
|
|
field.addEventListener("keypress", function clearKeypress(e) {
|
|
field.value = "";
|
|
}, false);
|
|
field.addEventListener("compositionend", function clearCompletedComposition(e) {
|
|
if (e.data)
|
|
field.value = "";
|
|
}, false);
|
|
field.addEventListener("input", function clearCompletedInput(e) {
|
|
if (e.data && !e.isComposing)
|
|
field.value = "";
|
|
}, false);
|
|
field.addEventListener("focus", function focusReceived() {
|
|
window.setTimeout(function deferRefocus() {
|
|
field.click();
|
|
field.select();
|
|
}, 0);
|
|
}, true);
|
|
this.focus = function focus() {
|
|
window.setTimeout(function deferRefocus() {
|
|
field.focus();
|
|
}, 0);
|
|
};
|
|
this.getElement = function getElement() {
|
|
return field;
|
|
};
|
|
document.addEventListener("keydown", function refocusSink(e) {
|
|
var focused = document.activeElement;
|
|
if (focused && focused !== document.body) {
|
|
var rect = focused.getBoundingClientRect();
|
|
if (rect.left + rect.width > 0 && rect.top + rect.height > 0)
|
|
return;
|
|
}
|
|
sink.focus();
|
|
}, true);
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.InputStream = function(client, index) {
|
|
var guac_stream = this;
|
|
this.index = index;
|
|
this.onblob = null;
|
|
this.onend = null;
|
|
this.sendAck = function(message, code) {
|
|
client.sendAck(guac_stream.index, message, code);
|
|
};
|
|
this.toReadableStream = function toReadableStream() {
|
|
return new ReadableStream({
|
|
type: "bytes",
|
|
start: function startStream(controller) {
|
|
var reader = new Guacamole.ArrayBufferReader(guac_stream);
|
|
reader.ondata = function dataReceived(data) {
|
|
if (controller.byobRequest) {
|
|
var view = controller.byobRequest.view;
|
|
var length = Math.min(view.byteLength, data.byteLength);
|
|
var byobBlock = new Uint8Array(data, 0, length);
|
|
view.buffer.set(byobBlock);
|
|
controller.byobRequest.respond(length);
|
|
if (length < data.byteLength) {
|
|
controller.enqueue(data.slice(length));
|
|
}
|
|
} else {
|
|
controller.enqueue(new Uint8Array(data));
|
|
}
|
|
};
|
|
reader.onend = function dataComplete() {
|
|
controller.close();
|
|
};
|
|
}
|
|
});
|
|
};
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.IntegerPool = function() {
|
|
var guac_pool = this;
|
|
var pool = [];
|
|
this.next_int = 0;
|
|
this.next = function() {
|
|
if (pool.length > 0)
|
|
return pool.shift();
|
|
return guac_pool.next_int++;
|
|
};
|
|
this.free = function(integer) {
|
|
pool.push(integer);
|
|
};
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.JSONReader = function guacamoleJSONReader(stream) {
|
|
var guacReader = this;
|
|
var stringReader = new Guacamole.StringReader(stream);
|
|
var json = "";
|
|
this.getLength = function getLength() {
|
|
return json.length;
|
|
};
|
|
this.getJSON = function getJSON() {
|
|
return JSON.parse(json);
|
|
};
|
|
stringReader.ontext = function ontext(text) {
|
|
json += text;
|
|
if (guacReader.onprogress)
|
|
guacReader.onprogress(text.length);
|
|
};
|
|
stringReader.onend = function onend() {
|
|
if (guacReader.onend)
|
|
guacReader.onend();
|
|
};
|
|
this.onprogress = null;
|
|
this.onend = null;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.KeyEventInterpreter = function KeyEventInterpreter(startTimestamp) {
|
|
if (startTimestamp === void 0 || startTimestamp === null)
|
|
startTimestamp = 0;
|
|
var _KNOWN_KEYS = [
|
|
{ keysym: 65027, name: "AltGr" },
|
|
{ keysym: 65288, name: "Backspace" },
|
|
{ keysym: 65289, name: "Tab" },
|
|
{ keysym: 65291, name: "Clear" },
|
|
{ keysym: 65293, name: "Return", value: "\n" },
|
|
{ keysym: 65299, name: "Pause" },
|
|
{ keysym: 65300, name: "Scroll" },
|
|
{ keysym: 65301, name: "SysReq" },
|
|
{ keysym: 65307, name: "Escape" },
|
|
{ keysym: 65360, name: "Home" },
|
|
{ keysym: 65361, name: "Left" },
|
|
{ keysym: 65362, name: "Up" },
|
|
{ keysym: 65363, name: "Right" },
|
|
{ keysym: 65364, name: "Down" },
|
|
{ keysym: 65365, name: "Page Up" },
|
|
{ keysym: 65366, name: "Page Down" },
|
|
{ keysym: 65367, name: "End" },
|
|
{ keysym: 65379, name: "Insert" },
|
|
{ keysym: 65381, name: "Undo" },
|
|
{ keysym: 65386, name: "Help" },
|
|
{ keysym: 65407, name: "Num" },
|
|
{ keysym: 65408, name: "Space", value: " " },
|
|
{ keysym: 65421, name: "Enter", value: "\n" },
|
|
{ keysym: 65429, name: "Home" },
|
|
{ keysym: 65430, name: "Left" },
|
|
{ keysym: 65431, name: "Up" },
|
|
{ keysym: 65432, name: "Right" },
|
|
{ keysym: 65433, name: "Down" },
|
|
{ keysym: 65434, name: "Page Up" },
|
|
{ keysym: 65435, name: "Page Down" },
|
|
{ keysym: 65436, name: "End" },
|
|
{ keysym: 65438, name: "Insert" },
|
|
{ keysym: 65450, name: "*", value: "*" },
|
|
{ keysym: 65451, name: "+", value: "+" },
|
|
{ keysym: 65453, name: "-", value: "-" },
|
|
{ keysym: 65454, name: ".", value: "." },
|
|
{ keysym: 65455, name: "/", value: "/" },
|
|
{ keysym: 65456, name: "0", value: "0" },
|
|
{ keysym: 65457, name: "1", value: "1" },
|
|
{ keysym: 65458, name: "2", value: "2" },
|
|
{ keysym: 65459, name: "3", value: "3" },
|
|
{ keysym: 65460, name: "4", value: "4" },
|
|
{ keysym: 65461, name: "5", value: "5" },
|
|
{ keysym: 65462, name: "6", value: "6" },
|
|
{ keysym: 65463, name: "7", value: "7" },
|
|
{ keysym: 65464, name: "8", value: "8" },
|
|
{ keysym: 65465, name: "9", value: "9" },
|
|
{ keysym: 65470, name: "F1" },
|
|
{ keysym: 65471, name: "F2" },
|
|
{ keysym: 65472, name: "F3" },
|
|
{ keysym: 65473, name: "F4" },
|
|
{ keysym: 65474, name: "F5" },
|
|
{ keysym: 65475, name: "F6" },
|
|
{ keysym: 65476, name: "F7" },
|
|
{ keysym: 65477, name: "F8" },
|
|
{ keysym: 65478, name: "F9" },
|
|
{ keysym: 65479, name: "F10" },
|
|
{ keysym: 65480, name: "F11" },
|
|
{ keysym: 65481, name: "F12" },
|
|
{ keysym: 65482, name: "F13" },
|
|
{ keysym: 65483, name: "F14" },
|
|
{ keysym: 65484, name: "F15" },
|
|
{ keysym: 65485, name: "F16" },
|
|
{ keysym: 65486, name: "F17" },
|
|
{ keysym: 65487, name: "F18" },
|
|
{ keysym: 65488, name: "F19" },
|
|
{ keysym: 65489, name: "F20" },
|
|
{ keysym: 65490, name: "F21" },
|
|
{ keysym: 65491, name: "F22" },
|
|
{ keysym: 65492, name: "F23" },
|
|
{ keysym: 65493, name: "F24" },
|
|
{ keysym: 65505, name: "Shift" },
|
|
{ keysym: 65506, name: "Shift" },
|
|
{ keysym: 65507, name: "Ctrl" },
|
|
{ keysym: 65508, name: "Ctrl" },
|
|
{ keysym: 65509, name: "Caps" },
|
|
{ keysym: 65511, name: "Meta" },
|
|
{ keysym: 65512, name: "Meta" },
|
|
{ keysym: 65513, name: "Alt" },
|
|
{ keysym: 65514, name: "Alt" },
|
|
{ keysym: 65515, name: "Super" },
|
|
{ keysym: 65516, name: "Super" },
|
|
{ keysym: 65517, name: "Hyper" },
|
|
{ keysym: 65518, name: "Hyper" },
|
|
{ keysym: 65535, name: "Delete" }
|
|
];
|
|
var KNOWN_KEYS = {};
|
|
_KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
|
|
KNOWN_KEYS[keyDefinition.keysym] = new Guacamole.KeyEventInterpreter.KeyDefinition(keyDefinition);
|
|
});
|
|
var parsedEvents = [];
|
|
function getUnicodeKeyDefinition(keysym) {
|
|
if (keysym < 0 || keysym > 255 && (keysym | 65535) != 16842751)
|
|
return null;
|
|
var codepoint = keysym & 65535;
|
|
var name = String.fromCharCode(codepoint);
|
|
return new Guacamole.KeyEventInterpreter.KeyDefinition({
|
|
keysym,
|
|
name,
|
|
value: name
|
|
});
|
|
}
|
|
function getKeyDefinitionByKeysym(keysym) {
|
|
if (keysym in KNOWN_KEYS)
|
|
return KNOWN_KEYS[keysym];
|
|
var definition = getUnicodeKeyDefinition(keysym);
|
|
if (definition != null)
|
|
return definition;
|
|
return new Guacamole.KeyEventInterpreter.KeyDefinition({
|
|
keysym,
|
|
name: "0x" + String(keysym.toString(16))
|
|
});
|
|
}
|
|
this.handleKeyEvent = function handleKeyEvent(args) {
|
|
var keysym = parseInt(args[0]);
|
|
var pressed = parseInt(args[1]);
|
|
var timestamp = parseInt(args[2]);
|
|
var relativeTimestap = timestamp - startTimestamp;
|
|
var definition = getKeyDefinitionByKeysym(keysym);
|
|
parsedEvents.push(new Guacamole.KeyEventInterpreter.KeyEvent({
|
|
definition,
|
|
pressed,
|
|
timestamp: relativeTimestap
|
|
}));
|
|
};
|
|
this.getEvents = function getEvents() {
|
|
return parsedEvents;
|
|
};
|
|
};
|
|
Guacamole.KeyEventInterpreter.KeyDefinition = function KeyDefinition(template) {
|
|
template = template || {};
|
|
this.keysym = parseInt(template.keysym);
|
|
this.name = template.name;
|
|
this.value = template.value;
|
|
};
|
|
Guacamole.KeyEventInterpreter.KeyEvent = function KeyEvent(template) {
|
|
template = template || {};
|
|
this.definition = template.definition;
|
|
this.pressed = !!template.pressed;
|
|
this.timestamp = template.timestamp;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Keyboard = function Keyboard(element) {
|
|
var guac_keyboard = this;
|
|
var guacKeyboardID = Guacamole.Keyboard._nextID++;
|
|
var EVENT_MARKER = "_GUAC_KEYBOARD_HANDLED_BY_" + guacKeyboardID;
|
|
this.onkeydown = null;
|
|
this.onkeyup = null;
|
|
var quirks = {
|
|
/**
|
|
* Whether keyup events are universally unreliable.
|
|
*
|
|
* @type {!boolean}
|
|
*/
|
|
keyupUnreliable: false,
|
|
/**
|
|
* Whether the Alt key is actually a modifier for typable keys and is
|
|
* thus never used for keyboard shortcuts.
|
|
*
|
|
* @type {!boolean}
|
|
*/
|
|
altIsTypableOnly: false,
|
|
/**
|
|
* Whether we can rely on receiving a keyup or keydown event for the
|
|
* Caps Lock key.
|
|
*
|
|
* @type {!boolean}
|
|
*/
|
|
capsLockKeyEventUnreliable: false
|
|
};
|
|
if (navigator && navigator.platform) {
|
|
if (navigator.platform.match(/ipad|iphone|ipod/i))
|
|
quirks.keyupUnreliable = true;
|
|
else if (navigator.platform.match(/^mac/i)) {
|
|
quirks.altIsTypableOnly = true;
|
|
quirks.capsLockKeyEventUnreliable = true;
|
|
}
|
|
}
|
|
var KeyEvent2 = function KeyEvent3(orig) {
|
|
var key_event = this;
|
|
this.keyCode = orig ? orig.which || orig.keyCode : 0;
|
|
this.keyIdentifier = orig && orig.keyIdentifier;
|
|
this.key = orig && orig.key;
|
|
this.location = orig ? getEventLocation(orig) : 0;
|
|
this.modifiers = orig ? Guacamole.Keyboard.ModifierState.fromKeyboardEvent(orig) : new Guacamole.Keyboard.ModifierState();
|
|
this.timestamp = (/* @__PURE__ */ new Date()).getTime();
|
|
this.defaultPrevented = false;
|
|
this.keysym = null;
|
|
this.reliable = false;
|
|
this.getAge = function() {
|
|
return (/* @__PURE__ */ new Date()).getTime() - key_event.timestamp;
|
|
};
|
|
};
|
|
var KeydownEvent = function KeydownEvent2(orig) {
|
|
KeyEvent2.call(this, orig);
|
|
this.keysym = keysym_from_key_identifier(this.key, this.location) || keysym_from_keycode(this.keyCode, this.location);
|
|
this.keyupReliable = !quirks.keyupUnreliable;
|
|
if (this.keysym && !isPrintable(this.keysym))
|
|
this.reliable = true;
|
|
if (!this.keysym && key_identifier_sane(this.keyCode, this.keyIdentifier))
|
|
this.keysym = keysym_from_key_identifier(this.keyIdentifier, this.location, this.modifiers.shift);
|
|
if (this.modifiers.meta && this.keysym !== 65511 && this.keysym !== 65512)
|
|
this.keyupReliable = false;
|
|
else if (this.keysym === 65509 && quirks.capsLockKeyEventUnreliable)
|
|
this.keyupReliable = false;
|
|
var prevent_alt = !this.modifiers.ctrl && !quirks.altIsTypableOnly;
|
|
if (quirks.altIsTypableOnly && (this.keysym === 65513 || this.keysym === 65514))
|
|
this.keysym = 65027;
|
|
var prevent_ctrl = !this.modifiers.alt;
|
|
if (prevent_ctrl && this.modifiers.ctrl || prevent_alt && this.modifiers.alt || this.modifiers.meta || this.modifiers.hyper)
|
|
this.reliable = true;
|
|
recentKeysym[this.keyCode] = this.keysym;
|
|
};
|
|
KeydownEvent.prototype = new KeyEvent2();
|
|
var KeypressEvent = function KeypressEvent2(orig) {
|
|
KeyEvent2.call(this, orig);
|
|
this.keysym = keysym_from_charcode(this.keyCode);
|
|
this.reliable = true;
|
|
};
|
|
KeypressEvent.prototype = new KeyEvent2();
|
|
var KeyupEvent = function KeyupEvent2(orig) {
|
|
KeyEvent2.call(this, orig);
|
|
if (this.keyCode == 20 && quirks.capsLockKeyEventUnreliable) {
|
|
eventLog.push(new KeydownEvent(this));
|
|
return;
|
|
}
|
|
this.keysym = keysym_from_keycode(this.keyCode, this.location) || keysym_from_key_identifier(this.key, this.location);
|
|
if (!guac_keyboard.pressed[this.keysym])
|
|
this.keysym = recentKeysym[this.keyCode] || this.keysym;
|
|
this.reliable = true;
|
|
};
|
|
KeyupEvent.prototype = new KeyEvent2();
|
|
var eventLog = [];
|
|
var keycodeKeysyms = {
|
|
8: [65288],
|
|
// backspace
|
|
9: [65289],
|
|
// tab
|
|
12: [65291, 65291, 65291, 65461],
|
|
// clear / KP 5
|
|
13: [65293],
|
|
// enter
|
|
16: [65505, 65505, 65506],
|
|
// shift
|
|
17: [65507, 65507, 65508],
|
|
// ctrl
|
|
18: [65513, 65513, 65514],
|
|
// alt
|
|
19: [65299],
|
|
// pause/break
|
|
20: [65509],
|
|
// caps lock
|
|
27: [65307],
|
|
// escape
|
|
32: [32],
|
|
// space
|
|
33: [65365, 65365, 65365, 65465],
|
|
// page up / KP 9
|
|
34: [65366, 65366, 65366, 65459],
|
|
// page down / KP 3
|
|
35: [65367, 65367, 65367, 65457],
|
|
// end / KP 1
|
|
36: [65360, 65360, 65360, 65463],
|
|
// home / KP 7
|
|
37: [65361, 65361, 65361, 65460],
|
|
// left arrow / KP 4
|
|
38: [65362, 65362, 65362, 65464],
|
|
// up arrow / KP 8
|
|
39: [65363, 65363, 65363, 65462],
|
|
// right arrow / KP 6
|
|
40: [65364, 65364, 65364, 65458],
|
|
// down arrow / KP 2
|
|
45: [65379, 65379, 65379, 65456],
|
|
// insert / KP 0
|
|
46: [65535, 65535, 65535, 65454],
|
|
// delete / KP decimal
|
|
91: [65511],
|
|
// left windows/command key (meta_l)
|
|
92: [65512],
|
|
// right window/command key (meta_r)
|
|
93: [65383],
|
|
// menu key
|
|
96: [65456],
|
|
// KP 0
|
|
97: [65457],
|
|
// KP 1
|
|
98: [65458],
|
|
// KP 2
|
|
99: [65459],
|
|
// KP 3
|
|
100: [65460],
|
|
// KP 4
|
|
101: [65461],
|
|
// KP 5
|
|
102: [65462],
|
|
// KP 6
|
|
103: [65463],
|
|
// KP 7
|
|
104: [65464],
|
|
// KP 8
|
|
105: [65465],
|
|
// KP 9
|
|
106: [65450],
|
|
// KP multiply
|
|
107: [65451],
|
|
// KP add
|
|
109: [65453],
|
|
// KP subtract
|
|
110: [65454],
|
|
// KP decimal
|
|
111: [65455],
|
|
// KP divide
|
|
112: [65470],
|
|
// f1
|
|
113: [65471],
|
|
// f2
|
|
114: [65472],
|
|
// f3
|
|
115: [65473],
|
|
// f4
|
|
116: [65474],
|
|
// f5
|
|
117: [65475],
|
|
// f6
|
|
118: [65476],
|
|
// f7
|
|
119: [65477],
|
|
// f8
|
|
120: [65478],
|
|
// f9
|
|
121: [65479],
|
|
// f10
|
|
122: [65480],
|
|
// f11
|
|
123: [65481],
|
|
// f12
|
|
144: [65407],
|
|
// num lock
|
|
145: [65300],
|
|
// scroll lock
|
|
225: [65027]
|
|
// altgraph (iso_level3_shift)
|
|
};
|
|
var keyidentifier_keysym = {
|
|
"Again": [65382],
|
|
"AllCandidates": [65341],
|
|
"Alphanumeric": [65328],
|
|
"Alt": [65513, 65513, 65514],
|
|
"Attn": [64782],
|
|
"AltGraph": [65027],
|
|
"ArrowDown": [65364],
|
|
"ArrowLeft": [65361],
|
|
"ArrowRight": [65363],
|
|
"ArrowUp": [65362],
|
|
"Backspace": [65288],
|
|
"CapsLock": [65509],
|
|
"Cancel": [65385],
|
|
"Clear": [65291],
|
|
"Convert": [65315],
|
|
"Copy": [64789],
|
|
"Crsel": [64796],
|
|
"CrSel": [64796],
|
|
"CodeInput": [65335],
|
|
"Compose": [65312],
|
|
"Control": [65507, 65507, 65508],
|
|
"ContextMenu": [65383],
|
|
"Delete": [65535],
|
|
"Down": [65364],
|
|
"End": [65367],
|
|
"Enter": [65293],
|
|
"EraseEof": [64774],
|
|
"Escape": [65307],
|
|
"Execute": [65378],
|
|
"Exsel": [64797],
|
|
"ExSel": [64797],
|
|
"F1": [65470],
|
|
"F2": [65471],
|
|
"F3": [65472],
|
|
"F4": [65473],
|
|
"F5": [65474],
|
|
"F6": [65475],
|
|
"F7": [65476],
|
|
"F8": [65477],
|
|
"F9": [65478],
|
|
"F10": [65479],
|
|
"F11": [65480],
|
|
"F12": [65481],
|
|
"F13": [65482],
|
|
"F14": [65483],
|
|
"F15": [65484],
|
|
"F16": [65485],
|
|
"F17": [65486],
|
|
"F18": [65487],
|
|
"F19": [65488],
|
|
"F20": [65489],
|
|
"F21": [65490],
|
|
"F22": [65491],
|
|
"F23": [65492],
|
|
"F24": [65493],
|
|
"Find": [65384],
|
|
"GroupFirst": [65036],
|
|
"GroupLast": [65038],
|
|
"GroupNext": [65032],
|
|
"GroupPrevious": [65034],
|
|
"FullWidth": null,
|
|
"HalfWidth": null,
|
|
"HangulMode": [65329],
|
|
"Hankaku": [65321],
|
|
"HanjaMode": [65332],
|
|
"Help": [65386],
|
|
"Hiragana": [65317],
|
|
"HiraganaKatakana": [65319],
|
|
"Home": [65360],
|
|
"Hyper": [65517, 65517, 65518],
|
|
"Insert": [65379],
|
|
"JapaneseHiragana": [65317],
|
|
"JapaneseKatakana": [65318],
|
|
"JapaneseRomaji": [65316],
|
|
"JunjaMode": [65336],
|
|
"KanaMode": [65325],
|
|
"KanjiMode": [65313],
|
|
"Katakana": [65318],
|
|
"Left": [65361],
|
|
"Meta": [65511, 65511, 65512],
|
|
"ModeChange": [65406],
|
|
"NonConvert": [65314],
|
|
"NumLock": [65407],
|
|
"PageDown": [65366],
|
|
"PageUp": [65365],
|
|
"Pause": [65299],
|
|
"Play": [64790],
|
|
"PreviousCandidate": [65342],
|
|
"PrintScreen": [65377],
|
|
"Redo": [65382],
|
|
"Right": [65363],
|
|
"Romaji": [65316],
|
|
"RomanCharacters": null,
|
|
"Scroll": [65300],
|
|
"Select": [65376],
|
|
"Separator": [65452],
|
|
"Shift": [65505, 65505, 65506],
|
|
"SingleCandidate": [65340],
|
|
"Super": [65515, 65515, 65516],
|
|
"Tab": [65289],
|
|
"UIKeyInputDownArrow": [65364],
|
|
"UIKeyInputEscape": [65307],
|
|
"UIKeyInputLeftArrow": [65361],
|
|
"UIKeyInputRightArrow": [65363],
|
|
"UIKeyInputUpArrow": [65362],
|
|
"Up": [65362],
|
|
"Undo": [65381],
|
|
"Win": [65511, 65511, 65512],
|
|
"Zenkaku": [65320],
|
|
"ZenkakuHankaku": [65322]
|
|
};
|
|
var no_repeat = {
|
|
65027: true,
|
|
// ISO Level 3 Shift (AltGr)
|
|
65505: true,
|
|
// Left shift
|
|
65506: true,
|
|
// Right shift
|
|
65507: true,
|
|
// Left ctrl
|
|
65508: true,
|
|
// Right ctrl
|
|
65509: true,
|
|
// Caps Lock
|
|
65511: true,
|
|
// Left meta
|
|
65512: true,
|
|
// Right meta
|
|
65513: true,
|
|
// Left alt
|
|
65514: true,
|
|
// Right alt
|
|
65515: true,
|
|
// Left super/hyper
|
|
65516: true
|
|
// Right super/hyper
|
|
};
|
|
this.modifiers = new Guacamole.Keyboard.ModifierState();
|
|
this.pressed = {};
|
|
var implicitlyPressed = {};
|
|
var last_keydown_result = {};
|
|
var recentKeysym = {};
|
|
var key_repeat_timeout = null;
|
|
var key_repeat_interval = null;
|
|
var get_keysym = function get_keysym2(keysyms, location) {
|
|
if (!keysyms)
|
|
return null;
|
|
return keysyms[location] || keysyms[0];
|
|
};
|
|
var isPrintable = function isPrintable2(keysym) {
|
|
return keysym >= 0 && keysym <= 255 || (keysym & 4294901760) === 16777216;
|
|
};
|
|
function keysym_from_key_identifier(identifier, location, shifted) {
|
|
if (!identifier)
|
|
return null;
|
|
var typedCharacter;
|
|
var unicodePrefixLocation = identifier.indexOf("U+");
|
|
if (unicodePrefixLocation >= 0) {
|
|
var hex = identifier.substring(unicodePrefixLocation + 2);
|
|
typedCharacter = String.fromCharCode(parseInt(hex, 16));
|
|
} else if (identifier.length === 1 && location !== 3)
|
|
typedCharacter = identifier;
|
|
else
|
|
return get_keysym(keyidentifier_keysym[identifier], location);
|
|
if (shifted === true)
|
|
typedCharacter = typedCharacter.toUpperCase();
|
|
else if (shifted === false)
|
|
typedCharacter = typedCharacter.toLowerCase();
|
|
var codepoint = typedCharacter.charCodeAt(0);
|
|
return keysym_from_charcode(codepoint);
|
|
}
|
|
function isControlCharacter(codepoint) {
|
|
return codepoint <= 31 || codepoint >= 127 && codepoint <= 159;
|
|
}
|
|
function keysym_from_charcode(codepoint) {
|
|
if (isControlCharacter(codepoint)) return 65280 | codepoint;
|
|
if (codepoint >= 0 && codepoint <= 255)
|
|
return codepoint;
|
|
if (codepoint >= 256 && codepoint <= 1114111)
|
|
return 16777216 | codepoint;
|
|
return null;
|
|
}
|
|
function keysym_from_keycode(keyCode, location) {
|
|
return get_keysym(keycodeKeysyms[keyCode], location);
|
|
}
|
|
var key_identifier_sane = function key_identifier_sane2(keyCode, keyIdentifier) {
|
|
if (!keyIdentifier)
|
|
return false;
|
|
var unicodePrefixLocation = keyIdentifier.indexOf("U+");
|
|
if (unicodePrefixLocation === -1)
|
|
return true;
|
|
var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation + 2), 16);
|
|
if (keyCode !== codepoint)
|
|
return true;
|
|
if (keyCode >= 65 && keyCode <= 90 || keyCode >= 48 && keyCode <= 57)
|
|
return true;
|
|
return false;
|
|
};
|
|
this.press = function(keysym) {
|
|
if (keysym === null) return;
|
|
if (!guac_keyboard.pressed[keysym]) {
|
|
guac_keyboard.pressed[keysym] = true;
|
|
if (guac_keyboard.onkeydown) {
|
|
var result = guac_keyboard.onkeydown(keysym);
|
|
last_keydown_result[keysym] = result;
|
|
window.clearTimeout(key_repeat_timeout);
|
|
window.clearInterval(key_repeat_interval);
|
|
if (!no_repeat[keysym])
|
|
key_repeat_timeout = window.setTimeout(function() {
|
|
key_repeat_interval = window.setInterval(function() {
|
|
guac_keyboard.onkeyup(keysym);
|
|
guac_keyboard.onkeydown(keysym);
|
|
}, 50);
|
|
}, 500);
|
|
return result;
|
|
}
|
|
}
|
|
return last_keydown_result[keysym] || false;
|
|
};
|
|
this.release = function(keysym) {
|
|
if (guac_keyboard.pressed[keysym]) {
|
|
delete guac_keyboard.pressed[keysym];
|
|
delete implicitlyPressed[keysym];
|
|
window.clearTimeout(key_repeat_timeout);
|
|
window.clearInterval(key_repeat_interval);
|
|
if (keysym !== null && guac_keyboard.onkeyup)
|
|
guac_keyboard.onkeyup(keysym);
|
|
}
|
|
};
|
|
this.type = function type(str) {
|
|
for (var i = 0; i < str.length; i++) {
|
|
var codepoint = str.codePointAt ? str.codePointAt(i) : str.charCodeAt(i);
|
|
var keysym = keysym_from_charcode(codepoint);
|
|
guac_keyboard.press(keysym);
|
|
guac_keyboard.release(keysym);
|
|
}
|
|
};
|
|
this.reset = function() {
|
|
for (var keysym in guac_keyboard.pressed)
|
|
guac_keyboard.release(parseInt(keysym));
|
|
eventLog = [];
|
|
};
|
|
var updateModifierState = function updateModifierState2(modifier, keysyms, keyEvent) {
|
|
var localState = keyEvent.modifiers[modifier];
|
|
var remoteState = guac_keyboard.modifiers[modifier];
|
|
var i;
|
|
if (keysyms.indexOf(keyEvent.keysym) !== -1)
|
|
return;
|
|
if (remoteState && localState === false) {
|
|
for (i = 0; i < keysyms.length; i++) {
|
|
guac_keyboard.release(keysyms[i]);
|
|
}
|
|
} else if (!remoteState && localState) {
|
|
for (i = 0; i < keysyms.length; i++) {
|
|
if (guac_keyboard.pressed[keysyms[i]])
|
|
return;
|
|
}
|
|
var keysym = keysyms[0];
|
|
if (keyEvent.keysym)
|
|
implicitlyPressed[keysym] = true;
|
|
guac_keyboard.press(keysym);
|
|
}
|
|
};
|
|
var syncModifierStates = function syncModifierStates2(keyEvent) {
|
|
updateModifierState("alt", [
|
|
65513,
|
|
// Left alt
|
|
65514,
|
|
// Right alt
|
|
65027
|
|
// AltGr
|
|
], keyEvent);
|
|
updateModifierState("shift", [
|
|
65505,
|
|
// Left shift
|
|
65506
|
|
// Right shift
|
|
], keyEvent);
|
|
updateModifierState("ctrl", [
|
|
65507,
|
|
// Left ctrl
|
|
65508
|
|
// Right ctrl
|
|
], keyEvent);
|
|
updateModifierState("meta", [
|
|
65511,
|
|
// Left meta
|
|
65512
|
|
// Right meta
|
|
], keyEvent);
|
|
updateModifierState("hyper", [
|
|
65515,
|
|
// Left super/hyper
|
|
65516
|
|
// Right super/hyper
|
|
], keyEvent);
|
|
guac_keyboard.modifiers = keyEvent.modifiers;
|
|
};
|
|
var isStateImplicit = function isStateImplicit2() {
|
|
for (var keysym in guac_keyboard.pressed) {
|
|
if (!implicitlyPressed[keysym])
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
function interpret_events() {
|
|
var handled_event = interpret_event();
|
|
if (!handled_event)
|
|
return false;
|
|
var last_event;
|
|
do {
|
|
last_event = handled_event;
|
|
handled_event = interpret_event();
|
|
} while (handled_event !== null);
|
|
if (isStateImplicit())
|
|
guac_keyboard.reset();
|
|
return last_event.defaultPrevented;
|
|
}
|
|
var release_simulated_altgr = function release_simulated_altgr2(keysym) {
|
|
if (!guac_keyboard.modifiers.ctrl || !guac_keyboard.modifiers.alt)
|
|
return;
|
|
if (keysym >= 65 && keysym <= 90)
|
|
return;
|
|
if (keysym >= 97 && keysym <= 122)
|
|
return;
|
|
if (keysym <= 255 || (keysym & 4278190080) === 16777216) {
|
|
guac_keyboard.release(65507);
|
|
guac_keyboard.release(65508);
|
|
guac_keyboard.release(65513);
|
|
guac_keyboard.release(65514);
|
|
}
|
|
};
|
|
var interpret_event = function interpret_event2() {
|
|
var first = eventLog[0];
|
|
if (!first)
|
|
return null;
|
|
if (first instanceof KeydownEvent) {
|
|
var keysym = null;
|
|
var accepted_events = [];
|
|
if (first.keysym === 65511 || first.keysym === 65512) {
|
|
if (eventLog.length === 1)
|
|
return null;
|
|
if (eventLog[1].keysym !== first.keysym) {
|
|
if (!eventLog[1].modifiers.meta)
|
|
return eventLog.shift();
|
|
} else if (eventLog[1] instanceof KeydownEvent)
|
|
return eventLog.shift();
|
|
}
|
|
if (first.reliable) {
|
|
keysym = first.keysym;
|
|
accepted_events = eventLog.splice(0, 1);
|
|
} else if (eventLog[1] instanceof KeypressEvent) {
|
|
keysym = eventLog[1].keysym;
|
|
accepted_events = eventLog.splice(0, 2);
|
|
} else if (eventLog[1]) {
|
|
keysym = first.keysym;
|
|
accepted_events = eventLog.splice(0, 1);
|
|
}
|
|
if (accepted_events.length > 0) {
|
|
syncModifierStates(first);
|
|
if (keysym) {
|
|
release_simulated_altgr(keysym);
|
|
var defaultPrevented = !guac_keyboard.press(keysym);
|
|
recentKeysym[first.keyCode] = keysym;
|
|
if (!first.keyupReliable)
|
|
guac_keyboard.release(keysym);
|
|
for (var i = 0; i < accepted_events.length; i++)
|
|
accepted_events[i].defaultPrevented = defaultPrevented;
|
|
}
|
|
return first;
|
|
}
|
|
} else if (first instanceof KeyupEvent && !quirks.keyupUnreliable) {
|
|
var keysym = first.keysym;
|
|
if (keysym) {
|
|
guac_keyboard.release(keysym);
|
|
delete recentKeysym[first.keyCode];
|
|
first.defaultPrevented = true;
|
|
} else {
|
|
guac_keyboard.reset();
|
|
return first;
|
|
}
|
|
syncModifierStates(first);
|
|
return eventLog.shift();
|
|
} else
|
|
return eventLog.shift();
|
|
return null;
|
|
};
|
|
var getEventLocation = function getEventLocation2(e) {
|
|
if ("location" in e)
|
|
return e.location;
|
|
if ("keyLocation" in e)
|
|
return e.keyLocation;
|
|
return 0;
|
|
};
|
|
var markEvent = function markEvent2(e) {
|
|
if (e[EVENT_MARKER])
|
|
return false;
|
|
e[EVENT_MARKER] = true;
|
|
return true;
|
|
};
|
|
this.listenTo = function listenTo(element2) {
|
|
element2.addEventListener("keydown", function(e) {
|
|
if (!guac_keyboard.onkeydown) return;
|
|
if (!markEvent(e)) return;
|
|
var keydownEvent = new KeydownEvent(e);
|
|
if (e.isComposing || keydownEvent.keyCode === 229)
|
|
return;
|
|
eventLog.push(keydownEvent);
|
|
if (interpret_events())
|
|
e.preventDefault();
|
|
}, true);
|
|
element2.addEventListener("keypress", function(e) {
|
|
if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
|
|
if (!markEvent(e)) return;
|
|
eventLog.push(new KeypressEvent(e));
|
|
if (interpret_events())
|
|
e.preventDefault();
|
|
}, true);
|
|
element2.addEventListener("keyup", function(e) {
|
|
if (!guac_keyboard.onkeyup) return;
|
|
if (!markEvent(e)) return;
|
|
e.preventDefault();
|
|
eventLog.push(new KeyupEvent(e));
|
|
interpret_events();
|
|
}, true);
|
|
var handleInput = function handleInput2(e) {
|
|
if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
|
|
if (!markEvent(e)) return;
|
|
if (e.data && !e.isComposing)
|
|
guac_keyboard.type(e.data);
|
|
};
|
|
var handleCompositionStart = function handleCompositionStart2(e) {
|
|
element2.removeEventListener("input", handleInput, false);
|
|
};
|
|
var handleCompositionEnd = function handleCompositionEnd2(e) {
|
|
if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
|
|
if (!markEvent(e)) return;
|
|
if (e.data)
|
|
guac_keyboard.type(e.data);
|
|
};
|
|
element2.addEventListener("input", handleInput, false);
|
|
element2.addEventListener("compositionend", handleCompositionEnd, false);
|
|
element2.addEventListener("compositionstart", handleCompositionStart, false);
|
|
};
|
|
if (element)
|
|
guac_keyboard.listenTo(element);
|
|
};
|
|
Guacamole.Keyboard._nextID = 0;
|
|
Guacamole.Keyboard.ModifierState = function() {
|
|
this.shift = false;
|
|
this.ctrl = false;
|
|
this.alt = false;
|
|
this.meta = false;
|
|
this.hyper = false;
|
|
};
|
|
Guacamole.Keyboard.ModifierState.fromKeyboardEvent = function(e) {
|
|
var state = new Guacamole.Keyboard.ModifierState();
|
|
state.shift = e.shiftKey;
|
|
state.ctrl = e.ctrlKey;
|
|
state.alt = e.altKey;
|
|
state.meta = e.metaKey;
|
|
if (e.getModifierState) {
|
|
state.hyper = e.getModifierState("OS") || e.getModifierState("Super") || e.getModifierState("Hyper") || e.getModifierState("Win");
|
|
}
|
|
return state;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Layer = function(width, height) {
|
|
var layer = this;
|
|
var CANVAS_SIZE_FACTOR = 64;
|
|
var canvas = document.createElement("canvas");
|
|
var context = canvas.getContext("2d");
|
|
context.save();
|
|
var empty = true;
|
|
var pathClosed = true;
|
|
var stackSize = 0;
|
|
var compositeOperation = {
|
|
/* 0x0 NOT IMPLEMENTED */
|
|
1: "destination-in",
|
|
2: "destination-out",
|
|
/* 0x3 NOT IMPLEMENTED */
|
|
4: "source-in",
|
|
/* 0x5 NOT IMPLEMENTED */
|
|
6: "source-atop",
|
|
/* 0x7 NOT IMPLEMENTED */
|
|
8: "source-out",
|
|
9: "destination-atop",
|
|
10: "xor",
|
|
11: "destination-over",
|
|
12: "copy",
|
|
/* 0xD NOT IMPLEMENTED */
|
|
14: "source-over",
|
|
15: "lighter"
|
|
};
|
|
var resize = function resize2(newWidth, newHeight) {
|
|
newWidth = newWidth || 0;
|
|
newHeight = newHeight || 0;
|
|
var canvasWidth = Math.ceil(newWidth / CANVAS_SIZE_FACTOR) * CANVAS_SIZE_FACTOR;
|
|
var canvasHeight = Math.ceil(newHeight / CANVAS_SIZE_FACTOR) * CANVAS_SIZE_FACTOR;
|
|
if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {
|
|
var oldData = null;
|
|
if (!empty && canvas.width !== 0 && canvas.height !== 0) {
|
|
oldData = document.createElement("canvas");
|
|
oldData.width = Math.min(layer.width, newWidth);
|
|
oldData.height = Math.min(layer.height, newHeight);
|
|
var oldDataContext = oldData.getContext("2d");
|
|
oldDataContext.drawImage(
|
|
canvas,
|
|
0,
|
|
0,
|
|
oldData.width,
|
|
oldData.height,
|
|
0,
|
|
0,
|
|
oldData.width,
|
|
oldData.height
|
|
);
|
|
}
|
|
var oldCompositeOperation = context.globalCompositeOperation;
|
|
canvas.width = canvasWidth;
|
|
canvas.height = canvasHeight;
|
|
if (oldData)
|
|
context.drawImage(
|
|
oldData,
|
|
0,
|
|
0,
|
|
oldData.width,
|
|
oldData.height,
|
|
0,
|
|
0,
|
|
oldData.width,
|
|
oldData.height
|
|
);
|
|
context.globalCompositeOperation = oldCompositeOperation;
|
|
stackSize = 0;
|
|
context.save();
|
|
} else
|
|
layer.reset();
|
|
layer.width = newWidth;
|
|
layer.height = newHeight;
|
|
};
|
|
function fitRect(x, y, w, h) {
|
|
var opBoundX = w + x;
|
|
var opBoundY = h + y;
|
|
var resizeWidth;
|
|
if (opBoundX > layer.width)
|
|
resizeWidth = opBoundX;
|
|
else
|
|
resizeWidth = layer.width;
|
|
var resizeHeight;
|
|
if (opBoundY > layer.height)
|
|
resizeHeight = opBoundY;
|
|
else
|
|
resizeHeight = layer.height;
|
|
layer.resize(resizeWidth, resizeHeight);
|
|
}
|
|
this.autosize = false;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.getCanvas = function getCanvas() {
|
|
return canvas;
|
|
};
|
|
this.toCanvas = function toCanvas() {
|
|
var canvas2 = document.createElement("canvas");
|
|
canvas2.width = layer.width;
|
|
canvas2.height = layer.height;
|
|
var context2 = canvas2.getContext("2d");
|
|
context2.drawImage(layer.getCanvas(), 0, 0);
|
|
return canvas2;
|
|
};
|
|
this.resize = function(newWidth, newHeight) {
|
|
if (newWidth !== layer.width || newHeight !== layer.height)
|
|
resize(newWidth, newHeight);
|
|
};
|
|
this.drawImage = function(x, y, image) {
|
|
if (layer.autosize) fitRect(x, y, image.width, image.height);
|
|
context.drawImage(image, x, y);
|
|
empty = false;
|
|
};
|
|
this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) {
|
|
var srcCanvas = srcLayer.getCanvas();
|
|
if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
|
|
if (srcx + srcw > srcCanvas.width)
|
|
srcw = srcCanvas.width - srcx;
|
|
if (srcy + srch > srcCanvas.height)
|
|
srch = srcCanvas.height - srcy;
|
|
if (srcw === 0 || srch === 0) return;
|
|
if (layer.autosize) fitRect(x, y, srcw, srch);
|
|
var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
|
|
var dst = context.getImageData(x, y, srcw, srch);
|
|
for (var i = 0; i < srcw * srch * 4; i += 4) {
|
|
var src_pixel = new Guacamole.Layer.Pixel(
|
|
src.data[i],
|
|
src.data[i + 1],
|
|
src.data[i + 2],
|
|
src.data[i + 3]
|
|
);
|
|
var dst_pixel = new Guacamole.Layer.Pixel(
|
|
dst.data[i],
|
|
dst.data[i + 1],
|
|
dst.data[i + 2],
|
|
dst.data[i + 3]
|
|
);
|
|
transferFunction(src_pixel, dst_pixel);
|
|
dst.data[i] = dst_pixel.red;
|
|
dst.data[i + 1] = dst_pixel.green;
|
|
dst.data[i + 2] = dst_pixel.blue;
|
|
dst.data[i + 3] = dst_pixel.alpha;
|
|
}
|
|
context.putImageData(dst, x, y);
|
|
empty = false;
|
|
};
|
|
this.put = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
|
|
var srcCanvas = srcLayer.getCanvas();
|
|
if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
|
|
if (srcx + srcw > srcCanvas.width)
|
|
srcw = srcCanvas.width - srcx;
|
|
if (srcy + srch > srcCanvas.height)
|
|
srch = srcCanvas.height - srcy;
|
|
if (srcw === 0 || srch === 0) return;
|
|
if (layer.autosize) fitRect(x, y, srcw, srch);
|
|
var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
|
|
context.putImageData(src, x, y);
|
|
empty = false;
|
|
};
|
|
this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
|
|
var srcCanvas = srcLayer.getCanvas();
|
|
if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
|
|
if (srcx + srcw > srcCanvas.width)
|
|
srcw = srcCanvas.width - srcx;
|
|
if (srcy + srch > srcCanvas.height)
|
|
srch = srcCanvas.height - srcy;
|
|
if (srcw === 0 || srch === 0) return;
|
|
if (layer.autosize) fitRect(x, y, srcw, srch);
|
|
context.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
|
|
empty = false;
|
|
};
|
|
this.moveTo = function(x, y) {
|
|
if (pathClosed) {
|
|
context.beginPath();
|
|
pathClosed = false;
|
|
}
|
|
if (layer.autosize) fitRect(x, y, 0, 0);
|
|
context.moveTo(x, y);
|
|
};
|
|
this.lineTo = function(x, y) {
|
|
if (pathClosed) {
|
|
context.beginPath();
|
|
pathClosed = false;
|
|
}
|
|
if (layer.autosize) fitRect(x, y, 0, 0);
|
|
context.lineTo(x, y);
|
|
};
|
|
this.arc = function(x, y, radius, startAngle, endAngle, negative) {
|
|
if (pathClosed) {
|
|
context.beginPath();
|
|
pathClosed = false;
|
|
}
|
|
if (layer.autosize) fitRect(x, y, 0, 0);
|
|
context.arc(x, y, radius, startAngle, endAngle, negative);
|
|
};
|
|
this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
|
|
if (pathClosed) {
|
|
context.beginPath();
|
|
pathClosed = false;
|
|
}
|
|
if (layer.autosize) fitRect(x, y, 0, 0);
|
|
context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
|
};
|
|
this.close = function() {
|
|
context.closePath();
|
|
pathClosed = true;
|
|
};
|
|
this.rect = function(x, y, w, h) {
|
|
if (pathClosed) {
|
|
context.beginPath();
|
|
pathClosed = false;
|
|
}
|
|
if (layer.autosize) fitRect(x, y, w, h);
|
|
context.rect(x, y, w, h);
|
|
};
|
|
this.clip = function() {
|
|
context.clip();
|
|
pathClosed = true;
|
|
};
|
|
this.strokeColor = function(cap, join, thickness, r, g, b, a) {
|
|
context.lineCap = cap;
|
|
context.lineJoin = join;
|
|
context.lineWidth = thickness;
|
|
context.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a / 255 + ")";
|
|
context.stroke();
|
|
empty = false;
|
|
pathClosed = true;
|
|
};
|
|
this.fillColor = function(r, g, b, a) {
|
|
context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a / 255 + ")";
|
|
context.fill();
|
|
empty = false;
|
|
pathClosed = true;
|
|
};
|
|
this.strokeLayer = function(cap, join, thickness, srcLayer) {
|
|
context.lineCap = cap;
|
|
context.lineJoin = join;
|
|
context.lineWidth = thickness;
|
|
context.strokeStyle = context.createPattern(
|
|
srcLayer.getCanvas(),
|
|
"repeat"
|
|
);
|
|
context.stroke();
|
|
empty = false;
|
|
pathClosed = true;
|
|
};
|
|
this.fillLayer = function(srcLayer) {
|
|
context.fillStyle = context.createPattern(
|
|
srcLayer.getCanvas(),
|
|
"repeat"
|
|
);
|
|
context.fill();
|
|
empty = false;
|
|
pathClosed = true;
|
|
};
|
|
this.push = function() {
|
|
context.save();
|
|
stackSize++;
|
|
};
|
|
this.pop = function() {
|
|
if (stackSize > 0) {
|
|
context.restore();
|
|
stackSize--;
|
|
}
|
|
};
|
|
this.reset = function() {
|
|
while (stackSize > 0) {
|
|
context.restore();
|
|
stackSize--;
|
|
}
|
|
context.restore();
|
|
context.save();
|
|
context.beginPath();
|
|
pathClosed = false;
|
|
};
|
|
this.setTransform = function(a, b, c, d, e, f) {
|
|
context.setTransform(
|
|
a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f
|
|
/*0, 0, 1*/
|
|
);
|
|
};
|
|
this.transform = function(a, b, c, d, e, f) {
|
|
context.transform(
|
|
a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f
|
|
/*0, 0, 1*/
|
|
);
|
|
};
|
|
this.setChannelMask = function(mask) {
|
|
context.globalCompositeOperation = compositeOperation[mask];
|
|
};
|
|
this.setMiterLimit = function(limit) {
|
|
context.miterLimit = limit;
|
|
};
|
|
resize(width, height);
|
|
canvas.style.zIndex = -1;
|
|
};
|
|
Guacamole.Layer.ROUT = 2;
|
|
Guacamole.Layer.ATOP = 6;
|
|
Guacamole.Layer.XOR = 10;
|
|
Guacamole.Layer.ROVER = 11;
|
|
Guacamole.Layer.OVER = 14;
|
|
Guacamole.Layer.PLUS = 15;
|
|
Guacamole.Layer.RIN = 1;
|
|
Guacamole.Layer.IN = 4;
|
|
Guacamole.Layer.OUT = 8;
|
|
Guacamole.Layer.RATOP = 9;
|
|
Guacamole.Layer.SRC = 12;
|
|
Guacamole.Layer.Pixel = function(r, g, b, a) {
|
|
this.red = r;
|
|
this.green = g;
|
|
this.blue = b;
|
|
this.alpha = a;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Mouse = function Mouse(element) {
|
|
Guacamole.Mouse.Event.Target.call(this);
|
|
var guac_mouse = this;
|
|
this.touchMouseThreshold = 3;
|
|
this.scrollThreshold = 53;
|
|
this.PIXELS_PER_LINE = 18;
|
|
this.PIXELS_PER_PAGE = this.PIXELS_PER_LINE * 16;
|
|
var MOUSE_BUTTONS = [
|
|
Guacamole.Mouse.State.Buttons.LEFT,
|
|
Guacamole.Mouse.State.Buttons.MIDDLE,
|
|
Guacamole.Mouse.State.Buttons.RIGHT
|
|
];
|
|
var ignore_mouse = 0;
|
|
var scroll_delta = 0;
|
|
element.addEventListener("contextmenu", function(e) {
|
|
Guacamole.Event.DOMEvent.cancelEvent(e);
|
|
}, false);
|
|
element.addEventListener("mousemove", function(e) {
|
|
if (ignore_mouse) {
|
|
Guacamole.Event.DOMEvent.cancelEvent(e);
|
|
ignore_mouse--;
|
|
return;
|
|
}
|
|
guac_mouse.move(Guacamole.Position.fromClientPosition(element, e.clientX, e.clientY), e);
|
|
}, false);
|
|
element.addEventListener("mousedown", function(e) {
|
|
if (ignore_mouse) {
|
|
Guacamole.Event.DOMEvent.cancelEvent(e);
|
|
return;
|
|
}
|
|
var button = MOUSE_BUTTONS[e.button];
|
|
if (button)
|
|
guac_mouse.press(button, e);
|
|
}, false);
|
|
element.addEventListener("mouseup", function(e) {
|
|
if (ignore_mouse) {
|
|
Guacamole.Event.DOMEvent.cancelEvent(e);
|
|
return;
|
|
}
|
|
var button = MOUSE_BUTTONS[e.button];
|
|
if (button)
|
|
guac_mouse.release(button, e);
|
|
}, false);
|
|
element.addEventListener("mouseout", function(e) {
|
|
if (!e) e = window.event;
|
|
var target = e.relatedTarget || e.toElement;
|
|
while (target) {
|
|
if (target === element)
|
|
return;
|
|
target = target.parentNode;
|
|
}
|
|
guac_mouse.reset(e);
|
|
guac_mouse.out(e);
|
|
}, false);
|
|
element.addEventListener("selectstart", function(e) {
|
|
Guacamole.Event.DOMEvent.cancelEvent(e);
|
|
}, false);
|
|
function ignorePendingMouseEvents() {
|
|
ignore_mouse = guac_mouse.touchMouseThreshold;
|
|
}
|
|
element.addEventListener("touchmove", ignorePendingMouseEvents, false);
|
|
element.addEventListener("touchstart", ignorePendingMouseEvents, false);
|
|
element.addEventListener("touchend", ignorePendingMouseEvents, false);
|
|
function mousewheel_handler(e) {
|
|
var delta = e.deltaY || -e.wheelDeltaY || -e.wheelDelta;
|
|
if (delta) {
|
|
if (e.deltaMode === 1)
|
|
delta = e.deltaY * guac_mouse.PIXELS_PER_LINE;
|
|
else if (e.deltaMode === 2)
|
|
delta = e.deltaY * guac_mouse.PIXELS_PER_PAGE;
|
|
} else
|
|
delta = e.detail * guac_mouse.PIXELS_PER_LINE;
|
|
scroll_delta += delta;
|
|
if (scroll_delta <= -guac_mouse.scrollThreshold) {
|
|
do {
|
|
guac_mouse.click(Guacamole.Mouse.State.Buttons.UP);
|
|
scroll_delta += guac_mouse.scrollThreshold;
|
|
} while (scroll_delta <= -guac_mouse.scrollThreshold);
|
|
scroll_delta = 0;
|
|
}
|
|
if (scroll_delta >= guac_mouse.scrollThreshold) {
|
|
do {
|
|
guac_mouse.click(Guacamole.Mouse.State.Buttons.DOWN);
|
|
scroll_delta -= guac_mouse.scrollThreshold;
|
|
} while (scroll_delta >= guac_mouse.scrollThreshold);
|
|
scroll_delta = 0;
|
|
}
|
|
Guacamole.Event.DOMEvent.cancelEvent(e);
|
|
}
|
|
if (window.WheelEvent) {
|
|
element.addEventListener("wheel", mousewheel_handler, false);
|
|
} else {
|
|
element.addEventListener("DOMMouseScroll", mousewheel_handler, false);
|
|
element.addEventListener("mousewheel", mousewheel_handler, false);
|
|
}
|
|
var CSS3_CURSOR_SUPPORTED = (function() {
|
|
var div = document.createElement("div");
|
|
if (!("cursor" in div.style))
|
|
return false;
|
|
try {
|
|
div.style.cursor = "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX///+nxBvIAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==) 0 0, auto";
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
return /\burl\([^()]*\)\s+0\s+0\b/.test(div.style.cursor || "");
|
|
})();
|
|
this.setCursor = function(canvas, x, y) {
|
|
if (CSS3_CURSOR_SUPPORTED) {
|
|
var dataURL = canvas.toDataURL("image/png");
|
|
element.style.cursor = "url(" + dataURL + ") " + x + " " + y + ", auto";
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
};
|
|
Guacamole.Mouse.State = function State(template) {
|
|
var legacyConstructor = function legacyConstructor2(x, y, left, middle, right, up, down) {
|
|
return {
|
|
x,
|
|
y,
|
|
left,
|
|
middle,
|
|
right,
|
|
up,
|
|
down
|
|
};
|
|
};
|
|
if (arguments.length > 1)
|
|
template = legacyConstructor.apply(this, arguments);
|
|
else
|
|
template = template || {};
|
|
Guacamole.Position.call(this, template);
|
|
this.left = template.left || false;
|
|
this.middle = template.middle || false;
|
|
this.right = template.right || false;
|
|
this.up = template.up || false;
|
|
this.down = template.down || false;
|
|
};
|
|
Guacamole.Mouse.State.Buttons = {
|
|
/**
|
|
* The name of the {@link Guacamole.Mouse.State} property representing the
|
|
* left mouse button.
|
|
*
|
|
* @constant
|
|
* @type {!string}
|
|
*/
|
|
LEFT: "left",
|
|
/**
|
|
* The name of the {@link Guacamole.Mouse.State} property representing the
|
|
* middle mouse button.
|
|
*
|
|
* @constant
|
|
* @type {!string}
|
|
*/
|
|
MIDDLE: "middle",
|
|
/**
|
|
* The name of the {@link Guacamole.Mouse.State} property representing the
|
|
* right mouse button.
|
|
*
|
|
* @constant
|
|
* @type {!string}
|
|
*/
|
|
RIGHT: "right",
|
|
/**
|
|
* The name of the {@link Guacamole.Mouse.State} property representing the
|
|
* up mouse button (the fourth mouse button, clicked when the mouse scroll
|
|
* wheel is scrolled up).
|
|
*
|
|
* @constant
|
|
* @type {!string}
|
|
*/
|
|
UP: "up",
|
|
/**
|
|
* The name of the {@link Guacamole.Mouse.State} property representing the
|
|
* down mouse button (the fifth mouse button, clicked when the mouse scroll
|
|
* wheel is scrolled up).
|
|
*
|
|
* @constant
|
|
* @type {!string}
|
|
*/
|
|
DOWN: "down"
|
|
};
|
|
Guacamole.Mouse.Event = function MouseEvent(type, state, events) {
|
|
Guacamole.Event.DOMEvent.call(this, type, events);
|
|
var legacyHandlerName = "on" + this.type;
|
|
this.state = state;
|
|
this.invokeLegacyHandler = function invokeLegacyHandler(target) {
|
|
if (target[legacyHandlerName]) {
|
|
this.preventDefault();
|
|
this.stopPropagation();
|
|
target[legacyHandlerName](this.state);
|
|
}
|
|
};
|
|
};
|
|
Guacamole.Mouse.Event.Target = function MouseEventTarget() {
|
|
Guacamole.Event.Target.call(this);
|
|
this.currentState = new Guacamole.Mouse.State();
|
|
this.press = function press(button, events) {
|
|
if (!this.currentState[button]) {
|
|
this.currentState[button] = true;
|
|
this.dispatch(new Guacamole.Mouse.Event("mousedown", this.currentState, events));
|
|
}
|
|
};
|
|
this.release = function release(button, events) {
|
|
if (this.currentState[button]) {
|
|
this.currentState[button] = false;
|
|
this.dispatch(new Guacamole.Mouse.Event("mouseup", this.currentState, events));
|
|
}
|
|
};
|
|
this.click = function click(button, events) {
|
|
this.press(button, events);
|
|
this.release(button, events);
|
|
};
|
|
this.move = function move(position, events) {
|
|
if (this.currentState.x !== position.x || this.currentState.y !== position.y) {
|
|
this.currentState.x = position.x;
|
|
this.currentState.y = position.y;
|
|
this.dispatch(new Guacamole.Mouse.Event("mousemove", this.currentState, events));
|
|
}
|
|
};
|
|
this.out = function out(events) {
|
|
this.dispatch(new Guacamole.Mouse.Event("mouseout", this.currentState, events));
|
|
};
|
|
this.reset = function reset(events) {
|
|
for (var button in Guacamole.Mouse.State.Buttons) {
|
|
this.release(Guacamole.Mouse.State.Buttons[button], events);
|
|
}
|
|
};
|
|
};
|
|
Guacamole.Mouse.Touchpad = function Touchpad(element) {
|
|
Guacamole.Mouse.Event.Target.call(this);
|
|
var guac_touchpad = this;
|
|
this.scrollThreshold = 20 * (window.devicePixelRatio || 1);
|
|
this.clickTimingThreshold = 250;
|
|
this.clickMoveThreshold = 10 * (window.devicePixelRatio || 1);
|
|
this.currentState = new Guacamole.Mouse.State();
|
|
var touch_count = 0;
|
|
var last_touch_x = 0;
|
|
var last_touch_y = 0;
|
|
var last_touch_time = 0;
|
|
var pixels_moved = 0;
|
|
var touch_buttons = {
|
|
1: "left",
|
|
2: "right",
|
|
3: "middle"
|
|
};
|
|
var gesture_in_progress = false;
|
|
var click_release_timeout = null;
|
|
element.addEventListener("touchend", function(e) {
|
|
e.preventDefault();
|
|
if (gesture_in_progress && e.touches.length === 0) {
|
|
var time = (/* @__PURE__ */ new Date()).getTime();
|
|
var button = touch_buttons[touch_count];
|
|
if (guac_touchpad.currentState[button]) {
|
|
guac_touchpad.release(button, e);
|
|
if (click_release_timeout) {
|
|
window.clearTimeout(click_release_timeout);
|
|
click_release_timeout = null;
|
|
}
|
|
}
|
|
if (time - last_touch_time <= guac_touchpad.clickTimingThreshold && pixels_moved < guac_touchpad.clickMoveThreshold) {
|
|
guac_touchpad.press(button, e);
|
|
click_release_timeout = window.setTimeout(function() {
|
|
guac_touchpad.release(button, e);
|
|
gesture_in_progress = false;
|
|
}, guac_touchpad.clickTimingThreshold);
|
|
}
|
|
if (!click_release_timeout)
|
|
gesture_in_progress = false;
|
|
}
|
|
}, false);
|
|
element.addEventListener("touchstart", function(e) {
|
|
e.preventDefault();
|
|
touch_count = Math.min(e.touches.length, 3);
|
|
if (click_release_timeout) {
|
|
window.clearTimeout(click_release_timeout);
|
|
click_release_timeout = null;
|
|
}
|
|
if (!gesture_in_progress) {
|
|
gesture_in_progress = true;
|
|
var starting_touch = e.touches[0];
|
|
last_touch_x = starting_touch.clientX;
|
|
last_touch_y = starting_touch.clientY;
|
|
last_touch_time = (/* @__PURE__ */ new Date()).getTime();
|
|
pixels_moved = 0;
|
|
}
|
|
}, false);
|
|
element.addEventListener("touchmove", function(e) {
|
|
e.preventDefault();
|
|
var touch = e.touches[0];
|
|
var delta_x = touch.clientX - last_touch_x;
|
|
var delta_y = touch.clientY - last_touch_y;
|
|
pixels_moved += Math.abs(delta_x) + Math.abs(delta_y);
|
|
if (touch_count === 1) {
|
|
var velocity = pixels_moved / ((/* @__PURE__ */ new Date()).getTime() - last_touch_time);
|
|
var scale = 1 + velocity;
|
|
var position = new Guacamole.Position(guac_touchpad.currentState);
|
|
position.x += delta_x * scale;
|
|
position.y += delta_y * scale;
|
|
position.x = Math.min(Math.max(0, position.x), element.offsetWidth - 1);
|
|
position.y = Math.min(Math.max(0, position.y), element.offsetHeight - 1);
|
|
guac_touchpad.move(position, e);
|
|
last_touch_x = touch.clientX;
|
|
last_touch_y = touch.clientY;
|
|
} else if (touch_count === 2) {
|
|
if (Math.abs(delta_y) >= guac_touchpad.scrollThreshold) {
|
|
var button;
|
|
if (delta_y > 0) button = "down";
|
|
else button = "up";
|
|
guac_touchpad.click(button, e);
|
|
last_touch_x = touch.clientX;
|
|
last_touch_y = touch.clientY;
|
|
}
|
|
}
|
|
}, false);
|
|
};
|
|
Guacamole.Mouse.Touchscreen = function Touchscreen(element) {
|
|
Guacamole.Mouse.Event.Target.call(this);
|
|
var guac_touchscreen = this;
|
|
var gesture_in_progress = false;
|
|
var gesture_start_x = null;
|
|
var gesture_start_y = null;
|
|
var click_release_timeout = null;
|
|
var long_press_timeout = null;
|
|
this.scrollThreshold = 20 * (window.devicePixelRatio || 1);
|
|
this.clickTimingThreshold = 250;
|
|
this.clickMoveThreshold = 16 * (window.devicePixelRatio || 1);
|
|
this.longPressThreshold = 500;
|
|
function finger_moved(e) {
|
|
var touch = e.touches[0] || e.changedTouches[0];
|
|
var delta_x = touch.clientX - gesture_start_x;
|
|
var delta_y = touch.clientY - gesture_start_y;
|
|
return Math.sqrt(delta_x * delta_x + delta_y * delta_y) >= guac_touchscreen.clickMoveThreshold;
|
|
}
|
|
function begin_gesture(e) {
|
|
var touch = e.touches[0];
|
|
gesture_in_progress = true;
|
|
gesture_start_x = touch.clientX;
|
|
gesture_start_y = touch.clientY;
|
|
}
|
|
function end_gesture() {
|
|
window.clearTimeout(click_release_timeout);
|
|
window.clearTimeout(long_press_timeout);
|
|
gesture_in_progress = false;
|
|
}
|
|
element.addEventListener("touchend", function(e) {
|
|
if (!gesture_in_progress)
|
|
return;
|
|
if (e.touches.length !== 0 || e.changedTouches.length !== 1) {
|
|
end_gesture();
|
|
return;
|
|
}
|
|
window.clearTimeout(long_press_timeout);
|
|
guac_touchscreen.release(Guacamole.Mouse.State.Buttons.LEFT, e);
|
|
if (!finger_moved(e)) {
|
|
e.preventDefault();
|
|
if (!guac_touchscreen.currentState.left) {
|
|
var touch = e.changedTouches[0];
|
|
guac_touchscreen.move(Guacamole.Position.fromClientPosition(element, touch.clientX, touch.clientY));
|
|
guac_touchscreen.press(Guacamole.Mouse.State.Buttons.LEFT, e);
|
|
click_release_timeout = window.setTimeout(function() {
|
|
guac_touchscreen.release(Guacamole.Mouse.State.Buttons.LEFT, e);
|
|
end_gesture();
|
|
}, guac_touchscreen.clickTimingThreshold);
|
|
}
|
|
}
|
|
}, false);
|
|
element.addEventListener("touchstart", function(e) {
|
|
if (e.touches.length !== 1) {
|
|
end_gesture();
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
begin_gesture(e);
|
|
window.clearTimeout(click_release_timeout);
|
|
long_press_timeout = window.setTimeout(function() {
|
|
var touch = e.touches[0];
|
|
guac_touchscreen.move(Guacamole.Position.fromClientPosition(element, touch.clientX, touch.clientY));
|
|
guac_touchscreen.click(Guacamole.Mouse.State.Buttons.RIGHT, e);
|
|
end_gesture();
|
|
}, guac_touchscreen.longPressThreshold);
|
|
}, false);
|
|
element.addEventListener("touchmove", function(e) {
|
|
if (!gesture_in_progress)
|
|
return;
|
|
if (finger_moved(e))
|
|
window.clearTimeout(long_press_timeout);
|
|
if (e.touches.length !== 1) {
|
|
end_gesture();
|
|
return;
|
|
}
|
|
if (guac_touchscreen.currentState.left) {
|
|
e.preventDefault();
|
|
var touch = e.touches[0];
|
|
guac_touchscreen.move(Guacamole.Position.fromClientPosition(element, touch.clientX, touch.clientY), e);
|
|
}
|
|
}, false);
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Object = function guacamoleObject(client, index) {
|
|
var guacObject = this;
|
|
var bodyCallbacks = {};
|
|
var dequeueBodyCallback = function dequeueBodyCallback2(name) {
|
|
var callbacks = bodyCallbacks[name];
|
|
if (!callbacks)
|
|
return null;
|
|
var callback = callbacks.shift();
|
|
if (callbacks.length === 0)
|
|
delete bodyCallbacks[name];
|
|
return callback;
|
|
};
|
|
var enqueueBodyCallback = function enqueueBodyCallback2(name, callback) {
|
|
var callbacks = bodyCallbacks[name];
|
|
if (!callbacks) {
|
|
callbacks = [];
|
|
bodyCallbacks[name] = callbacks;
|
|
}
|
|
callbacks.push(callback);
|
|
};
|
|
this.index = index;
|
|
this.onbody = function defaultBodyHandler(inputStream, mimetype, name) {
|
|
var callback = dequeueBodyCallback(name);
|
|
if (callback)
|
|
callback(inputStream, mimetype);
|
|
};
|
|
this.onundefine = null;
|
|
this.requestInputStream = function requestInputStream(name, bodyCallback) {
|
|
if (bodyCallback)
|
|
enqueueBodyCallback(name, bodyCallback);
|
|
client.requestObjectInputStream(guacObject.index, name);
|
|
};
|
|
this.createOutputStream = function createOutputStream(mimetype, name) {
|
|
return client.createObjectOutputStream(guacObject.index, mimetype, name);
|
|
};
|
|
};
|
|
Guacamole.Object.ROOT_STREAM = "/";
|
|
Guacamole.Object.STREAM_INDEX_MIMETYPE = "application/vnd.glyptodon.guacamole.stream-index+json";
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.OnScreenKeyboard = function(layout) {
|
|
var osk = this;
|
|
var modifierKeysyms = {};
|
|
var pressed = {};
|
|
var scaledElements = [];
|
|
var addClass = function addClass2(element, classname) {
|
|
if (element.classList)
|
|
element.classList.add(classname);
|
|
else
|
|
element.className += " " + classname;
|
|
};
|
|
var removeClass = function removeClass2(element, classname) {
|
|
if (element.classList)
|
|
element.classList.remove(classname);
|
|
else {
|
|
element.className = element.className.replace(
|
|
/([^ ]+)[ ]*/g,
|
|
function removeMatchingClasses(match, testClassname) {
|
|
if (testClassname === classname)
|
|
return "";
|
|
return match;
|
|
}
|
|
);
|
|
}
|
|
};
|
|
var ignoreMouse = 0;
|
|
var ignorePendingMouseEvents = function ignorePendingMouseEvents2() {
|
|
ignoreMouse = osk.touchMouseThreshold;
|
|
};
|
|
var ScaledElement = function ScaledElement2(element, width, height, scaleFont) {
|
|
this.width = width;
|
|
this.height = height;
|
|
this.scale = function(pixels) {
|
|
element.style.width = width * pixels + "px";
|
|
element.style.height = height * pixels + "px";
|
|
if (scaleFont) {
|
|
element.style.lineHeight = height * pixels + "px";
|
|
element.style.fontSize = pixels + "px";
|
|
}
|
|
};
|
|
};
|
|
var modifiersPressed = function modifiersPressed2(names) {
|
|
for (var i = 0; i < names.length; i++) {
|
|
var name = names[i];
|
|
if (!(name in modifierKeysyms))
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
var getActiveKey = function getActiveKey2(keyName) {
|
|
var keys = osk.keys[keyName];
|
|
if (!keys)
|
|
return null;
|
|
for (var i = keys.length - 1; i >= 0; i--) {
|
|
var candidate = keys[i];
|
|
if (modifiersPressed(candidate.requires))
|
|
return candidate;
|
|
}
|
|
return null;
|
|
};
|
|
var press = function press2(keyName, keyElement) {
|
|
if (!pressed[keyName]) {
|
|
addClass(keyElement, "guac-keyboard-pressed");
|
|
var key = getActiveKey(keyName);
|
|
if (key.modifier) {
|
|
var modifierClass = "guac-keyboard-modifier-" + getCSSName(key.modifier);
|
|
var originalKeysym = modifierKeysyms[key.modifier];
|
|
if (originalKeysym === void 0) {
|
|
addClass(keyboard, modifierClass);
|
|
modifierKeysyms[key.modifier] = key.keysym;
|
|
if (key.keysym && osk.onkeydown)
|
|
osk.onkeydown(key.keysym);
|
|
} else {
|
|
removeClass(keyboard, modifierClass);
|
|
delete modifierKeysyms[key.modifier];
|
|
if (originalKeysym && osk.onkeyup)
|
|
osk.onkeyup(originalKeysym);
|
|
}
|
|
} else if (osk.onkeydown)
|
|
osk.onkeydown(key.keysym);
|
|
pressed[keyName] = true;
|
|
}
|
|
};
|
|
var release = function release2(keyName, keyElement) {
|
|
if (pressed[keyName]) {
|
|
removeClass(keyElement, "guac-keyboard-pressed");
|
|
var key = getActiveKey(keyName);
|
|
if (!key.modifier && osk.onkeyup)
|
|
osk.onkeyup(key.keysym);
|
|
pressed[keyName] = false;
|
|
}
|
|
};
|
|
var keyboard = document.createElement("div");
|
|
keyboard.className = "guac-keyboard";
|
|
keyboard.onselectstart = keyboard.onmousemove = keyboard.onmouseup = keyboard.onmousedown = function handleMouseEvents(e) {
|
|
if (ignoreMouse)
|
|
ignoreMouse--;
|
|
e.stopPropagation();
|
|
return false;
|
|
};
|
|
this.touchMouseThreshold = 3;
|
|
this.onkeydown = null;
|
|
this.onkeyup = null;
|
|
this.layout = new Guacamole.OnScreenKeyboard.Layout(layout);
|
|
this.getElement = function() {
|
|
return keyboard;
|
|
};
|
|
this.resize = function(width) {
|
|
var unit = Math.floor(width * 10 / osk.layout.width) / 10;
|
|
for (var i = 0; i < scaledElements.length; i++) {
|
|
var scaledElement = scaledElements[i];
|
|
scaledElement.scale(unit);
|
|
}
|
|
};
|
|
var asKeyArray = function asKeyArray2(name, object) {
|
|
if (object instanceof Array) {
|
|
var keys = [];
|
|
for (var i = 0; i < object.length; i++) {
|
|
keys.push(new Guacamole.OnScreenKeyboard.Key(object[i], name));
|
|
}
|
|
return keys;
|
|
}
|
|
if (typeof object === "number") {
|
|
return [new Guacamole.OnScreenKeyboard.Key({
|
|
name,
|
|
keysym: object
|
|
})];
|
|
}
|
|
if (typeof object === "string") {
|
|
return [new Guacamole.OnScreenKeyboard.Key({
|
|
name,
|
|
title: object
|
|
})];
|
|
}
|
|
return [new Guacamole.OnScreenKeyboard.Key(object, name)];
|
|
};
|
|
var getKeys = function getKeys2(keys) {
|
|
var keyArrays = {};
|
|
for (var name in layout.keys) {
|
|
keyArrays[name] = asKeyArray(name, keys[name]);
|
|
}
|
|
return keyArrays;
|
|
};
|
|
this.keys = getKeys(layout.keys);
|
|
var getCSSName = function getCSSName2(name) {
|
|
var cssName = name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[^A-Za-z0-9]+/g, "-").toLowerCase();
|
|
return cssName;
|
|
};
|
|
var appendElements = function appendElements2(element, object, name) {
|
|
var i;
|
|
var div = document.createElement("div");
|
|
if (name)
|
|
addClass(div, "guac-keyboard-" + getCSSName(name));
|
|
if (object instanceof Array) {
|
|
addClass(div, "guac-keyboard-group");
|
|
for (i = 0; i < object.length; i++)
|
|
appendElements2(div, object[i]);
|
|
} else if (object instanceof Object) {
|
|
addClass(div, "guac-keyboard-group");
|
|
var names = Object.keys(object).sort();
|
|
for (i = 0; i < names.length; i++) {
|
|
var name = names[i];
|
|
appendElements2(div, object[name], name);
|
|
}
|
|
} else if (typeof object === "number") {
|
|
addClass(div, "guac-keyboard-gap");
|
|
scaledElements.push(new ScaledElement(div, object, object));
|
|
} else if (typeof object === "string") {
|
|
var keyName = object;
|
|
if (keyName.length === 1)
|
|
keyName = "0x" + keyName.charCodeAt(0).toString(16);
|
|
addClass(div, "guac-keyboard-key-container");
|
|
var keyElement = document.createElement("div");
|
|
keyElement.className = "guac-keyboard-key guac-keyboard-key-" + getCSSName(keyName);
|
|
var keys = osk.keys[object];
|
|
if (keys) {
|
|
for (i = 0; i < keys.length; i++) {
|
|
var key = keys[i];
|
|
var capElement = document.createElement("div");
|
|
capElement.className = "guac-keyboard-cap";
|
|
capElement.textContent = key.title;
|
|
for (var j = 0; j < key.requires.length; j++) {
|
|
var requirement = key.requires[j];
|
|
addClass(capElement, "guac-keyboard-requires-" + getCSSName(requirement));
|
|
addClass(keyElement, "guac-keyboard-uses-" + getCSSName(requirement));
|
|
}
|
|
keyElement.appendChild(capElement);
|
|
}
|
|
}
|
|
div.appendChild(keyElement);
|
|
scaledElements.push(new ScaledElement(div, osk.layout.keyWidths[object] || 1, 1, true));
|
|
var touchPress = function touchPress2(e) {
|
|
e.preventDefault();
|
|
ignoreMouse = osk.touchMouseThreshold;
|
|
press(object, keyElement);
|
|
};
|
|
var touchRelease = function touchRelease2(e) {
|
|
e.preventDefault();
|
|
ignoreMouse = osk.touchMouseThreshold;
|
|
release(object, keyElement);
|
|
};
|
|
var mousePress = function mousePress2(e) {
|
|
e.preventDefault();
|
|
if (ignoreMouse === 0)
|
|
press(object, keyElement);
|
|
};
|
|
var mouseRelease = function mouseRelease2(e) {
|
|
e.preventDefault();
|
|
if (ignoreMouse === 0)
|
|
release(object, keyElement);
|
|
};
|
|
keyElement.addEventListener("touchstart", touchPress, true);
|
|
keyElement.addEventListener("touchend", touchRelease, true);
|
|
keyElement.addEventListener("mousedown", mousePress, true);
|
|
keyElement.addEventListener("mouseup", mouseRelease, true);
|
|
keyElement.addEventListener("mouseout", mouseRelease, true);
|
|
}
|
|
element.appendChild(div);
|
|
};
|
|
appendElements(keyboard, layout.layout);
|
|
};
|
|
Guacamole.OnScreenKeyboard.Layout = function(template) {
|
|
this.language = template.language;
|
|
this.type = template.type;
|
|
this.keys = template.keys;
|
|
this.layout = template.layout;
|
|
this.width = template.width;
|
|
this.keyWidths = template.keyWidths || {};
|
|
};
|
|
Guacamole.OnScreenKeyboard.Key = function(template, name) {
|
|
this.name = name || template.name;
|
|
this.title = template.title || this.name;
|
|
this.keysym = template.keysym || (function deriveKeysym(title) {
|
|
if (!title || title.length !== 1)
|
|
return null;
|
|
var charCode = title.charCodeAt(0);
|
|
if (charCode >= 0 && charCode <= 255)
|
|
return charCode;
|
|
if (charCode >= 256 && charCode <= 1114111)
|
|
return 16777216 | charCode;
|
|
return null;
|
|
})(this.title);
|
|
this.modifier = template.modifier;
|
|
this.requires = template.requires || [];
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.OutputStream = function(client, index) {
|
|
var guac_stream = this;
|
|
this.index = index;
|
|
this.onack = null;
|
|
this.sendBlob = function(data) {
|
|
client.sendBlob(guac_stream.index, data);
|
|
};
|
|
this.sendEnd = function() {
|
|
client.endStream(guac_stream.index);
|
|
};
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Parser = function Parser() {
|
|
var parser = this;
|
|
var buffer = "";
|
|
var elementBuffer = [];
|
|
var elementEnd = -1;
|
|
var startIndex = 0;
|
|
var elementCodepoints = 0;
|
|
var BUFFER_TRUNCATION_THRESHOLD = 4096;
|
|
var MIN_CODEPOINT_REQUIRES_SURROGATE = 65536;
|
|
this.receive = function receive(packet, isBuffer) {
|
|
if (isBuffer)
|
|
buffer = packet;
|
|
else {
|
|
if (startIndex > BUFFER_TRUNCATION_THRESHOLD && elementEnd >= startIndex) {
|
|
buffer = buffer.substring(startIndex);
|
|
elementEnd -= startIndex;
|
|
startIndex = 0;
|
|
}
|
|
if (buffer.length)
|
|
buffer += packet;
|
|
else
|
|
buffer = packet;
|
|
}
|
|
while (elementEnd < buffer.length) {
|
|
if (elementEnd >= startIndex) {
|
|
var codepoints = Guacamole.Parser.codePointCount(buffer, startIndex, elementEnd);
|
|
if (codepoints < elementCodepoints) {
|
|
elementEnd += elementCodepoints - codepoints;
|
|
continue;
|
|
} else if (elementCodepoints && buffer.codePointAt(elementEnd - 1) >= MIN_CODEPOINT_REQUIRES_SURROGATE) {
|
|
elementEnd++;
|
|
continue;
|
|
}
|
|
var element = buffer.substring(startIndex, elementEnd);
|
|
var terminator = buffer.substring(elementEnd, elementEnd + 1);
|
|
elementBuffer.push(element);
|
|
if (terminator === ";") {
|
|
var opcode = elementBuffer.shift();
|
|
if (parser.oninstruction !== null)
|
|
parser.oninstruction(opcode, elementBuffer);
|
|
elementBuffer = [];
|
|
if (!isBuffer && elementEnd + 1 === buffer.length) {
|
|
elementEnd = -1;
|
|
buffer = "";
|
|
}
|
|
} else if (terminator !== ",")
|
|
throw new Error('Element terminator of instruction was not ";" nor ",".');
|
|
startIndex = elementEnd + 1;
|
|
}
|
|
var lengthEnd = buffer.indexOf(".", startIndex);
|
|
if (lengthEnd !== -1) {
|
|
elementCodepoints = parseInt(buffer.substring(elementEnd + 1, lengthEnd));
|
|
if (isNaN(elementCodepoints))
|
|
throw new Error("Non-numeric character in element length.");
|
|
startIndex = lengthEnd + 1;
|
|
elementEnd = startIndex + elementCodepoints;
|
|
} else {
|
|
startIndex = buffer.length;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
this.oninstruction = null;
|
|
};
|
|
Guacamole.Parser.codePointCount = function codePointCount(str, start, end) {
|
|
str = str.substring(start || 0, end);
|
|
var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
|
|
return str.length - (surrogatePairs ? surrogatePairs.length : 0);
|
|
};
|
|
Guacamole.Parser.toInstruction = function toInstruction(elements) {
|
|
var toElement = function toElement2(value) {
|
|
var str = "" + value;
|
|
return Guacamole.Parser.codePointCount(str) + "." + str;
|
|
};
|
|
var instr = toElement(elements[0]);
|
|
for (var i = 1; i < elements.length; i++)
|
|
instr += "," + toElement(elements[i]);
|
|
return instr + ";";
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Position = function Position(template) {
|
|
template = template || {};
|
|
this.x = template.x || 0;
|
|
this.y = template.y || 0;
|
|
this.fromClientPosition = function fromClientPosition2(element, clientX, clientY) {
|
|
this.x = clientX - element.offsetLeft;
|
|
this.y = clientY - element.offsetTop;
|
|
var parent = element.offsetParent;
|
|
while (parent && !(parent === document.body)) {
|
|
this.x -= parent.offsetLeft - parent.scrollLeft;
|
|
this.y -= parent.offsetTop - parent.scrollTop;
|
|
parent = parent.offsetParent;
|
|
}
|
|
if (parent) {
|
|
var documentScrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
|
|
var documentScrollTop = document.body.scrollTop || document.documentElement.scrollTop;
|
|
this.x -= parent.offsetLeft - documentScrollLeft;
|
|
this.y -= parent.offsetTop - documentScrollTop;
|
|
}
|
|
};
|
|
};
|
|
Guacamole.Position.fromClientPosition = function fromClientPosition(element, clientX, clientY) {
|
|
var position = new Guacamole.Position();
|
|
position.fromClientPosition(element, clientX, clientY);
|
|
return position;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.RawAudioFormat = function RawAudioFormat(template) {
|
|
this.bytesPerSample = template.bytesPerSample;
|
|
this.channels = template.channels;
|
|
this.rate = template.rate;
|
|
};
|
|
Guacamole.RawAudioFormat.parse = function parseFormat(mimetype) {
|
|
var bytesPerSample;
|
|
var rate = null;
|
|
var channels = 1;
|
|
if (mimetype.substring(0, 9) === "audio/L8;") {
|
|
mimetype = mimetype.substring(9);
|
|
bytesPerSample = 1;
|
|
} else if (mimetype.substring(0, 10) === "audio/L16;") {
|
|
mimetype = mimetype.substring(10);
|
|
bytesPerSample = 2;
|
|
} else
|
|
return null;
|
|
var parameters = mimetype.split(",");
|
|
for (var i = 0; i < parameters.length; i++) {
|
|
var parameter = parameters[i];
|
|
var equals = parameter.indexOf("=");
|
|
if (equals === -1)
|
|
return null;
|
|
var name = parameter.substring(0, equals);
|
|
var value = parameter.substring(equals + 1);
|
|
switch (name) {
|
|
// Number of audio channels
|
|
case "channels":
|
|
channels = parseInt(value);
|
|
break;
|
|
// Sample rate
|
|
case "rate":
|
|
rate = parseInt(value);
|
|
break;
|
|
// All other parameters are unsupported
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
;
|
|
if (rate === null)
|
|
return null;
|
|
return new Guacamole.RawAudioFormat({
|
|
bytesPerSample,
|
|
channels,
|
|
rate
|
|
});
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.SessionRecording = function SessionRecording(source, refreshInterval) {
|
|
if (refreshInterval === void 0)
|
|
refreshInterval = 1e3;
|
|
var recording = this;
|
|
var recordingBlob;
|
|
var tunnel = null;
|
|
var BLOCK_SIZE = 262144;
|
|
var KEYFRAME_CHAR_INTERVAL = 16384;
|
|
var KEYFRAME_TIME_INTERVAL = 5e3;
|
|
var frames = [];
|
|
var lastKeyframe = 0;
|
|
var playbackTunnel = new Guacamole.SessionRecording._PlaybackTunnel();
|
|
var playbackClient = new Guacamole.Client(playbackTunnel);
|
|
var currentFrame = -1;
|
|
var startVideoPosition = null;
|
|
var startRealTimestamp = null;
|
|
var currentPosition = 0;
|
|
var activeSeek = null;
|
|
var frameStart = 0;
|
|
var frameEnd = 0;
|
|
var aborted = false;
|
|
var seekCallback = null;
|
|
var updateTimeout = null;
|
|
var lastUpdateTimestamp = null;
|
|
var parseBlob = function parseBlob2(blob, instructionCallback, completionCallback) {
|
|
if (aborted && blob === recordingBlob)
|
|
return;
|
|
var parser = new Guacamole.Parser();
|
|
parser.oninstruction = instructionCallback;
|
|
var offset = 0;
|
|
var reader = new FileReader();
|
|
var readNextBlock = function readNextBlock2() {
|
|
if (aborted && blob === recordingBlob)
|
|
return;
|
|
if (reader.readyState === 2) {
|
|
try {
|
|
parser.receive(reader.result);
|
|
} catch (parseError) {
|
|
if (recording.onerror) {
|
|
recording.onerror(parseError.message);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (offset >= blob.size) {
|
|
if (completionCallback)
|
|
completionCallback();
|
|
} else {
|
|
var block = blob.slice(offset, offset + BLOCK_SIZE);
|
|
offset += block.size;
|
|
reader.readAsText(block);
|
|
}
|
|
};
|
|
reader.onload = readNextBlock;
|
|
readNextBlock();
|
|
};
|
|
var getUtf8StringByteSize = function(str) {
|
|
var byteSize = str.length;
|
|
for (var i = str.length - 1; i >= 0; i--) {
|
|
var code = str.charCodeAt(i);
|
|
if (code > 127 && code <= 2047)
|
|
byteSize++;
|
|
else if (code > 2047 && code <= 65535)
|
|
byteSize += 2;
|
|
if (code >= 56320 && code <= 57343)
|
|
i--;
|
|
}
|
|
return byteSize;
|
|
};
|
|
var getElementSize = function getElementSize2(value) {
|
|
var byteSize = getUtf8StringByteSize(value) + 3;
|
|
var valueLength = Guacamole.Parser.codePointCount(value);
|
|
while (valueLength >= 10) {
|
|
byteSize++;
|
|
valueLength = Math.floor(valueLength / 10);
|
|
}
|
|
return byteSize;
|
|
};
|
|
playbackClient.connect();
|
|
playbackClient.getDisplay().showCursor(false);
|
|
var keyEventInterpreter = null;
|
|
function initializeKeyInterpreter(startTimestamp) {
|
|
keyEventInterpreter = new Guacamole.KeyEventInterpreter(startTimestamp);
|
|
}
|
|
var loadInstruction = function loadInstruction2(opcode, args) {
|
|
frameEnd += getElementSize(opcode);
|
|
for (var i = 0; i < args.length; i++)
|
|
frameEnd += getElementSize(args[i]);
|
|
if (opcode === "sync") {
|
|
var timestamp = parseInt(args[0]);
|
|
var frame = new Guacamole.SessionRecording._Frame(timestamp, frameStart, frameEnd);
|
|
frames.push(frame);
|
|
frameStart = frameEnd;
|
|
if (frames.length === 1)
|
|
initializeKeyInterpreter(timestamp);
|
|
if (frames.length === 1 || frameEnd - frames[lastKeyframe].start >= KEYFRAME_CHAR_INTERVAL && timestamp - frames[lastKeyframe].timestamp >= KEYFRAME_TIME_INTERVAL) {
|
|
frame.keyframe = true;
|
|
lastKeyframe = frames.length - 1;
|
|
}
|
|
if (recording.onprogress)
|
|
recording.onprogress(recording.getDuration(), frameEnd);
|
|
} else if (opcode === "key")
|
|
keyEventInterpreter.handleKeyEvent(args);
|
|
};
|
|
var notifyLoaded = function notifyLoaded2() {
|
|
if (recording.onload)
|
|
recording.onload();
|
|
};
|
|
if (source instanceof Blob) {
|
|
recordingBlob = source;
|
|
parseBlob(recordingBlob, loadInstruction, notifyLoaded);
|
|
} else {
|
|
tunnel = source;
|
|
recordingBlob = new Blob();
|
|
var errorEncountered = false;
|
|
var instructionBuffer = "";
|
|
tunnel.oninstruction = function handleInstruction(opcode, args) {
|
|
instructionBuffer += opcode.length + "." + opcode;
|
|
args.forEach(function appendArg(arg) {
|
|
instructionBuffer += "," + arg.length + "." + arg;
|
|
});
|
|
instructionBuffer += ";";
|
|
if (instructionBuffer.length >= BLOCK_SIZE) {
|
|
recordingBlob = new Blob([recordingBlob, instructionBuffer]);
|
|
instructionBuffer = "";
|
|
}
|
|
loadInstruction(opcode, args);
|
|
};
|
|
tunnel.onerror = function tunnelError(status) {
|
|
errorEncountered = true;
|
|
if (recording.onerror)
|
|
recording.onerror(status.message);
|
|
};
|
|
tunnel.onstatechange = function tunnelStateChanged(state) {
|
|
if (state === Guacamole.Tunnel.State.CLOSED) {
|
|
if (instructionBuffer.length) {
|
|
recordingBlob = new Blob([recordingBlob, instructionBuffer]);
|
|
instructionBuffer = "";
|
|
}
|
|
if (recording.onkeyevents)
|
|
recording.onkeyevents(keyEventInterpreter.getEvents());
|
|
if (!errorEncountered)
|
|
notifyLoaded();
|
|
}
|
|
};
|
|
}
|
|
var toRelativeTimestamp = function toRelativeTimestamp2(timestamp) {
|
|
if (frames.length === 0)
|
|
return 0;
|
|
return timestamp - frames[0].timestamp;
|
|
};
|
|
var findFrame = function findFrame2(minIndex, maxIndex, timestamp) {
|
|
if (minIndex === maxIndex) {
|
|
if (minIndex === 0)
|
|
return minIndex;
|
|
if (toRelativeTimestamp(frames[minIndex].timestamp) > timestamp)
|
|
return minIndex - 1;
|
|
}
|
|
var midIndex = Math.floor((minIndex + maxIndex) / 2);
|
|
var midTimestamp = toRelativeTimestamp(frames[midIndex].timestamp);
|
|
if (timestamp < midTimestamp && midIndex > minIndex)
|
|
return findFrame2(minIndex, midIndex - 1, timestamp);
|
|
if (timestamp > midTimestamp && midIndex < maxIndex)
|
|
return findFrame2(midIndex + 1, maxIndex, timestamp);
|
|
return midIndex;
|
|
};
|
|
var replayFrame = function replayFrame2(index, callback) {
|
|
var frame = frames[index];
|
|
parseBlob(recordingBlob.slice(frame.start, frame.end), function handleInstruction(opcode, args) {
|
|
playbackTunnel.receiveInstruction(opcode, args);
|
|
}, function replayCompleted() {
|
|
if (frame.keyframe && !frame.clientState) {
|
|
playbackClient.exportState(function storeClientState(state) {
|
|
frame.clientState = new Blob([JSON.stringify(state)]);
|
|
});
|
|
}
|
|
currentFrame = index;
|
|
if (callback)
|
|
callback();
|
|
});
|
|
};
|
|
var seekToFrame = function seekToFrame2(index, callback, nextRealTimestamp) {
|
|
abortSeek();
|
|
var thisSeek = activeSeek = {
|
|
aborted: false
|
|
};
|
|
var startIndex = index;
|
|
var continueReplay = function continueReplay2() {
|
|
if (recording.onseek && currentFrame > startIndex) {
|
|
currentPosition = toRelativeTimestamp(frames[currentFrame].timestamp);
|
|
recording.onseek(
|
|
currentPosition,
|
|
currentFrame - startIndex,
|
|
index - startIndex
|
|
);
|
|
}
|
|
if (thisSeek.aborted)
|
|
return;
|
|
if (currentFrame < index)
|
|
replayFrame(currentFrame + 1, continueReplay2);
|
|
else
|
|
callback();
|
|
};
|
|
var continueAfterRequiredDelay = function continueAfterRequiredDelay2() {
|
|
var delay = nextRealTimestamp ? Math.max(nextRealTimestamp - (/* @__PURE__ */ new Date()).getTime(), 0) : 0;
|
|
if (delay) {
|
|
updateTimeout && clearTimeout(updateTimeout);
|
|
updateTimeout = window.setTimeout(function timeoutComplete() {
|
|
updateTimeout = null;
|
|
continueReplay();
|
|
}, delay);
|
|
} else
|
|
continueReplay();
|
|
};
|
|
for (; startIndex >= 0; startIndex--) {
|
|
var frame = frames[startIndex];
|
|
if (startIndex === currentFrame)
|
|
break;
|
|
if (frame.clientState) {
|
|
frame.clientState.text().then(function textReady(text) {
|
|
playbackClient.importState(JSON.parse(text));
|
|
currentFrame = startIndex;
|
|
continueAfterRequiredDelay();
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
continueAfterRequiredDelay();
|
|
};
|
|
var abortSeek = function abortSeek2() {
|
|
if (activeSeek) {
|
|
activeSeek.aborted = true;
|
|
activeSeek = null;
|
|
}
|
|
};
|
|
var continuePlayback = function continuePlayback2() {
|
|
if (!recording.isPlaying())
|
|
return;
|
|
if (currentFrame + 1 < frames.length) {
|
|
var next = frames[currentFrame + 1];
|
|
var realLifePlayTime = Date.now() - startRealTimestamp;
|
|
var timestampOffset = toRelativeTimestamp(next.timestamp) - startVideoPosition;
|
|
var nextFrameDelay = timestampOffset - realLifePlayTime;
|
|
var nextRefreshDelay = refreshInterval >= 0 ? refreshInterval * Math.floor(
|
|
(currentPosition + refreshInterval) / refreshInterval
|
|
) - currentPosition : nextFrameDelay;
|
|
if (nextFrameDelay <= nextRefreshDelay)
|
|
seekToFrame(currentFrame + 1, function frameDelayElapsed() {
|
|
lastUpdateTimestamp = Date.now();
|
|
continuePlayback2();
|
|
}, Date.now() + nextFrameDelay);
|
|
else {
|
|
updateTimeout && window.clearTimeout(updateTimeout);
|
|
updateTimeout = window.setTimeout(function incrementPosition() {
|
|
updateTimeout = null;
|
|
currentPosition += nextRefreshDelay;
|
|
if (recording.onseek)
|
|
recording.onseek(currentPosition);
|
|
lastUpdateTimestamp = Date.now();
|
|
continuePlayback2();
|
|
}, nextRefreshDelay);
|
|
}
|
|
} else
|
|
recording.pause();
|
|
};
|
|
this.onload = null;
|
|
this.onerror = null;
|
|
this.onabort = null;
|
|
this.onprogress = null;
|
|
this.onplay = null;
|
|
this.onpause = null;
|
|
this.onkeyevents = null;
|
|
this.onseek = null;
|
|
this.connect = function connect(data) {
|
|
if (tunnel)
|
|
tunnel.connect(data);
|
|
};
|
|
this.disconnect = function disconnect() {
|
|
if (tunnel)
|
|
tunnel.disconnect();
|
|
};
|
|
this.abort = function abort() {
|
|
if (!aborted) {
|
|
aborted = true;
|
|
if (recording.onabort)
|
|
recording.onabort();
|
|
if (tunnel)
|
|
tunnel.disconnect();
|
|
}
|
|
};
|
|
this.getDisplay = function getDisplay() {
|
|
return playbackClient.getDisplay();
|
|
};
|
|
this.isPlaying = function isPlaying() {
|
|
return !!startRealTimestamp;
|
|
};
|
|
this.getPosition = function getPosition() {
|
|
return currentPosition;
|
|
};
|
|
this.getDuration = function getDuration() {
|
|
if (frames.length === 0)
|
|
return 0;
|
|
return toRelativeTimestamp(frames[frames.length - 1].timestamp);
|
|
};
|
|
this.play = function play() {
|
|
if (!recording.isPlaying() && currentFrame + 1 < frames.length) {
|
|
if (recording.onplay)
|
|
recording.onplay();
|
|
startVideoPosition = currentPosition;
|
|
startRealTimestamp = Date.now();
|
|
lastUpdateTimestamp = Date.now();
|
|
continuePlayback();
|
|
}
|
|
};
|
|
this.seek = function seek(position, callback) {
|
|
if (frames.length === 0)
|
|
return;
|
|
recording.cancel();
|
|
var originallyPlaying = recording.isPlaying();
|
|
recording.pause();
|
|
seekCallback = function restorePlaybackState() {
|
|
seekCallback = null;
|
|
if (originallyPlaying) {
|
|
recording.play();
|
|
originallyPlaying = null;
|
|
}
|
|
if (callback)
|
|
callback();
|
|
};
|
|
var closestFrame = findFrame(0, frames.length - 1, position);
|
|
seekToFrame(closestFrame, function seekComplete() {
|
|
currentPosition = position;
|
|
if (recording.onseek)
|
|
recording.onseek(position);
|
|
seekCallback();
|
|
});
|
|
};
|
|
this.cancel = function cancel() {
|
|
if (seekCallback) {
|
|
abortSeek();
|
|
seekCallback();
|
|
}
|
|
};
|
|
this.pause = function pause() {
|
|
abortSeek();
|
|
updateTimeout && clearTimeout(updateTimeout);
|
|
currentPosition += Date.now() - lastUpdateTimestamp;
|
|
if (recording.isPlaying()) {
|
|
if (recording.onpause)
|
|
recording.onpause();
|
|
lastUpdateTimestamp = null;
|
|
startVideoPosition = null;
|
|
startRealTimestamp = null;
|
|
}
|
|
};
|
|
};
|
|
Guacamole.SessionRecording._Frame = function _Frame(timestamp, start, end) {
|
|
this.keyframe = false;
|
|
this.timestamp = timestamp;
|
|
this.start = start;
|
|
this.end = end;
|
|
this.clientState = null;
|
|
};
|
|
Guacamole.SessionRecording._PlaybackTunnel = function _PlaybackTunnel() {
|
|
var tunnel = this;
|
|
this.connect = function connect(data) {
|
|
};
|
|
this.sendMessage = function sendMessage(elements) {
|
|
};
|
|
this.disconnect = function disconnect() {
|
|
};
|
|
this.receiveInstruction = function receiveInstruction(opcode, args) {
|
|
if (tunnel.oninstruction)
|
|
tunnel.oninstruction(opcode, args);
|
|
};
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Status = function(code, message) {
|
|
var guac_status = this;
|
|
this.code = code;
|
|
this.message = message;
|
|
this.isError = function() {
|
|
return guac_status.code < 0 || guac_status.code > 255;
|
|
};
|
|
};
|
|
Guacamole.Status.Code = {
|
|
/**
|
|
* The operation succeeded.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"SUCCESS": 0,
|
|
/**
|
|
* The requested operation is unsupported.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"UNSUPPORTED": 256,
|
|
/**
|
|
* The operation could not be performed due to an internal failure.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"SERVER_ERROR": 512,
|
|
/**
|
|
* The operation could not be performed as the server is busy.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"SERVER_BUSY": 513,
|
|
/**
|
|
* The operation could not be performed because the upstream server is not
|
|
* responding.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"UPSTREAM_TIMEOUT": 514,
|
|
/**
|
|
* The operation was unsuccessful due to an error or otherwise unexpected
|
|
* condition of the upstream server.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"UPSTREAM_ERROR": 515,
|
|
/**
|
|
* The operation could not be performed as the requested resource does not
|
|
* exist.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"RESOURCE_NOT_FOUND": 516,
|
|
/**
|
|
* The operation could not be performed as the requested resource is
|
|
* already in use.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"RESOURCE_CONFLICT": 517,
|
|
/**
|
|
* The operation could not be performed as the requested resource is now
|
|
* closed.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"RESOURCE_CLOSED": 518,
|
|
/**
|
|
* The operation could not be performed because the upstream server does
|
|
* not appear to exist.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"UPSTREAM_NOT_FOUND": 519,
|
|
/**
|
|
* The operation could not be performed because the upstream server is not
|
|
* available to service the request.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"UPSTREAM_UNAVAILABLE": 520,
|
|
/**
|
|
* The session within the upstream server has ended because it conflicted
|
|
* with another session.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"SESSION_CONFLICT": 521,
|
|
/**
|
|
* The session within the upstream server has ended because it appeared to
|
|
* be inactive.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"SESSION_TIMEOUT": 522,
|
|
/**
|
|
* The session within the upstream server has been forcibly terminated.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"SESSION_CLOSED": 523,
|
|
/**
|
|
* The operation could not be performed because bad parameters were given.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CLIENT_BAD_REQUEST": 768,
|
|
/**
|
|
* Permission was denied to perform the operation, as the user is not yet
|
|
* authorized (not yet logged in, for example).
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CLIENT_UNAUTHORIZED": 769,
|
|
/**
|
|
* Permission was denied to perform the operation, and this permission will
|
|
* not be granted even if the user is authorized.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CLIENT_FORBIDDEN": 771,
|
|
/**
|
|
* The client took too long to respond.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CLIENT_TIMEOUT": 776,
|
|
/**
|
|
* The client sent too much data.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CLIENT_OVERRUN": 781,
|
|
/**
|
|
* The client sent data of an unsupported or unexpected type.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CLIENT_BAD_TYPE": 783,
|
|
/**
|
|
* The operation failed because the current client is already using too
|
|
* many resources.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CLIENT_TOO_MANY": 797
|
|
};
|
|
Guacamole.Status.Code.fromHTTPCode = function fromHTTPCode(status) {
|
|
switch (status) {
|
|
// HTTP 400 - Bad request
|
|
case 400:
|
|
return Guacamole.Status.Code.CLIENT_BAD_REQUEST;
|
|
// HTTP 403 - Forbidden
|
|
case 403:
|
|
return Guacamole.Status.Code.CLIENT_FORBIDDEN;
|
|
// HTTP 404 - Resource not found
|
|
case 404:
|
|
return Guacamole.Status.Code.RESOURCE_NOT_FOUND;
|
|
// HTTP 429 - Too many requests
|
|
case 429:
|
|
return Guacamole.Status.Code.CLIENT_TOO_MANY;
|
|
// HTTP 503 - Server unavailable
|
|
case 503:
|
|
return Guacamole.Status.Code.SERVER_BUSY;
|
|
}
|
|
return Guacamole.Status.Code.SERVER_ERROR;
|
|
};
|
|
Guacamole.Status.Code.fromWebSocketCode = function fromWebSocketCode(code) {
|
|
switch (code) {
|
|
// Successful disconnect (no error)
|
|
case 1e3:
|
|
return Guacamole.Status.Code.SUCCESS;
|
|
// Codes which indicate the server is not reachable
|
|
case 1006:
|
|
// Abnormal Closure (also signalled by JavaScript when the connection cannot be opened in the first place)
|
|
case 1015:
|
|
return Guacamole.Status.Code.UPSTREAM_NOT_FOUND;
|
|
// Codes which indicate the server is reachable but busy/unavailable
|
|
case 1001:
|
|
// Going Away
|
|
case 1012:
|
|
// Service Restart
|
|
case 1013:
|
|
// Try Again Later
|
|
case 1014:
|
|
return Guacamole.Status.Code.UPSTREAM_UNAVAILABLE;
|
|
}
|
|
return Guacamole.Status.Code.SERVER_ERROR;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.StringReader = function(stream) {
|
|
var guac_reader = this;
|
|
var utf8Parser = new Guacamole.UTF8Parser();
|
|
var array_reader = new Guacamole.ArrayBufferReader(stream);
|
|
array_reader.ondata = function(buffer) {
|
|
var text = utf8Parser.decode(buffer);
|
|
if (guac_reader.ontext)
|
|
guac_reader.ontext(text);
|
|
};
|
|
array_reader.onend = function() {
|
|
if (guac_reader.onend)
|
|
guac_reader.onend();
|
|
};
|
|
this.ontext = null;
|
|
this.onend = null;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.StringWriter = function(stream) {
|
|
var guac_writer = this;
|
|
var array_writer = new Guacamole.ArrayBufferWriter(stream);
|
|
var buffer = new Uint8Array(8192);
|
|
var length = 0;
|
|
array_writer.onack = function(status) {
|
|
if (guac_writer.onack)
|
|
guac_writer.onack(status);
|
|
};
|
|
function __expand(bytes) {
|
|
if (length + bytes >= buffer.length) {
|
|
var new_buffer = new Uint8Array((length + bytes) * 2);
|
|
new_buffer.set(buffer);
|
|
buffer = new_buffer;
|
|
}
|
|
length += bytes;
|
|
}
|
|
function __append_utf8(codepoint) {
|
|
var mask;
|
|
var bytes;
|
|
if (codepoint <= 127) {
|
|
mask = 0;
|
|
bytes = 1;
|
|
} else if (codepoint <= 2047) {
|
|
mask = 192;
|
|
bytes = 2;
|
|
} else if (codepoint <= 65535) {
|
|
mask = 224;
|
|
bytes = 3;
|
|
} else if (codepoint <= 2097151) {
|
|
mask = 240;
|
|
bytes = 4;
|
|
} else {
|
|
__append_utf8(65533);
|
|
return;
|
|
}
|
|
__expand(bytes);
|
|
var offset = length - 1;
|
|
for (var i = 1; i < bytes; i++) {
|
|
buffer[offset--] = 128 | codepoint & 63;
|
|
codepoint >>= 6;
|
|
}
|
|
buffer[offset] = mask | codepoint;
|
|
}
|
|
function __encode_utf8(text) {
|
|
for (var i = 0; i < text.length; i++) {
|
|
var codepoint = text.charCodeAt(i);
|
|
__append_utf8(codepoint);
|
|
}
|
|
if (length > 0) {
|
|
var out_buffer = buffer.subarray(0, length);
|
|
length = 0;
|
|
return out_buffer;
|
|
}
|
|
}
|
|
this.sendText = function(text) {
|
|
if (text.length)
|
|
array_writer.sendData(__encode_utf8(text));
|
|
};
|
|
this.sendEnd = function() {
|
|
array_writer.sendEnd();
|
|
};
|
|
this.onack = null;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Touch = function Touch(element) {
|
|
Guacamole.Event.Target.call(this);
|
|
var guacTouch = this;
|
|
var identifierPool = new Guacamole.IntegerPool();
|
|
var identifierMapping = {};
|
|
var DEFAULT_CONTACT_RADIUS = Math.floor(16 * window.devicePixelRatio);
|
|
this.touches = {};
|
|
this.activeTouches = 0;
|
|
var mapIdentifier = function mapIdentifier2(identifier) {
|
|
if (identifier in identifierMapping)
|
|
return identifierMapping[identifier];
|
|
return identifierMapping[identifier] = identifierPool.next();
|
|
};
|
|
var unmapIdentifier = function unmapIdentifier2(identifier) {
|
|
var id = mapIdentifier(identifier);
|
|
delete identifierMapping[identifier];
|
|
identifierPool.free(id);
|
|
return id;
|
|
};
|
|
element.addEventListener("touchstart", function touchstart(e) {
|
|
for (var i = 0; i < e.changedTouches.length; i++) {
|
|
var changedTouch = e.changedTouches[i];
|
|
var identifier = mapIdentifier(changedTouch.identifier);
|
|
if (guacTouch.touches[identifier])
|
|
continue;
|
|
var touch = guacTouch.touches[identifier] = new Guacamole.Touch.State({
|
|
id: identifier,
|
|
radiusX: changedTouch.radiusX || DEFAULT_CONTACT_RADIUS,
|
|
radiusY: changedTouch.radiusY || DEFAULT_CONTACT_RADIUS,
|
|
angle: changedTouch.angle || 0,
|
|
force: changedTouch.force || 1
|
|
/* Within JavaScript changedTouch events, a force of 0.0 indicates the device does not support reporting changedTouch force */
|
|
});
|
|
guacTouch.activeTouches++;
|
|
touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY);
|
|
guacTouch.dispatch(new Guacamole.Touch.Event("touchmove", e, touch));
|
|
}
|
|
}, false);
|
|
element.addEventListener("touchmove", function touchstart(e) {
|
|
for (var i = 0; i < e.changedTouches.length; i++) {
|
|
var changedTouch = e.changedTouches[i];
|
|
var identifier = mapIdentifier(changedTouch.identifier);
|
|
var touch = guacTouch.touches[identifier];
|
|
if (!touch)
|
|
continue;
|
|
if (changedTouch.force)
|
|
touch.force = changedTouch.force;
|
|
touch.angle = changedTouch.angle || 0;
|
|
touch.radiusX = changedTouch.radiusX || DEFAULT_CONTACT_RADIUS;
|
|
touch.radiusY = changedTouch.radiusY || DEFAULT_CONTACT_RADIUS;
|
|
touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY);
|
|
guacTouch.dispatch(new Guacamole.Touch.Event("touchmove", e, touch));
|
|
}
|
|
}, false);
|
|
element.addEventListener("touchend", function touchstart(e) {
|
|
for (var i = 0; i < e.changedTouches.length; i++) {
|
|
var changedTouch = e.changedTouches[i];
|
|
var identifier = unmapIdentifier(changedTouch.identifier);
|
|
var touch = guacTouch.touches[identifier];
|
|
if (!touch)
|
|
continue;
|
|
delete guacTouch.touches[identifier];
|
|
guacTouch.activeTouches--;
|
|
touch.force = 0;
|
|
touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY);
|
|
guacTouch.dispatch(new Guacamole.Touch.Event("touchend", e, touch));
|
|
}
|
|
}, false);
|
|
};
|
|
Guacamole.Touch.State = function State2(template) {
|
|
template = template || {};
|
|
Guacamole.Position.call(this, template);
|
|
this.id = template.id || 0;
|
|
this.radiusX = template.radiusX || 0;
|
|
this.radiusY = template.radiusY || 0;
|
|
this.angle = template.angle || 0;
|
|
this.force = template.force || 1;
|
|
};
|
|
Guacamole.Touch.Event = function TouchEvent(type, event, state) {
|
|
Guacamole.Event.DOMEvent.call(this, type, [event]);
|
|
this.state = state;
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.Tunnel = function() {
|
|
this.connect = function(data) {
|
|
};
|
|
this.disconnect = function() {
|
|
};
|
|
this.sendMessage = function(elements) {
|
|
};
|
|
this.setState = function(state) {
|
|
if (state !== this.state) {
|
|
this.state = state;
|
|
if (this.onstatechange)
|
|
this.onstatechange(state);
|
|
}
|
|
};
|
|
this.setUUID = function setUUID(uuid) {
|
|
this.uuid = uuid;
|
|
if (this.onuuid)
|
|
this.onuuid(uuid);
|
|
};
|
|
this.isConnected = function isConnected() {
|
|
return this.state === Guacamole.Tunnel.State.OPEN || this.state === Guacamole.Tunnel.State.UNSTABLE;
|
|
};
|
|
this.state = Guacamole.Tunnel.State.CLOSED;
|
|
this.receiveTimeout = 15e3;
|
|
this.unstableThreshold = 1500;
|
|
this.uuid = null;
|
|
this.onuuid = null;
|
|
this.onerror = null;
|
|
this.onstatechange = null;
|
|
this.oninstruction = null;
|
|
};
|
|
Guacamole.Tunnel.INTERNAL_DATA_OPCODE = "";
|
|
Guacamole.Tunnel.State = {
|
|
/**
|
|
* A connection is in pending. It is not yet known whether connection was
|
|
* successful.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CONNECTING": 0,
|
|
/**
|
|
* Connection was successful, and data is being received.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"OPEN": 1,
|
|
/**
|
|
* The connection is closed. Connection may not have been successful, the
|
|
* tunnel may have been explicitly closed by either side, or an error may
|
|
* have occurred.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"CLOSED": 2,
|
|
/**
|
|
* The connection is open, but communication through the tunnel appears to
|
|
* be disrupted, and the connection may close as a result.
|
|
*
|
|
* @type {!number}
|
|
*/
|
|
"UNSTABLE": 3
|
|
};
|
|
Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
|
|
var tunnel = this;
|
|
var TUNNEL_CONNECT = tunnelURL + "?connect";
|
|
var TUNNEL_READ = tunnelURL + "?read:";
|
|
var TUNNEL_WRITE = tunnelURL + "?write:";
|
|
var POLLING_ENABLED = 1;
|
|
var POLLING_DISABLED = 0;
|
|
var pollingMode = POLLING_ENABLED;
|
|
var sendingMessages = false;
|
|
var outputMessageBuffer = "";
|
|
var withCredentials = !!crossDomain;
|
|
var receive_timeout = null;
|
|
var unstableTimeout = null;
|
|
var pingInterval = null;
|
|
var PING_FREQUENCY = 500;
|
|
var extraHeaders = extraTunnelHeaders || {};
|
|
var TUNNEL_TOKEN_HEADER = "Guacamole-Tunnel-Token";
|
|
var tunnelSessionToken = null;
|
|
function addExtraHeaders(request, headers) {
|
|
for (var name in headers) {
|
|
request.setRequestHeader(name, headers[name]);
|
|
}
|
|
}
|
|
var resetTimers = function resetTimers2() {
|
|
window.clearTimeout(receive_timeout);
|
|
window.clearTimeout(unstableTimeout);
|
|
if (tunnel.state === Guacamole.Tunnel.State.UNSTABLE)
|
|
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
|
receive_timeout = window.setTimeout(function() {
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_TIMEOUT, "Server timeout."));
|
|
}, tunnel.receiveTimeout);
|
|
unstableTimeout = window.setTimeout(function() {
|
|
tunnel.setState(Guacamole.Tunnel.State.UNSTABLE);
|
|
}, tunnel.unstableThreshold);
|
|
};
|
|
function close_tunnel(status) {
|
|
window.clearTimeout(receive_timeout);
|
|
window.clearTimeout(unstableTimeout);
|
|
window.clearInterval(pingInterval);
|
|
if (tunnel.state === Guacamole.Tunnel.State.CLOSED)
|
|
return;
|
|
if (status.code !== Guacamole.Status.Code.SUCCESS && tunnel.onerror) {
|
|
if (tunnel.state === Guacamole.Tunnel.State.CONNECTING || status.code !== Guacamole.Status.Code.RESOURCE_NOT_FOUND)
|
|
tunnel.onerror(status);
|
|
}
|
|
sendingMessages = false;
|
|
tunnel.setState(Guacamole.Tunnel.State.CLOSED);
|
|
}
|
|
this.sendMessage = function() {
|
|
if (!tunnel.isConnected())
|
|
return;
|
|
if (!arguments.length)
|
|
return;
|
|
outputMessageBuffer += Guacamole.Parser.toInstruction(arguments);
|
|
if (!sendingMessages)
|
|
sendPendingMessages();
|
|
};
|
|
function sendPendingMessages() {
|
|
if (!tunnel.isConnected())
|
|
return;
|
|
if (outputMessageBuffer.length > 0) {
|
|
sendingMessages = true;
|
|
var message_xmlhttprequest = new XMLHttpRequest();
|
|
message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel.uuid);
|
|
message_xmlhttprequest.withCredentials = withCredentials;
|
|
addExtraHeaders(message_xmlhttprequest, extraHeaders);
|
|
message_xmlhttprequest.setRequestHeader("Content-type", "application/octet-stream");
|
|
message_xmlhttprequest.setRequestHeader(TUNNEL_TOKEN_HEADER, tunnelSessionToken);
|
|
message_xmlhttprequest.onreadystatechange = function() {
|
|
if (message_xmlhttprequest.readyState === 4) {
|
|
resetTimers();
|
|
if (message_xmlhttprequest.status !== 200)
|
|
handleHTTPTunnelError(message_xmlhttprequest);
|
|
else
|
|
sendPendingMessages();
|
|
}
|
|
};
|
|
message_xmlhttprequest.send(outputMessageBuffer);
|
|
outputMessageBuffer = "";
|
|
} else
|
|
sendingMessages = false;
|
|
}
|
|
function handleHTTPTunnelError(xmlhttprequest) {
|
|
var code = parseInt(xmlhttprequest.getResponseHeader("Guacamole-Status-Code"));
|
|
if (code) {
|
|
var message = xmlhttprequest.getResponseHeader("Guacamole-Error-Message");
|
|
close_tunnel(new Guacamole.Status(code, message));
|
|
} else if (xmlhttprequest.status)
|
|
close_tunnel(new Guacamole.Status(
|
|
Guacamole.Status.Code.fromHTTPCode(xmlhttprequest.status),
|
|
xmlhttprequest.statusText
|
|
));
|
|
else
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_NOT_FOUND));
|
|
}
|
|
function handleResponse(xmlhttprequest) {
|
|
var interval = null;
|
|
var nextRequest = null;
|
|
var dataUpdateEvents = 0;
|
|
var parser = new Guacamole.Parser();
|
|
parser.oninstruction = function instructionReceived(opcode, args) {
|
|
if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE && args.length === 0) {
|
|
parser = new Guacamole.Parser();
|
|
parser.oninstruction = instructionReceived;
|
|
if (interval)
|
|
clearInterval(interval);
|
|
xmlhttprequest.onreadystatechange = null;
|
|
xmlhttprequest.abort();
|
|
if (nextRequest)
|
|
handleResponse(nextRequest);
|
|
} else if (opcode !== Guacamole.Tunnel.INTERNAL_DATA_OPCODE && tunnel.oninstruction)
|
|
tunnel.oninstruction(opcode, args);
|
|
};
|
|
function parseResponse() {
|
|
if (!tunnel.isConnected()) {
|
|
if (interval !== null)
|
|
clearInterval(interval);
|
|
return;
|
|
}
|
|
if (xmlhttprequest.readyState < 2) return;
|
|
var status;
|
|
try {
|
|
status = xmlhttprequest.status;
|
|
} catch (e) {
|
|
status = 200;
|
|
}
|
|
if (!nextRequest && status === 200)
|
|
nextRequest = makeRequest();
|
|
if (xmlhttprequest.readyState === 3 || xmlhttprequest.readyState === 4) {
|
|
resetTimers();
|
|
if (pollingMode === POLLING_ENABLED) {
|
|
if (xmlhttprequest.readyState === 3 && !interval)
|
|
interval = setInterval(parseResponse, 30);
|
|
else if (xmlhttprequest.readyState === 4 && interval)
|
|
clearInterval(interval);
|
|
}
|
|
if (xmlhttprequest.status === 0) {
|
|
tunnel.disconnect();
|
|
return;
|
|
} else if (xmlhttprequest.status !== 200) {
|
|
handleHTTPTunnelError(xmlhttprequest);
|
|
return;
|
|
}
|
|
var current;
|
|
try {
|
|
current = xmlhttprequest.responseText;
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
try {
|
|
parser.receive(current, true);
|
|
} catch (e) {
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SERVER_ERROR, e.message));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (pollingMode === POLLING_ENABLED) {
|
|
xmlhttprequest.onreadystatechange = function() {
|
|
if (xmlhttprequest.readyState === 3) {
|
|
dataUpdateEvents++;
|
|
if (dataUpdateEvents >= 2) {
|
|
pollingMode = POLLING_DISABLED;
|
|
xmlhttprequest.onreadystatechange = parseResponse;
|
|
}
|
|
}
|
|
parseResponse();
|
|
};
|
|
} else
|
|
xmlhttprequest.onreadystatechange = parseResponse;
|
|
parseResponse();
|
|
}
|
|
var request_id = 0;
|
|
function makeRequest() {
|
|
var xmlhttprequest = new XMLHttpRequest();
|
|
xmlhttprequest.open("GET", TUNNEL_READ + tunnel.uuid + ":" + request_id++);
|
|
xmlhttprequest.setRequestHeader(TUNNEL_TOKEN_HEADER, tunnelSessionToken);
|
|
xmlhttprequest.withCredentials = withCredentials;
|
|
addExtraHeaders(xmlhttprequest, extraHeaders);
|
|
xmlhttprequest.send(null);
|
|
return xmlhttprequest;
|
|
}
|
|
this.connect = function(data) {
|
|
resetTimers();
|
|
tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
|
|
var connect_xmlhttprequest = new XMLHttpRequest();
|
|
connect_xmlhttprequest.onreadystatechange = function() {
|
|
if (connect_xmlhttprequest.readyState !== 4)
|
|
return;
|
|
if (connect_xmlhttprequest.status !== 200) {
|
|
handleHTTPTunnelError(connect_xmlhttprequest);
|
|
return;
|
|
}
|
|
resetTimers();
|
|
tunnel.setUUID(connect_xmlhttprequest.responseText);
|
|
tunnelSessionToken = connect_xmlhttprequest.getResponseHeader(TUNNEL_TOKEN_HEADER);
|
|
if (!tunnelSessionToken) {
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_NOT_FOUND));
|
|
return;
|
|
}
|
|
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
|
pingInterval = setInterval(function sendPing() {
|
|
tunnel.sendMessage("nop");
|
|
}, PING_FREQUENCY);
|
|
handleResponse(makeRequest());
|
|
};
|
|
connect_xmlhttprequest.open("POST", TUNNEL_CONNECT, true);
|
|
connect_xmlhttprequest.withCredentials = withCredentials;
|
|
addExtraHeaders(connect_xmlhttprequest, extraHeaders);
|
|
connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
|
|
connect_xmlhttprequest.send(data);
|
|
};
|
|
this.disconnect = function() {
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SUCCESS, "Manually closed."));
|
|
};
|
|
};
|
|
Guacamole.HTTPTunnel.prototype = new Guacamole.Tunnel();
|
|
Guacamole.WebSocketTunnel = function(tunnelURL) {
|
|
var tunnel = this;
|
|
var parser = null;
|
|
var socket = null;
|
|
var receive_timeout = null;
|
|
var unstableTimeout = null;
|
|
var pingTimeout = null;
|
|
var ws_protocol = {
|
|
"http:": "ws:",
|
|
"https:": "wss:"
|
|
};
|
|
var PING_FREQUENCY = 500;
|
|
var lastSentPing = 0;
|
|
if (tunnelURL.substring(0, 3) !== "ws:" && tunnelURL.substring(0, 4) !== "wss:") {
|
|
var protocol = ws_protocol[window.location.protocol];
|
|
if (tunnelURL.substring(0, 1) === "/")
|
|
tunnelURL = protocol + "//" + window.location.host + tunnelURL;
|
|
else {
|
|
var slash = window.location.pathname.lastIndexOf("/");
|
|
var path = window.location.pathname.substring(0, slash + 1);
|
|
tunnelURL = protocol + "//" + window.location.host + path + tunnelURL;
|
|
}
|
|
}
|
|
var sendPing = function sendPing2() {
|
|
var currentTime = (/* @__PURE__ */ new Date()).getTime();
|
|
tunnel.sendMessage(Guacamole.Tunnel.INTERNAL_DATA_OPCODE, "ping", currentTime);
|
|
lastSentPing = currentTime;
|
|
};
|
|
var resetTimers = function resetTimers2() {
|
|
window.clearTimeout(receive_timeout);
|
|
window.clearTimeout(unstableTimeout);
|
|
window.clearTimeout(pingTimeout);
|
|
if (tunnel.state === Guacamole.Tunnel.State.UNSTABLE)
|
|
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
|
receive_timeout = window.setTimeout(function() {
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_TIMEOUT, "Server timeout."));
|
|
}, tunnel.receiveTimeout);
|
|
unstableTimeout = window.setTimeout(function() {
|
|
tunnel.setState(Guacamole.Tunnel.State.UNSTABLE);
|
|
}, tunnel.unstableThreshold);
|
|
var currentTime = (/* @__PURE__ */ new Date()).getTime();
|
|
var pingDelay = Math.max(lastSentPing + PING_FREQUENCY - currentTime, 0);
|
|
if (pingDelay > 0)
|
|
pingTimeout = window.setTimeout(sendPing, pingDelay);
|
|
else
|
|
sendPing();
|
|
};
|
|
function close_tunnel(status) {
|
|
window.clearTimeout(receive_timeout);
|
|
window.clearTimeout(unstableTimeout);
|
|
window.clearTimeout(pingTimeout);
|
|
if (tunnel.state === Guacamole.Tunnel.State.CLOSED)
|
|
return;
|
|
if (status.code !== Guacamole.Status.Code.SUCCESS && tunnel.onerror)
|
|
tunnel.onerror(status);
|
|
tunnel.setState(Guacamole.Tunnel.State.CLOSED);
|
|
socket.close();
|
|
}
|
|
this.sendMessage = function(elements) {
|
|
if (!tunnel.isConnected())
|
|
return;
|
|
if (!arguments.length)
|
|
return;
|
|
socket.send(Guacamole.Parser.toInstruction(arguments));
|
|
};
|
|
this.connect = function(data) {
|
|
resetTimers();
|
|
tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
|
|
parser = new Guacamole.Parser();
|
|
parser.oninstruction = function instructionReceived(opcode, args) {
|
|
if (tunnel.uuid === null) {
|
|
if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE && args.length === 1)
|
|
tunnel.setUUID(args[0]);
|
|
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
|
}
|
|
if (opcode !== Guacamole.Tunnel.INTERNAL_DATA_OPCODE && tunnel.oninstruction)
|
|
tunnel.oninstruction(opcode, args);
|
|
};
|
|
socket = new WebSocket(tunnelURL + "?" + data, "guacamole");
|
|
socket.onopen = function(event) {
|
|
resetTimers();
|
|
};
|
|
socket.onclose = function(event) {
|
|
if (event.reason)
|
|
close_tunnel(new Guacamole.Status(parseInt(event.reason), event.reason));
|
|
else if (event.code)
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.fromWebSocketCode(event.code)));
|
|
else
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_NOT_FOUND));
|
|
};
|
|
socket.onmessage = function(event) {
|
|
resetTimers();
|
|
try {
|
|
parser.receive(event.data);
|
|
} catch (e) {
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SERVER_ERROR, e.message));
|
|
}
|
|
};
|
|
};
|
|
this.disconnect = function() {
|
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SUCCESS, "Manually closed."));
|
|
};
|
|
};
|
|
Guacamole.WebSocketTunnel.prototype = new Guacamole.Tunnel();
|
|
Guacamole.ChainedTunnel = function(tunnelChain) {
|
|
var chained_tunnel = this;
|
|
var connect_data;
|
|
var tunnels = [];
|
|
var committedTunnel = null;
|
|
for (var i = 0; i < arguments.length; i++)
|
|
tunnels.push(arguments[i]);
|
|
function attach(tunnel) {
|
|
chained_tunnel.disconnect = tunnel.disconnect;
|
|
chained_tunnel.sendMessage = tunnel.sendMessage;
|
|
var failTunnel = function failTunnel2(status) {
|
|
if (status && status.code === Guacamole.Status.Code.UPSTREAM_TIMEOUT) {
|
|
tunnels = [];
|
|
return null;
|
|
}
|
|
var next_tunnel = tunnels.shift();
|
|
if (next_tunnel) {
|
|
tunnel.onerror = null;
|
|
tunnel.oninstruction = null;
|
|
tunnel.onstatechange = null;
|
|
attach(next_tunnel);
|
|
}
|
|
return next_tunnel;
|
|
};
|
|
function commit_tunnel() {
|
|
tunnel.onstatechange = chained_tunnel.onstatechange;
|
|
tunnel.oninstruction = chained_tunnel.oninstruction;
|
|
tunnel.onerror = chained_tunnel.onerror;
|
|
if (tunnel.uuid)
|
|
chained_tunnel.setUUID(tunnel.uuid);
|
|
tunnel.onuuid = function uuidReceived(uuid) {
|
|
chained_tunnel.setUUID(uuid);
|
|
};
|
|
committedTunnel = tunnel;
|
|
}
|
|
tunnel.onstatechange = function(state) {
|
|
switch (state) {
|
|
// If open, use this tunnel from this point forward.
|
|
case Guacamole.Tunnel.State.OPEN:
|
|
commit_tunnel();
|
|
if (chained_tunnel.onstatechange)
|
|
chained_tunnel.onstatechange(state);
|
|
break;
|
|
// If closed, mark failure, attempt next tunnel
|
|
case Guacamole.Tunnel.State.CLOSED:
|
|
if (!failTunnel() && chained_tunnel.onstatechange)
|
|
chained_tunnel.onstatechange(state);
|
|
break;
|
|
}
|
|
};
|
|
tunnel.oninstruction = function(opcode, elements) {
|
|
commit_tunnel();
|
|
if (chained_tunnel.oninstruction)
|
|
chained_tunnel.oninstruction(opcode, elements);
|
|
};
|
|
tunnel.onerror = function(status) {
|
|
if (!failTunnel(status) && chained_tunnel.onerror)
|
|
chained_tunnel.onerror(status);
|
|
};
|
|
tunnel.connect(connect_data);
|
|
}
|
|
this.connect = function(data) {
|
|
connect_data = data;
|
|
var next_tunnel = committedTunnel ? committedTunnel : tunnels.shift();
|
|
if (next_tunnel)
|
|
attach(next_tunnel);
|
|
else if (chained_tunnel.onerror)
|
|
chained_tunnel.onerror(Guacamole.Status.Code.SERVER_ERROR, "No tunnels to try.");
|
|
};
|
|
};
|
|
Guacamole.ChainedTunnel.prototype = new Guacamole.Tunnel();
|
|
Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTunnelHeaders) {
|
|
var tunnel = this;
|
|
var abortController = null;
|
|
var extraHeaders = extraTunnelHeaders || {};
|
|
this.size = null;
|
|
this.sendMessage = function sendMessage(elements) {
|
|
};
|
|
this.connect = function connect(data) {
|
|
tunnel.disconnect();
|
|
tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
|
|
var parser = new Guacamole.Parser();
|
|
var utf8Parser = new Guacamole.UTF8Parser();
|
|
parser.oninstruction = function instructionReceived(opcode, args) {
|
|
if (tunnel.oninstruction)
|
|
tunnel.oninstruction(opcode, args);
|
|
};
|
|
abortController = new AbortController();
|
|
fetch(url, {
|
|
headers: extraHeaders,
|
|
credentials: crossDomain ? "include" : "same-origin",
|
|
signal: abortController.signal
|
|
}).then(function gotResponse(response) {
|
|
if (!response.ok) {
|
|
if (tunnel.onerror)
|
|
tunnel.onerror(new Guacamole.Status(
|
|
Guacamole.Status.Code.fromHTTPCode(response.status),
|
|
response.statusText
|
|
));
|
|
tunnel.disconnect();
|
|
return;
|
|
}
|
|
tunnel.size = response.headers.get("Content-Length");
|
|
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
|
var reader = response.body.getReader();
|
|
var processReceivedText = function processReceivedText2(result) {
|
|
if (result.done) {
|
|
tunnel.disconnect();
|
|
return;
|
|
}
|
|
parser.receive(utf8Parser.decode(result.value));
|
|
reader.read().then(processReceivedText2);
|
|
};
|
|
reader.read().then(processReceivedText);
|
|
});
|
|
};
|
|
this.disconnect = function disconnect() {
|
|
if (abortController) {
|
|
abortController.abort();
|
|
abortController = null;
|
|
}
|
|
tunnel.setState(Guacamole.Tunnel.State.CLOSED);
|
|
};
|
|
};
|
|
Guacamole.StaticHTTPTunnel.prototype = new Guacamole.Tunnel();
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.UTF8Parser = function UTF8Parser() {
|
|
var bytesRemaining = 0;
|
|
var codepoint = 0;
|
|
this.decode = function decode(buffer) {
|
|
var text = "";
|
|
var bytes = new Uint8Array(buffer);
|
|
for (var i = 0; i < bytes.length; i++) {
|
|
var value = bytes[i];
|
|
if (bytesRemaining === 0) {
|
|
if ((value | 127) === 127)
|
|
text += String.fromCharCode(value);
|
|
else if ((value | 31) === 223) {
|
|
codepoint = value & 31;
|
|
bytesRemaining = 1;
|
|
} else if ((value | 15) === 239) {
|
|
codepoint = value & 15;
|
|
bytesRemaining = 2;
|
|
} else if ((value | 7) === 247) {
|
|
codepoint = value & 7;
|
|
bytesRemaining = 3;
|
|
} else
|
|
text += "�";
|
|
} else if ((value | 63) === 191) {
|
|
codepoint = codepoint << 6 | value & 63;
|
|
bytesRemaining--;
|
|
if (bytesRemaining === 0)
|
|
text += String.fromCharCode(codepoint);
|
|
} else {
|
|
bytesRemaining = 0;
|
|
text += "�";
|
|
}
|
|
}
|
|
return text;
|
|
};
|
|
};
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.API_VERSION = "1.6.0";
|
|
var Guacamole = Guacamole || {};
|
|
Guacamole.VideoPlayer = function VideoPlayer() {
|
|
this.sync = function sync() {
|
|
};
|
|
};
|
|
Guacamole.VideoPlayer.isSupportedType = function isSupportedType5(mimetype) {
|
|
return false;
|
|
};
|
|
Guacamole.VideoPlayer.getSupportedTypes = function getSupportedTypes5() {
|
|
return [];
|
|
};
|
|
Guacamole.VideoPlayer.getInstance = function getInstance3(stream, layer, mimetype) {
|
|
return null;
|
|
};
|
|
var guacamole_common_default = Guacamole;
|
|
export {
|
|
guacamole_common_default as default
|
|
};
|
|
//# sourceMappingURL=@dushixiang_guacamole-common-js.js.map
|