mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
Merge patch branch changes to main.
This commit is contained in:
@@ -39,14 +39,28 @@ Guacamole.ArrayBufferReader = function(stream) {
|
|||||||
// Receive blobs as array buffers
|
// Receive blobs as array buffers
|
||||||
stream.onblob = function(data) {
|
stream.onblob = function(data) {
|
||||||
|
|
||||||
// Convert to ArrayBuffer
|
var arrayBuffer, bufferView;
|
||||||
|
|
||||||
|
// Use native methods for directly decoding base64 to an array buffer
|
||||||
|
// when possible
|
||||||
|
if (Uint8Array.fromBase64) {
|
||||||
|
bufferView = Uint8Array.fromBase64(data);
|
||||||
|
arrayBuffer = bufferView.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rely on binary strings and manual conversions where native methods
|
||||||
|
// like fromBase64() are not available
|
||||||
|
else {
|
||||||
|
|
||||||
var binary = window.atob(data);
|
var binary = window.atob(data);
|
||||||
var arrayBuffer = new ArrayBuffer(binary.length);
|
arrayBuffer = new ArrayBuffer(binary.length);
|
||||||
var bufferView = new Uint8Array(arrayBuffer);
|
bufferView = new Uint8Array(arrayBuffer);
|
||||||
|
|
||||||
for (var i=0; i<binary.length; i++)
|
for (var i=0; i<binary.length; i++)
|
||||||
bufferView[i] = binary.charCodeAt(i);
|
bufferView[i] = binary.charCodeAt(i);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Call handler, if present
|
// Call handler, if present
|
||||||
if (guac_reader.ondata)
|
if (guac_reader.ondata)
|
||||||
guac_reader.ondata(arrayBuffer);
|
guac_reader.ondata(arrayBuffer);
|
||||||
|
@@ -185,19 +185,6 @@ Guacamole.Display = function() {
|
|||||||
*/
|
*/
|
||||||
var frames = [];
|
var frames = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of the animation frame request returned by the last call to
|
|
||||||
* requestAnimationFrame(). This value will only be set if the browser
|
|
||||||
* supports requestAnimationFrame(), if a frame render is currently
|
|
||||||
* pending, and if the current browser tab is currently focused (likely to
|
|
||||||
* handle requests for animation frames). In all other cases, this will be
|
|
||||||
* null.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
var inProgressFrame = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flushes all pending frames synchronously. This function will block until
|
* Flushes all pending frames synchronously. This function will block until
|
||||||
* all pending frames have rendered. If a frame is currently blocked by an
|
* all pending frames have rendered. If a frame is currently blocked by an
|
||||||
@@ -239,45 +226,6 @@ Guacamole.Display = function() {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes all pending frames asynchronously. This function returns
|
|
||||||
* immediately, relying on requestAnimationFrame() to dictate when each
|
|
||||||
* frame should be flushed.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var asyncFlush = function asyncFlush() {
|
|
||||||
|
|
||||||
var continueFlush = function continueFlush() {
|
|
||||||
|
|
||||||
// We're no longer waiting to render a frame
|
|
||||||
inProgressFrame = null;
|
|
||||||
|
|
||||||
// Nothing to do if there are no frames remaining
|
|
||||||
if (!frames.length)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Flush the next frame only if it is ready (not awaiting
|
|
||||||
// completion of some asynchronous operation like an image load)
|
|
||||||
if (frames[0].isReady()) {
|
|
||||||
var frame = frames.shift();
|
|
||||||
frame.flush();
|
|
||||||
notifyFlushed(frame.localTimestamp, frame.remoteTimestamp, frame.logicalFrames);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request yet another animation frame if frames remain to be
|
|
||||||
// flushed
|
|
||||||
if (frames.length)
|
|
||||||
inProgressFrame = window.requestAnimationFrame(continueFlush);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Begin flushing frames if not already waiting to render a frame
|
|
||||||
if (!inProgressFrame)
|
|
||||||
inProgressFrame = window.requestAnimationFrame(continueFlush);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recently-gathered display render statistics, as made available by calls
|
* Recently-gathered display render statistics, as made available by calls
|
||||||
* to notifyFlushed(). The contents of this array will be trimmed to
|
* to notifyFlushed(). The contents of this array will be trimmed to
|
||||||
@@ -373,33 +321,12 @@ Guacamole.Display = function() {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Switch from asynchronous frame handling to synchronous frame handling if
|
|
||||||
// requestAnimationFrame() is unlikely to be usable (browsers may not
|
|
||||||
// invoke the animation frame callback if the relevant tab is not focused)
|
|
||||||
window.addEventListener('blur', function switchToSyncFlush() {
|
|
||||||
if (inProgressFrame && !document.hasFocus()) {
|
|
||||||
|
|
||||||
// Cancel pending asynchronous processing of frame ...
|
|
||||||
window.cancelAnimationFrame(inProgressFrame);
|
|
||||||
inProgressFrame = null;
|
|
||||||
|
|
||||||
// ... and instead process it synchronously
|
|
||||||
syncFlush();
|
|
||||||
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flushes all pending frames.
|
* Flushes all pending frames.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function __flush_frames() {
|
function __flush_frames() {
|
||||||
|
|
||||||
if (window.requestAnimationFrame && document.hasFocus())
|
|
||||||
asyncFlush();
|
|
||||||
else
|
|
||||||
syncFlush();
|
syncFlush();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -553,7 +480,10 @@ Guacamole.Display = function() {
|
|||||||
this.unblock = function() {
|
this.unblock = function() {
|
||||||
if (task.blocked) {
|
if (task.blocked) {
|
||||||
task.blocked = false;
|
task.blocked = false;
|
||||||
|
|
||||||
|
if (frames.length)
|
||||||
__flush_frames();
|
__flush_frames();
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -978,17 +908,38 @@ Guacamole.Display = function() {
|
|||||||
*/
|
*/
|
||||||
this.drawStream = function drawStream(layer, x, y, stream, mimetype) {
|
this.drawStream = function drawStream(layer, x, y, stream, mimetype) {
|
||||||
|
|
||||||
// If createImageBitmap() is available, load the image as a blob so
|
// Leverage ImageDecoder to decode the image stream as it is received
|
||||||
// that function can be used
|
// whenever possible, as this reduces latency that might otherwise be
|
||||||
if (window.createImageBitmap) {
|
// caused by waiting for the full image to be received
|
||||||
var reader = new Guacamole.BlobReader(stream, mimetype);
|
if (window.ImageDecoder && window.ReadableStream) {
|
||||||
reader.onend = function drawImageBlob() {
|
|
||||||
guac_display.drawBlob(layer, x, y, reader.getBlob());
|
var imageDecoder = new ImageDecoder({
|
||||||
};
|
type: mimetype,
|
||||||
|
data: stream.toReadableStream()
|
||||||
|
});
|
||||||
|
|
||||||
|
var decodedFrame = null;
|
||||||
|
|
||||||
|
// Draw image once loaded
|
||||||
|
var task = scheduleTask(function drawImageBitmap() {
|
||||||
|
layer.drawImage(x, y, decodedFrame);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
imageDecoder.decode({ completeFramesOnly: true }).then(function bitmapLoaded(result) {
|
||||||
|
decodedFrame = result.image;
|
||||||
|
task.unblock();
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lacking createImageBitmap(), fall back to data URIs and the Image
|
// NOTE: We do not use Blobs and createImageBitmap() here, as doing so
|
||||||
// object
|
// is very latent compared to the old data URI method and the new
|
||||||
|
// ImageDecoder object. The new ImageDecoder object is currently
|
||||||
|
// supported by most browsers, with other browsers being much faster if
|
||||||
|
// data URIs are used. The iOS version of Safari is particularly laggy
|
||||||
|
// if Blobs and createImageBitmap() are used instead.
|
||||||
|
|
||||||
|
// Lacking ImageDecoder, fall back to data URIs and the Image object
|
||||||
else {
|
else {
|
||||||
var reader = new Guacamole.DataURIReader(stream, mimetype);
|
var reader = new Guacamole.DataURIReader(stream, mimetype);
|
||||||
reader.onend = function drawImageDataURI() {
|
reader.onend = function drawImageDataURI() {
|
||||||
|
@@ -76,4 +76,66 @@ Guacamole.InputStream = function(client, index) {
|
|||||||
client.sendAck(guac_stream.index, message, code);
|
client.sendAck(guac_stream.index, message, code);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ReadableStream that receives the data sent to this stream
|
||||||
|
* by the Guacamole server. This function may be invoked at most once per
|
||||||
|
* stream, and invoking this function will overwrite any installed event
|
||||||
|
* handlers on this stream.
|
||||||
|
*
|
||||||
|
* A ReadableStream is a JavaScript object defined by the "Streams"
|
||||||
|
* standard. It is supported by most browsers, but not necessarily all
|
||||||
|
* browsers. The caller should verify this support is present before
|
||||||
|
* invoking this function. The behavior of this function when the browser
|
||||||
|
* does not support ReadableStream is not defined.
|
||||||
|
*
|
||||||
|
* @see {@link https://streams.spec.whatwg.org/#rs-class}
|
||||||
|
*
|
||||||
|
* @returns {!ReadableStream}
|
||||||
|
* A new ReadableStream that receives the bytes sent along this stream
|
||||||
|
* by the Guacamole server.
|
||||||
|
*/
|
||||||
|
this.toReadableStream = function toReadableStream() {
|
||||||
|
return new ReadableStream({
|
||||||
|
type: 'bytes',
|
||||||
|
start: function startStream(controller) {
|
||||||
|
|
||||||
|
var reader = new Guacamole.ArrayBufferReader(guac_stream);
|
||||||
|
|
||||||
|
// Provide any received blocks of data to the ReadableStream
|
||||||
|
// controller, such that they will be read by whatever is
|
||||||
|
// consuming the ReadableStream
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Notify the ReadableStream when the end of the stream is
|
||||||
|
// reached
|
||||||
|
reader.onend = function dataComplete() {
|
||||||
|
controller.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@@ -34,6 +34,7 @@ import java.io.OutputStreamWriter;
|
|||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.StandardSocketOptions;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.GuacamoleServerException;
|
import org.apache.guacamole.GuacamoleServerException;
|
||||||
import org.apache.guacamole.GuacamoleUpstreamTimeoutException;
|
import org.apache.guacamole.GuacamoleUpstreamTimeoutException;
|
||||||
@@ -102,6 +103,10 @@ public class InetGuacamoleSocket implements GuacamoleSocket {
|
|||||||
// Set read timeout
|
// Set read timeout
|
||||||
sock.setSoTimeout(SOCKET_TIMEOUT);
|
sock.setSoTimeout(SOCKET_TIMEOUT);
|
||||||
|
|
||||||
|
// Set TCP_NODELAY to avoid any latency that would otherwise be
|
||||||
|
// added by the networking stack and Nagle's algorithm
|
||||||
|
sock.setTcpNoDelay(true);
|
||||||
|
|
||||||
// On successful connect, retrieve I/O streams
|
// On successful connect, retrieve I/O streams
|
||||||
reader = new ReaderGuacamoleReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
|
reader = new ReaderGuacamoleReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
|
||||||
writer = new WriterGuacamoleWriter(new OutputStreamWriter(sock.getOutputStream(), "UTF-8"));
|
writer = new WriterGuacamoleWriter(new OutputStreamWriter(sock.getOutputStream(), "UTF-8"));
|
||||||
|
Reference in New Issue
Block a user