Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
4
guacamole-common-js/.gitignore
vendored
Normal file
4
guacamole-common-js/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node/
|
||||
node_modules/
|
||||
target/
|
||||
*~
|
||||
0
guacamole-common-js/.ratignore
Normal file
0
guacamole-common-js/.ratignore
Normal file
14
guacamole-common-js/jsdoc-conf.json
Normal file
14
guacamole-common-js/jsdoc-conf.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"source" : {
|
||||
"include" : "src"
|
||||
},
|
||||
"opts" : {
|
||||
"recurse" : true,
|
||||
"destination" : "target/apidocs"
|
||||
},
|
||||
"templates" : {
|
||||
"default" : {
|
||||
"useLongnameInNav" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
54
guacamole-common-js/karma-ci.conf.js
Normal file
54
guacamole-common-js/karma-ci.conf.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A karma configuration intended for use in builds or CI. Runs all discovered
|
||||
* unit tests under a headless firefox browser and immediately exits.
|
||||
*/
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
|
||||
// Discover and run jasmine tests
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
// Pattern matching all javascript source and tests
|
||||
files: [
|
||||
'src/**/*.js'
|
||||
],
|
||||
|
||||
// Run the tests once and exit
|
||||
singleRun: true,
|
||||
|
||||
// Disable automatic test running on changed files
|
||||
autoWatch: false,
|
||||
|
||||
// Use a headless firefox browser to run the tests
|
||||
browsers: ['FirefoxHeadless'],
|
||||
customLaunchers: {
|
||||
'FirefoxHeadless': {
|
||||
base: 'Firefox',
|
||||
flags: [
|
||||
'--headless'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
1881
guacamole-common-js/package-lock.json
generated
Normal file
1881
guacamole-common-js/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
guacamole-common-js/package.json
Normal file
9
guacamole-common-js/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"description": "Dependencies to be installed by Maven for running unit tests and generating documentation",
|
||||
"devDependencies": {
|
||||
"jsdoc": "^4.0.3",
|
||||
"karma": "^6.4.4",
|
||||
"karma-firefox-launcher": "^2.1.3",
|
||||
"karma-jasmine": "^5.1.0"
|
||||
}
|
||||
}
|
||||
237
guacamole-common-js/pom.xml
Normal file
237
guacamole-common-js/pom.xml
Normal file
@@ -0,0 +1,237 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-common-js</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.6.0</version>
|
||||
<name>guacamole-common-js</name>
|
||||
<url>http://guacamole.apache.org/</url>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-client</artifactId>
|
||||
<version>1.6.0</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
<!--
|
||||
The location where temporary files should be stored for communicating
|
||||
between karma and firefox. The default location, /tmp, does not work
|
||||
if firefox is installed via snap.
|
||||
-->
|
||||
<firefox.temp.dir>${project.build.directory}/tmp</firefox.temp.dir>
|
||||
|
||||
<!--
|
||||
Skip tests unless requested otherwise with -DskipTests=false.
|
||||
Skipped by default because these tests require firefox to be installed.
|
||||
-->
|
||||
<skipTests>true</skipTests>
|
||||
|
||||
</properties>
|
||||
|
||||
<description>
|
||||
The base JavaScript API of the Guacamole project, providing JavaScript
|
||||
support for the Guacamole stack, including a full client
|
||||
implementation for the Guacamole protocol.
|
||||
</description>
|
||||
|
||||
<!-- All applicable licenses -->
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<!-- Git repository -->
|
||||
<scm>
|
||||
<url>https://github.com/apache/guacamole-client</url>
|
||||
<connection>scm:git:https://git.wip-us.apache.org/repos/asf/guacamole-client.git</connection>
|
||||
|
||||
</scm>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<!-- Assemble JS files into single .zip -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<descriptors>
|
||||
<descriptor>static.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-zip</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- JS/CSS Minification Plugin -->
|
||||
<plugin>
|
||||
<groupId>com.github.buckelieg</groupId>
|
||||
<artifactId>minify-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-minify</id>
|
||||
<configuration>
|
||||
|
||||
<charset>UTF-8</charset>
|
||||
<jsEngine>CLOSURE</jsEngine>
|
||||
|
||||
<jsSourceDir>/</jsSourceDir>
|
||||
<jsTargetDir>/</jsTargetDir>
|
||||
<jsFinalFile>all.js</jsFinalFile>
|
||||
|
||||
<jsSourceFiles>
|
||||
<jsSourceFile>common/license.js</jsSourceFile>
|
||||
</jsSourceFiles>
|
||||
|
||||
<jsSourceIncludes>
|
||||
<jsSourceInclude>modules/**/*.js</jsSourceInclude>
|
||||
</jsSourceIncludes>
|
||||
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>minify</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Skip tests if configured to do so -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<skipTests>${skipTests}</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Ensure the firefox temp directory exists -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>createFirefoxTempdir</id>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<target>
|
||||
<mkdir dir="${firefox.temp.dir}"/>
|
||||
</target>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Unit test using Jasmin and Firefox -->
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>1.12.1</version>
|
||||
|
||||
<configuration>
|
||||
|
||||
<!-- The version of node to use for running tests -->
|
||||
<nodeVersion>v16.19.1</nodeVersion>
|
||||
|
||||
<!-- Install dependencies with "npm ci" for repeatability -->
|
||||
<arguments>ci</arguments>
|
||||
|
||||
<!-- The location of the karma config file -->
|
||||
<karmaConfPath>karma-ci.conf.js</karmaConfPath>
|
||||
|
||||
<!-- Tell karma to use the custom temp directory -->
|
||||
<environmentVariables>
|
||||
<TMPDIR>${firefox.temp.dir}</TMPDIR>
|
||||
</environmentVariables>
|
||||
|
||||
</configuration>
|
||||
|
||||
<executions>
|
||||
|
||||
<!-- Install node.js and NPM before running tests or generating documentation -->
|
||||
<execution>
|
||||
<id>install-node-and-npm</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
<!-- Generate documentation using JSDoc -->
|
||||
<execution>
|
||||
<id>generate-docs</id>
|
||||
<goals>
|
||||
<goal>npx</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<arguments>jsdoc -c jsdoc-conf.json</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<!-- Install test and documentation dependencies -->
|
||||
<execution>
|
||||
<id>npm-install</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
<!-- Run all tests non-interactively -->
|
||||
<execution>
|
||||
<id>run-tests</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>karma</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
</executions>
|
||||
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
18
guacamole-common-js/src/main/webapp/common/license.js
Normal file
18
guacamole-common-js/src/main/webapp/common/license.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A reader which automatically handles the given input stream, returning
|
||||
* strictly received packets as array buffers. Note that this object will
|
||||
* overwrite any installed event handlers on the given Guacamole.InputStream.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.InputStream} stream
|
||||
* The stream that data will be read from.
|
||||
*/
|
||||
Guacamole.ArrayBufferReader = function(stream) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.InputStream.
|
||||
* @private
|
||||
*/
|
||||
var guac_reader = this;
|
||||
|
||||
// Receive blobs as array buffers
|
||||
stream.onblob = function(data) {
|
||||
|
||||
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);
|
||||
arrayBuffer = new ArrayBuffer(binary.length);
|
||||
bufferView = new Uint8Array(arrayBuffer);
|
||||
|
||||
for (var i=0; i<binary.length; i++)
|
||||
bufferView[i] = binary.charCodeAt(i);
|
||||
|
||||
}
|
||||
|
||||
// Call handler, if present
|
||||
if (guac_reader.ondata)
|
||||
guac_reader.ondata(arrayBuffer);
|
||||
|
||||
};
|
||||
|
||||
// Simply call onend when end received
|
||||
stream.onend = function() {
|
||||
if (guac_reader.onend)
|
||||
guac_reader.onend();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired once for every blob of data received.
|
||||
*
|
||||
* @event
|
||||
* @param {!ArrayBuffer} buffer
|
||||
* The data packet received.
|
||||
*/
|
||||
this.ondata = null;
|
||||
|
||||
/**
|
||||
* Fired once this stream is finished and no further data will be written.
|
||||
* @event
|
||||
*/
|
||||
this.onend = null;
|
||||
|
||||
};
|
||||
128
guacamole-common-js/src/main/webapp/modules/ArrayBufferWriter.js
Normal file
128
guacamole-common-js/src/main/webapp/modules/ArrayBufferWriter.js
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A writer which automatically writes to the given output stream with arbitrary
|
||||
* binary data, supplied as ArrayBuffers.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.OutputStream} stream
|
||||
* The stream that data will be written to.
|
||||
*/
|
||||
Guacamole.ArrayBufferWriter = function(stream) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.StringWriter.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.ArrayBufferWriter}
|
||||
*/
|
||||
var guac_writer = this;
|
||||
|
||||
// Simply call onack for acknowledgements
|
||||
stream.onack = function(status) {
|
||||
if (guac_writer.onack)
|
||||
guac_writer.onack(status);
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes the given data as base64, sending it as a blob. The data must
|
||||
* be small enough to fit into a single blob instruction.
|
||||
*
|
||||
* @private
|
||||
* @param {!Uint8Array} bytes
|
||||
* The data to send.
|
||||
*/
|
||||
function __send_blob(bytes) {
|
||||
|
||||
var binary = "";
|
||||
|
||||
// Produce binary string from bytes in buffer
|
||||
for (var i=0; i<bytes.byteLength; i++)
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
|
||||
// Send as base64
|
||||
stream.sendBlob(window.btoa(binary));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum length of any blob sent by this Guacamole.ArrayBufferWriter,
|
||||
* in bytes. Data sent via
|
||||
* [sendData()]{@link Guacamole.ArrayBufferWriter#sendData} which exceeds
|
||||
* this length will be split into multiple blobs. As the Guacamole protocol
|
||||
* limits the maximum size of any instruction or instruction element to
|
||||
* 8192 bytes, and the contents of blobs will be base64-encoded, this value
|
||||
* should only be increased with extreme caution.
|
||||
*
|
||||
* @type {!number}
|
||||
* @default {@link Guacamole.ArrayBufferWriter.DEFAULT_BLOB_LENGTH}
|
||||
*/
|
||||
this.blobLength = Guacamole.ArrayBufferWriter.DEFAULT_BLOB_LENGTH;
|
||||
|
||||
/**
|
||||
* Sends the given data.
|
||||
*
|
||||
* @param {!(ArrayBuffer|TypedArray)} data
|
||||
* The data to send.
|
||||
*/
|
||||
this.sendData = function(data) {
|
||||
|
||||
var bytes = new Uint8Array(data);
|
||||
|
||||
// If small enough to fit into single instruction, send as-is
|
||||
if (bytes.length <= guac_writer.blobLength)
|
||||
__send_blob(bytes);
|
||||
|
||||
// Otherwise, send as multiple instructions
|
||||
else {
|
||||
for (var offset=0; offset<bytes.length; offset += guac_writer.blobLength)
|
||||
__send_blob(bytes.subarray(offset, offset + guac_writer.blobLength));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Signals that no further text will be sent, effectively closing the
|
||||
* stream.
|
||||
*/
|
||||
this.sendEnd = function() {
|
||||
stream.sendEnd();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired for received data, if acknowledged by the server.
|
||||
* @event
|
||||
* @param {!Guacamole.Status} status
|
||||
* The status of the operation.
|
||||
*/
|
||||
this.onack = null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The default maximum blob length for new Guacamole.ArrayBufferWriter
|
||||
* instances.
|
||||
*
|
||||
* @constant
|
||||
* @type {!number}
|
||||
*/
|
||||
Guacamole.ArrayBufferWriter.DEFAULT_BLOB_LENGTH = 6048;
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Maintains a singleton instance of the Web Audio API AudioContext class,
|
||||
* instantiating the AudioContext only in response to the first call to
|
||||
* getAudioContext(), and only if no existing AudioContext instance has been
|
||||
* provided via the singleton property. Subsequent calls to getAudioContext()
|
||||
* will return the same instance.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
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() {
|
||||
|
||||
// Fallback to Webkit-specific AudioContext implementation
|
||||
var AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
|
||||
// Get new AudioContext instance if Web Audio API is supported
|
||||
if (AudioContext) {
|
||||
try {
|
||||
|
||||
// Create new instance if none yet exists
|
||||
if (!Guacamole.AudioContextFactory.singleton)
|
||||
Guacamole.AudioContextFactory.singleton = new AudioContext();
|
||||
|
||||
// Return singleton instance
|
||||
return Guacamole.AudioContextFactory.singleton;
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
// Do not use Web Audio API if not allowed by browser
|
||||
}
|
||||
}
|
||||
|
||||
// Web Audio API not supported
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
506
guacamole-common-js/src/main/webapp/modules/AudioPlayer.js
Normal file
506
guacamole-common-js/src/main/webapp/modules/AudioPlayer.js
Normal file
@@ -0,0 +1,506 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Abstract audio player which accepts, queues and plays back arbitrary audio
|
||||
* data. It is up to implementations of this class to provide some means of
|
||||
* handling a provided Guacamole.InputStream. Data received along the provided
|
||||
* stream is to be played back immediately.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
Guacamole.AudioPlayer = function AudioPlayer() {
|
||||
|
||||
/**
|
||||
* Notifies this Guacamole.AudioPlayer that all audio up to the current
|
||||
* point in time has been given via the underlying stream, and that any
|
||||
* difference in time between queued audio data and the current time can be
|
||||
* considered latency.
|
||||
*/
|
||||
this.sync = function sync() {
|
||||
// Default implementation - do nothing
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether the given mimetype is supported by any built-in
|
||||
* implementation of Guacamole.AudioPlayer, and thus will be properly handled
|
||||
* by Guacamole.AudioPlayer.getInstance().
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype to check.
|
||||
*
|
||||
* @returns {!boolean}
|
||||
* true if the given mimetype is supported by any built-in
|
||||
* Guacamole.AudioPlayer, false otherwise.
|
||||
*/
|
||||
Guacamole.AudioPlayer.isSupportedType = function isSupportedType(mimetype) {
|
||||
|
||||
return Guacamole.RawAudioPlayer.isSupportedType(mimetype);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of all mimetypes supported by any built-in
|
||||
* Guacamole.AudioPlayer, in rough order of priority. Beware that only the core
|
||||
* mimetypes themselves will be listed. Any mimetype parameters, even required
|
||||
* ones, will not be included in the list. For example, "audio/L8" is a
|
||||
* supported raw audio mimetype that is supported, but it is invalid without
|
||||
* additional parameters. Something like "audio/L8;rate=44100" would be valid,
|
||||
* however (see https://tools.ietf.org/html/rfc4856).
|
||||
*
|
||||
* @returns {!string[]}
|
||||
* A list of all mimetypes supported by any built-in Guacamole.AudioPlayer,
|
||||
* excluding any parameters.
|
||||
*/
|
||||
Guacamole.AudioPlayer.getSupportedTypes = function getSupportedTypes() {
|
||||
|
||||
return Guacamole.RawAudioPlayer.getSupportedTypes();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an instance of Guacamole.AudioPlayer providing support for the given
|
||||
* audio format. If support for the given audio format is not available, null
|
||||
* is returned.
|
||||
*
|
||||
* @param {!Guacamole.InputStream} stream
|
||||
* The Guacamole.InputStream to read audio data from.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype of the audio data in the provided stream.
|
||||
*
|
||||
* @return {Guacamole.AudioPlayer}
|
||||
* A Guacamole.AudioPlayer instance supporting the given mimetype and
|
||||
* reading from the given stream, or null if support for the given mimetype
|
||||
* is absent.
|
||||
*/
|
||||
Guacamole.AudioPlayer.getInstance = function getInstance(stream, mimetype) {
|
||||
|
||||
// Use raw audio player if possible
|
||||
if (Guacamole.RawAudioPlayer.isSupportedType(mimetype))
|
||||
return new Guacamole.RawAudioPlayer(stream, mimetype);
|
||||
|
||||
// No support for given mimetype
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of Guacamole.AudioPlayer providing support for raw PCM format
|
||||
* audio. This player relies only on the Web Audio API and does not require any
|
||||
* browser-level support for its audio formats.
|
||||
*
|
||||
* @constructor
|
||||
* @augments Guacamole.AudioPlayer
|
||||
* @param {!Guacamole.InputStream} stream
|
||||
* The Guacamole.InputStream to read audio data from.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype of the audio data in the provided stream, which must be a
|
||||
* "audio/L8" or "audio/L16" mimetype with necessary parameters, such as:
|
||||
* "audio/L16;rate=44100,channels=2".
|
||||
*/
|
||||
Guacamole.RawAudioPlayer = function RawAudioPlayer(stream, mimetype) {
|
||||
|
||||
/**
|
||||
* The format of audio this player will decode.
|
||||
*
|
||||
* @private
|
||||
* @type {Guacamole.RawAudioFormat}
|
||||
*/
|
||||
var format = Guacamole.RawAudioFormat.parse(mimetype);
|
||||
|
||||
/**
|
||||
* An instance of a Web Audio API AudioContext object, or null if the
|
||||
* Web Audio API is not supported.
|
||||
*
|
||||
* @private
|
||||
* @type {AudioContext}
|
||||
*/
|
||||
var context = Guacamole.AudioContextFactory.getAudioContext();
|
||||
|
||||
/**
|
||||
* The earliest possible time that the next packet could play without
|
||||
* overlapping an already-playing packet, in seconds. Note that while this
|
||||
* value is in seconds, it is not an integer value and has microsecond
|
||||
* resolution.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var nextPacketTime = context.currentTime;
|
||||
|
||||
/**
|
||||
* Guacamole.ArrayBufferReader wrapped around the audio input stream
|
||||
* provided with this Guacamole.RawAudioPlayer was created.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.ArrayBufferReader}
|
||||
*/
|
||||
var reader = new Guacamole.ArrayBufferReader(stream);
|
||||
|
||||
/**
|
||||
* The minimum size of an audio packet split by splitAudioPacket(), in
|
||||
* seconds. Audio packets smaller than this will not be split, nor will the
|
||||
* split result of a larger packet ever be smaller in size than this
|
||||
* minimum.
|
||||
*
|
||||
* @private
|
||||
* @constant
|
||||
* @type {!number}
|
||||
*/
|
||||
var MIN_SPLIT_SIZE = 0.02;
|
||||
|
||||
/**
|
||||
* The maximum amount of latency to allow between the buffered data stream
|
||||
* and the playback position, in seconds. Initially, this is set to
|
||||
* roughly one third of a second.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var maxLatency = 0.3;
|
||||
|
||||
/**
|
||||
* The type of typed array that will be used to represent each audio packet
|
||||
* internally. This will be either Int8Array or Int16Array, depending on
|
||||
* whether the raw audio format is 8-bit or 16-bit.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
*/
|
||||
var SampleArray = (format.bytesPerSample === 1) ? window.Int8Array : window.Int16Array;
|
||||
|
||||
/**
|
||||
* The maximum absolute value of any sample within a raw audio packet
|
||||
* received by this audio player. This depends only on the size of each
|
||||
* sample, and will be 128 for 8-bit audio and 32768 for 16-bit audio.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var maxSampleValue = (format.bytesPerSample === 1) ? 128 : 32768;
|
||||
|
||||
/**
|
||||
* The queue of all pending audio packets, as an array of sample arrays.
|
||||
* Audio packets which are pending playback will be added to this queue for
|
||||
* further manipulation prior to scheduling via the Web Audio API. Once an
|
||||
* audio packet leaves this queue and is scheduled via the Web Audio API,
|
||||
* no further modifications can be made to that packet.
|
||||
*
|
||||
* @private
|
||||
* @type {!SampleArray[]}
|
||||
*/
|
||||
var packetQueue = [];
|
||||
|
||||
/**
|
||||
* Given an array of audio packets, returns a single audio packet
|
||||
* containing the concatenation of those packets.
|
||||
*
|
||||
* @private
|
||||
* @param {!SampleArray[]} packets
|
||||
* The array of audio packets to concatenate.
|
||||
*
|
||||
* @returns {SampleArray}
|
||||
* A single audio packet containing the concatenation of all given
|
||||
* audio packets. If no packets are provided, this will be undefined.
|
||||
*/
|
||||
var joinAudioPackets = function joinAudioPackets(packets) {
|
||||
|
||||
// Do not bother joining if one or fewer packets are in the queue
|
||||
if (packets.length <= 1)
|
||||
return packets[0];
|
||||
|
||||
// Determine total sample length of the entire queue
|
||||
var totalLength = 0;
|
||||
packets.forEach(function addPacketLengths(packet) {
|
||||
totalLength += packet.length;
|
||||
});
|
||||
|
||||
// Append each packet within queue
|
||||
var offset = 0;
|
||||
var joined = new SampleArray(totalLength);
|
||||
packets.forEach(function appendPacket(packet) {
|
||||
joined.set(packet, offset);
|
||||
offset += packet.length;
|
||||
});
|
||||
|
||||
return joined;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a single packet of audio data, splits off an arbitrary length of
|
||||
* audio data from the beginning of that packet, returning the split result
|
||||
* as an array of two packets. The split location is determined through an
|
||||
* algorithm intended to minimize the liklihood of audible clicking between
|
||||
* packets. If no such split location is possible, an array containing only
|
||||
* the originally-provided audio packet is returned.
|
||||
*
|
||||
* @private
|
||||
* @param {!SampleArray} data
|
||||
* The audio packet to split.
|
||||
*
|
||||
* @returns {!SampleArray[]}
|
||||
* An array of audio packets containing the result of splitting the
|
||||
* provided audio packet. If splitting is possible, this array will
|
||||
* contain two packets. If splitting is not possible, this array will
|
||||
* contain only the originally-provided packet.
|
||||
*/
|
||||
var splitAudioPacket = function splitAudioPacket(data) {
|
||||
|
||||
var minValue = Number.MAX_VALUE;
|
||||
var optimalSplitLength = data.length;
|
||||
|
||||
// Calculate number of whole samples in the provided audio packet AND
|
||||
// in the minimum possible split packet
|
||||
var samples = Math.floor(data.length / format.channels);
|
||||
var minSplitSamples = Math.floor(format.rate * MIN_SPLIT_SIZE);
|
||||
|
||||
// Calculate the beginning of the "end" of the audio packet
|
||||
var start = Math.max(
|
||||
format.channels * minSplitSamples,
|
||||
format.channels * (samples - minSplitSamples)
|
||||
);
|
||||
|
||||
// For all samples at the end of the given packet, find a point where
|
||||
// the perceptible volume across all channels is lowest (and thus is
|
||||
// the optimal point to split)
|
||||
for (var offset = start; offset < data.length; offset += format.channels) {
|
||||
|
||||
// Calculate the sum of all values across all channels (the result
|
||||
// will be proportional to the average volume of a sample)
|
||||
var totalValue = 0;
|
||||
for (var channel = 0; channel < format.channels; channel++) {
|
||||
totalValue += Math.abs(data[offset + channel]);
|
||||
}
|
||||
|
||||
// If this is the smallest average value thus far, set the split
|
||||
// length such that the first packet ends with the current sample
|
||||
if (totalValue <= minValue) {
|
||||
optimalSplitLength = offset + format.channels;
|
||||
minValue = totalValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If packet is not split, return the supplied packet untouched
|
||||
if (optimalSplitLength === data.length)
|
||||
return [data];
|
||||
|
||||
// Otherwise, split the packet into two new packets according to the
|
||||
// calculated optimal split length
|
||||
return [
|
||||
new SampleArray(data.buffer.slice(0, optimalSplitLength * format.bytesPerSample)),
|
||||
new SampleArray(data.buffer.slice(optimalSplitLength * format.bytesPerSample))
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Pushes the given packet of audio data onto the playback queue. Unlike
|
||||
* other private functions within Guacamole.RawAudioPlayer, the type of the
|
||||
* ArrayBuffer packet of audio data here need not be specific to the type
|
||||
* of audio (as with SampleArray). The ArrayBuffer type provided by a
|
||||
* Guacamole.ArrayBufferReader, for example, is sufficient. Any necessary
|
||||
* conversions will be performed automatically internally.
|
||||
*
|
||||
* @private
|
||||
* @param {!ArrayBuffer} data
|
||||
* A raw packet of audio data that should be pushed onto the audio
|
||||
* playback queue.
|
||||
*/
|
||||
var pushAudioPacket = function pushAudioPacket(data) {
|
||||
packetQueue.push(new SampleArray(data));
|
||||
};
|
||||
|
||||
/**
|
||||
* Shifts off and returns a packet of audio data from the beginning of the
|
||||
* playback queue. The length of this audio packet is determined
|
||||
* dynamically according to the click-reduction algorithm implemented by
|
||||
* splitAudioPacket().
|
||||
*
|
||||
* @private
|
||||
* @returns {SampleArray}
|
||||
* A packet of audio data pulled from the beginning of the playback
|
||||
* queue. If there is no audio currently in the playback queue, this
|
||||
* will be null.
|
||||
*/
|
||||
var shiftAudioPacket = function shiftAudioPacket() {
|
||||
|
||||
// Flatten data in packet queue
|
||||
var data = joinAudioPackets(packetQueue);
|
||||
if (!data)
|
||||
return null;
|
||||
|
||||
// Pull an appropriate amount of data from the front of the queue
|
||||
packetQueue = splitAudioPacket(data);
|
||||
data = packetQueue.shift();
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the given audio packet into an AudioBuffer, ready for playback
|
||||
* by the Web Audio API. Unlike the raw audio packets received by this
|
||||
* audio player, AudioBuffers require floating point samples and are split
|
||||
* into isolated planes of channel-specific data.
|
||||
*
|
||||
* @private
|
||||
* @param {!SampleArray} data
|
||||
* The raw audio packet that should be converted into a Web Audio API
|
||||
* AudioBuffer.
|
||||
*
|
||||
* @returns {!AudioBuffer}
|
||||
* A new Web Audio API AudioBuffer containing the provided audio data,
|
||||
* converted to the format used by the Web Audio API.
|
||||
*/
|
||||
var toAudioBuffer = function toAudioBuffer(data) {
|
||||
|
||||
// Calculate total number of samples
|
||||
var samples = data.length / format.channels;
|
||||
|
||||
// Determine exactly when packet CAN play
|
||||
var packetTime = context.currentTime;
|
||||
if (nextPacketTime < packetTime)
|
||||
nextPacketTime = packetTime;
|
||||
|
||||
// Get audio buffer for specified format
|
||||
var audioBuffer = context.createBuffer(format.channels, samples, format.rate);
|
||||
|
||||
// Convert each channel
|
||||
for (var channel = 0; channel < format.channels; channel++) {
|
||||
|
||||
var audioData = audioBuffer.getChannelData(channel);
|
||||
|
||||
// Fill audio buffer with data for channel
|
||||
var offset = channel;
|
||||
for (var i = 0; i < samples; i++) {
|
||||
audioData[i] = data[offset] / maxSampleValue;
|
||||
offset += format.channels;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return audioBuffer;
|
||||
|
||||
};
|
||||
|
||||
// Defer playback of received audio packets slightly
|
||||
reader.ondata = function playReceivedAudio(data) {
|
||||
|
||||
// Push received samples onto queue
|
||||
pushAudioPacket(new SampleArray(data));
|
||||
|
||||
// Shift off an arbitrary packet of audio data from the queue (this may
|
||||
// be different in size from the packet just pushed)
|
||||
var packet = shiftAudioPacket();
|
||||
if (!packet)
|
||||
return;
|
||||
|
||||
// Determine exactly when packet CAN play
|
||||
var packetTime = context.currentTime;
|
||||
if (nextPacketTime < packetTime)
|
||||
nextPacketTime = packetTime;
|
||||
|
||||
// Set up buffer source
|
||||
var source = context.createBufferSource();
|
||||
source.connect(context.destination);
|
||||
|
||||
// Use noteOn() instead of start() if necessary
|
||||
if (!source.start)
|
||||
source.start = source.noteOn;
|
||||
|
||||
// Schedule packet
|
||||
source.buffer = toAudioBuffer(packet);
|
||||
source.start(nextPacketTime);
|
||||
|
||||
// Update timeline by duration of scheduled packet
|
||||
nextPacketTime += packet.length / format.channels / format.rate;
|
||||
|
||||
};
|
||||
|
||||
/** @override */
|
||||
this.sync = function sync() {
|
||||
|
||||
// Calculate elapsed time since last sync
|
||||
var now = context.currentTime;
|
||||
|
||||
// Reschedule future playback time such that playback latency is
|
||||
// bounded within a reasonable latency threshold
|
||||
nextPacketTime = Math.min(nextPacketTime, now + maxLatency);
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
Guacamole.RawAudioPlayer.prototype = new Guacamole.AudioPlayer();
|
||||
|
||||
/**
|
||||
* Determines whether the given mimetype is supported by
|
||||
* Guacamole.RawAudioPlayer.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype to check.
|
||||
*
|
||||
* @returns {!boolean}
|
||||
* true if the given mimetype is supported by Guacamole.RawAudioPlayer,
|
||||
* false otherwise.
|
||||
*/
|
||||
Guacamole.RawAudioPlayer.isSupportedType = function isSupportedType(mimetype) {
|
||||
|
||||
// No supported types if no Web Audio API
|
||||
if (!Guacamole.AudioContextFactory.getAudioContext())
|
||||
return false;
|
||||
|
||||
return Guacamole.RawAudioFormat.parse(mimetype) !== null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of all mimetypes supported by Guacamole.RawAudioPlayer. Only
|
||||
* the core mimetypes themselves will be listed. Any mimetype parameters, even
|
||||
* required ones, will not be included in the list. For example, "audio/L8" is
|
||||
* a raw audio mimetype that may be supported, but it is invalid without
|
||||
* additional parameters. Something like "audio/L8;rate=44100" would be valid,
|
||||
* however (see https://tools.ietf.org/html/rfc4856).
|
||||
*
|
||||
* @returns {!string[]}
|
||||
* A list of all mimetypes supported by Guacamole.RawAudioPlayer, excluding
|
||||
* any parameters. If the necessary JavaScript APIs for playing raw audio
|
||||
* are absent, this list will be empty.
|
||||
*/
|
||||
Guacamole.RawAudioPlayer.getSupportedTypes = function getSupportedTypes() {
|
||||
|
||||
// No supported types if no Web Audio API
|
||||
if (!Guacamole.AudioContextFactory.getAudioContext())
|
||||
return [];
|
||||
|
||||
// We support 8-bit and 16-bit raw PCM
|
||||
return [
|
||||
'audio/L8',
|
||||
'audio/L16'
|
||||
];
|
||||
|
||||
};
|
||||
604
guacamole-common-js/src/main/webapp/modules/AudioRecorder.js
Normal file
604
guacamole-common-js/src/main/webapp/modules/AudioRecorder.js
Normal file
@@ -0,0 +1,604 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Abstract audio recorder which streams arbitrary audio data to an underlying
|
||||
* Guacamole.OutputStream. It is up to implementations of this class to provide
|
||||
* some means of handling this Guacamole.OutputStream. Data produced by the
|
||||
* recorder is to be sent along the provided stream immediately.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
Guacamole.AudioRecorder = function AudioRecorder() {
|
||||
|
||||
/**
|
||||
* Callback which is invoked when the audio recording process has stopped
|
||||
* and the underlying Guacamole stream has been closed normally. Audio will
|
||||
* only resume recording if a new Guacamole.AudioRecorder is started. This
|
||||
* Guacamole.AudioRecorder instance MAY NOT be reused.
|
||||
*
|
||||
* @event
|
||||
*/
|
||||
this.onclose = null;
|
||||
|
||||
/**
|
||||
* Callback which is invoked when the audio recording process cannot
|
||||
* continue due to an error, if it has started at all. The underlying
|
||||
* Guacamole stream is automatically closed. Future attempts to record
|
||||
* audio should not be made, and this Guacamole.AudioRecorder instance
|
||||
* MAY NOT be reused.
|
||||
*
|
||||
* @event
|
||||
*/
|
||||
this.onerror = null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether the given mimetype is supported by any built-in
|
||||
* implementation of Guacamole.AudioRecorder, and thus will be properly handled
|
||||
* by Guacamole.AudioRecorder.getInstance().
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype to check.
|
||||
*
|
||||
* @returns {!boolean}
|
||||
* true if the given mimetype is supported by any built-in
|
||||
* Guacamole.AudioRecorder, false otherwise.
|
||||
*/
|
||||
Guacamole.AudioRecorder.isSupportedType = function isSupportedType(mimetype) {
|
||||
|
||||
return Guacamole.RawAudioRecorder.isSupportedType(mimetype);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of all mimetypes supported by any built-in
|
||||
* Guacamole.AudioRecorder, in rough order of priority. Beware that only the
|
||||
* core mimetypes themselves will be listed. Any mimetype parameters, even
|
||||
* required ones, will not be included in the list. For example, "audio/L8" is
|
||||
* a supported raw audio mimetype that is supported, but it is invalid without
|
||||
* additional parameters. Something like "audio/L8;rate=44100" would be valid,
|
||||
* however (see https://tools.ietf.org/html/rfc4856).
|
||||
*
|
||||
* @returns {!string[]}
|
||||
* A list of all mimetypes supported by any built-in
|
||||
* Guacamole.AudioRecorder, excluding any parameters.
|
||||
*/
|
||||
Guacamole.AudioRecorder.getSupportedTypes = function getSupportedTypes() {
|
||||
|
||||
return Guacamole.RawAudioRecorder.getSupportedTypes();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an instance of Guacamole.AudioRecorder providing support for the
|
||||
* given audio format. If support for the given audio format is not available,
|
||||
* null is returned.
|
||||
*
|
||||
* @param {!Guacamole.OutputStream} stream
|
||||
* The Guacamole.OutputStream to send audio data through.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype of the audio data to be sent along the provided stream.
|
||||
*
|
||||
* @return {Guacamole.AudioRecorder}
|
||||
* A Guacamole.AudioRecorder instance supporting the given mimetype and
|
||||
* writing to the given stream, or null if support for the given mimetype
|
||||
* is absent.
|
||||
*/
|
||||
Guacamole.AudioRecorder.getInstance = function getInstance(stream, mimetype) {
|
||||
|
||||
// Use raw audio recorder if possible
|
||||
if (Guacamole.RawAudioRecorder.isSupportedType(mimetype))
|
||||
return new Guacamole.RawAudioRecorder(stream, mimetype);
|
||||
|
||||
// No support for given mimetype
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of Guacamole.AudioRecorder providing support for raw PCM
|
||||
* format audio. This recorder relies only on the Web Audio API and does not
|
||||
* require any browser-level support for its audio formats.
|
||||
*
|
||||
* @constructor
|
||||
* @augments Guacamole.AudioRecorder
|
||||
* @param {!Guacamole.OutputStream} stream
|
||||
* The Guacamole.OutputStream to write audio data to.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype of the audio data to send along the provided stream, which
|
||||
* must be a "audio/L8" or "audio/L16" mimetype with necessary parameters,
|
||||
* such as: "audio/L16;rate=44100,channels=2".
|
||||
*/
|
||||
Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
||||
|
||||
/**
|
||||
* Reference to this RawAudioRecorder.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.RawAudioRecorder}
|
||||
*/
|
||||
var recorder = this;
|
||||
|
||||
/**
|
||||
* The size of audio buffer to request from the Web Audio API when
|
||||
* recording or processing audio, in sample-frames. This must be a power of
|
||||
* two between 256 and 16384 inclusive, as required by
|
||||
* AudioContext.createScriptProcessor().
|
||||
*
|
||||
* @private
|
||||
* @constant
|
||||
* @type {!number}
|
||||
*/
|
||||
var BUFFER_SIZE = 2048;
|
||||
|
||||
/**
|
||||
* The window size to use when applying Lanczos interpolation, commonly
|
||||
* denoted by the variable "a".
|
||||
* See: https://en.wikipedia.org/wiki/Lanczos_resampling
|
||||
*
|
||||
* @private
|
||||
* @contant
|
||||
* @type {!number}
|
||||
*/
|
||||
var LANCZOS_WINDOW_SIZE = 3;
|
||||
|
||||
/**
|
||||
* The format of audio this recorder will encode.
|
||||
*
|
||||
* @private
|
||||
* @type {Guacamole.RawAudioFormat}
|
||||
*/
|
||||
var format = Guacamole.RawAudioFormat.parse(mimetype);
|
||||
|
||||
/**
|
||||
* An instance of a Web Audio API AudioContext object, or null if the
|
||||
* Web Audio API is not supported.
|
||||
*
|
||||
* @private
|
||||
* @type {AudioContext}
|
||||
*/
|
||||
var context = Guacamole.AudioContextFactory.getAudioContext();
|
||||
|
||||
// Some browsers do not implement navigator.mediaDevices - this
|
||||
// shims in this functionality to ensure code compatibility.
|
||||
if (!navigator.mediaDevices)
|
||||
navigator.mediaDevices = {};
|
||||
|
||||
// Browsers that either do not implement navigator.mediaDevices
|
||||
// at all or do not implement it completely need the getUserMedia
|
||||
// method defined. This shims in this function by detecting
|
||||
// one of the supported legacy methods.
|
||||
if (!navigator.mediaDevices.getUserMedia)
|
||||
navigator.mediaDevices.getUserMedia = (navigator.getUserMedia
|
||||
|| navigator.webkitGetUserMedia
|
||||
|| navigator.mozGetUserMedia
|
||||
|| navigator.msGetUserMedia).bind(navigator);
|
||||
|
||||
/**
|
||||
* Guacamole.ArrayBufferWriter wrapped around the audio output stream
|
||||
* provided when this Guacamole.RawAudioRecorder was created.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.ArrayBufferWriter}
|
||||
*/
|
||||
var writer = new Guacamole.ArrayBufferWriter(stream);
|
||||
|
||||
/**
|
||||
* The type of typed array that will be used to represent each audio packet
|
||||
* internally. This will be either Int8Array or Int16Array, depending on
|
||||
* whether the raw audio format is 8-bit or 16-bit.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
*/
|
||||
var SampleArray = (format.bytesPerSample === 1) ? window.Int8Array : window.Int16Array;
|
||||
|
||||
/**
|
||||
* The maximum absolute value of any sample within a raw audio packet sent
|
||||
* by this audio recorder. This depends only on the size of each sample,
|
||||
* and will be 128 for 8-bit audio and 32768 for 16-bit audio.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var maxSampleValue = (format.bytesPerSample === 1) ? 128 : 32768;
|
||||
|
||||
/**
|
||||
* The total number of audio samples read from the local audio input device
|
||||
* over the life of this audio recorder.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var readSamples = 0;
|
||||
|
||||
/**
|
||||
* The total number of audio samples written to the underlying Guacamole
|
||||
* connection over the life of this audio recorder.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var writtenSamples = 0;
|
||||
|
||||
/**
|
||||
* The audio stream provided by the browser, if allowed. If no stream has
|
||||
* yet been received, this will be null.
|
||||
*
|
||||
* @private
|
||||
* @type {MediaStream}
|
||||
*/
|
||||
var mediaStream = null;
|
||||
|
||||
/**
|
||||
* The source node providing access to the local audio input device.
|
||||
*
|
||||
* @private
|
||||
* @type {MediaStreamAudioSourceNode}
|
||||
*/
|
||||
var source = null;
|
||||
|
||||
/**
|
||||
* The script processing node which receives audio input from the media
|
||||
* stream source node as individual audio buffers.
|
||||
*
|
||||
* @private
|
||||
* @type {ScriptProcessorNode}
|
||||
*/
|
||||
var processor = null;
|
||||
|
||||
/**
|
||||
* The normalized sinc function. The normalized sinc function is defined as
|
||||
* 1 for x=0 and sin(PI * x) / (PI * x) for all other values of x.
|
||||
*
|
||||
* See: https://en.wikipedia.org/wiki/Sinc_function
|
||||
*
|
||||
* @private
|
||||
* @param {!number} x
|
||||
* The point at which the normalized sinc function should be computed.
|
||||
*
|
||||
* @returns {!number}
|
||||
* The value of the normalized sinc function at x.
|
||||
*/
|
||||
var sinc = function sinc(x) {
|
||||
|
||||
// The value of sinc(0) is defined as 1
|
||||
if (x === 0)
|
||||
return 1;
|
||||
|
||||
// Otherwise, normlized sinc(x) is sin(PI * x) / (PI * x)
|
||||
var piX = Math.PI * x;
|
||||
return Math.sin(piX) / piX;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the value of the Lanczos kernal at point x for a given window
|
||||
* size. See: https://en.wikipedia.org/wiki/Lanczos_resampling
|
||||
*
|
||||
* @private
|
||||
* @param {!number} x
|
||||
* The point at which the value of the Lanczos kernel should be
|
||||
* computed.
|
||||
*
|
||||
* @param {!number} a
|
||||
* The window size to use for the Lanczos kernel.
|
||||
*
|
||||
* @returns {!number}
|
||||
* The value of the Lanczos kernel at the given point for the given
|
||||
* window size.
|
||||
*/
|
||||
var lanczos = function lanczos(x, a) {
|
||||
|
||||
// Lanczos is sinc(x) * sinc(x / a) for -a < x < a ...
|
||||
if (-a < x && x < a)
|
||||
return sinc(x) * sinc(x / a);
|
||||
|
||||
// ... and 0 otherwise
|
||||
return 0;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines the value of the waveform represented by the audio data at
|
||||
* the given location. If the value cannot be determined exactly as it does
|
||||
* not correspond to an exact sample within the audio data, the value will
|
||||
* be derived through interpolating nearby samples.
|
||||
*
|
||||
* @private
|
||||
* @param {!Float32Array} audioData
|
||||
* An array of audio data, as returned by AudioBuffer.getChannelData().
|
||||
*
|
||||
* @param {!number} t
|
||||
* The relative location within the waveform from which the value
|
||||
* should be retrieved, represented as a floating point number between
|
||||
* 0 and 1 inclusive, where 0 represents the earliest point in time and
|
||||
* 1 represents the latest.
|
||||
*
|
||||
* @returns {!number}
|
||||
* The value of the waveform at the given location.
|
||||
*/
|
||||
var interpolateSample = function getValueAt(audioData, t) {
|
||||
|
||||
// Convert [0, 1] range to [0, audioData.length - 1]
|
||||
var index = (audioData.length - 1) * t;
|
||||
|
||||
// Determine the start and end points for the summation used by the
|
||||
// Lanczos interpolation algorithm (see: https://en.wikipedia.org/wiki/Lanczos_resampling)
|
||||
var start = Math.floor(index) - LANCZOS_WINDOW_SIZE + 1;
|
||||
var end = Math.floor(index) + LANCZOS_WINDOW_SIZE;
|
||||
|
||||
// Calculate the value of the Lanczos interpolation function for the
|
||||
// required range
|
||||
var sum = 0;
|
||||
for (var i = start; i <= end; i++) {
|
||||
sum += (audioData[i] || 0) * lanczos(index - i, LANCZOS_WINDOW_SIZE);
|
||||
}
|
||||
|
||||
return sum;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the given AudioBuffer into an audio packet, ready for streaming
|
||||
* along the underlying output stream. Unlike the raw audio packets used by
|
||||
* this audio recorder, AudioBuffers require floating point samples and are
|
||||
* split into isolated planes of channel-specific data.
|
||||
*
|
||||
* @private
|
||||
* @param {!AudioBuffer} audioBuffer
|
||||
* The Web Audio API AudioBuffer that should be converted to a raw
|
||||
* audio packet.
|
||||
*
|
||||
* @returns {!SampleArray}
|
||||
* A new raw audio packet containing the audio data from the provided
|
||||
* AudioBuffer.
|
||||
*/
|
||||
var toSampleArray = function toSampleArray(audioBuffer) {
|
||||
|
||||
// Track overall amount of data read
|
||||
var inSamples = audioBuffer.length;
|
||||
readSamples += inSamples;
|
||||
|
||||
// Calculate the total number of samples that should be written as of
|
||||
// the audio data just received and adjust the size of the output
|
||||
// packet accordingly
|
||||
var expectedWrittenSamples = Math.round(readSamples * format.rate / audioBuffer.sampleRate);
|
||||
var outSamples = expectedWrittenSamples - writtenSamples;
|
||||
|
||||
// Update number of samples written
|
||||
writtenSamples += outSamples;
|
||||
|
||||
// Get array for raw PCM storage
|
||||
var data = new SampleArray(outSamples * format.channels);
|
||||
|
||||
// Convert each channel
|
||||
for (var channel = 0; channel < format.channels; channel++) {
|
||||
|
||||
var audioData = audioBuffer.getChannelData(channel);
|
||||
|
||||
// Fill array with data from audio buffer channel
|
||||
var offset = channel;
|
||||
for (var i = 0; i < outSamples; i++) {
|
||||
data[offset] = interpolateSample(audioData, i / (outSamples - 1)) * maxSampleValue;
|
||||
offset += format.channels;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* getUserMedia() callback which handles successful retrieval of an
|
||||
* audio stream (successful start of recording).
|
||||
*
|
||||
* @private
|
||||
* @param {!MediaStream} stream
|
||||
* A MediaStream which provides access to audio data read from the
|
||||
* user's local audio input device.
|
||||
*/
|
||||
var streamReceived = function streamReceived(stream) {
|
||||
|
||||
// Create processing node which receives appropriately-sized audio buffers
|
||||
processor = context.createScriptProcessor(BUFFER_SIZE, format.channels, format.channels);
|
||||
processor.connect(context.destination);
|
||||
|
||||
// Send blobs when audio buffers are received
|
||||
processor.onaudioprocess = function processAudio(e) {
|
||||
writer.sendData(toSampleArray(e.inputBuffer).buffer);
|
||||
};
|
||||
|
||||
// Connect processing node to user's audio input source
|
||||
source = context.createMediaStreamSource(stream);
|
||||
source.connect(processor);
|
||||
|
||||
// Attempt to explicitly resume AudioContext, as it may be paused
|
||||
// by default
|
||||
if (context.state === 'suspended')
|
||||
context.resume();
|
||||
|
||||
// Save stream for later cleanup
|
||||
mediaStream = stream;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* getUserMedia() callback which handles audio recording denial. The
|
||||
* underlying Guacamole output stream is closed, and the failure to
|
||||
* record is noted using onerror.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var streamDenied = function streamDenied() {
|
||||
|
||||
// Simply end stream if audio access is not allowed
|
||||
writer.sendEnd();
|
||||
|
||||
// Notify of closure
|
||||
if (recorder.onerror)
|
||||
recorder.onerror();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests access to the user's microphone and begins capturing audio. All
|
||||
* received audio data is resampled as necessary and forwarded to the
|
||||
* Guacamole stream underlying this Guacamole.RawAudioRecorder. This
|
||||
* function must be invoked ONLY ONCE per instance of
|
||||
* Guacamole.RawAudioRecorder.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var beginAudioCapture = function beginAudioCapture() {
|
||||
|
||||
// Attempt to retrieve an audio input stream from the browser
|
||||
var promise = navigator.mediaDevices.getUserMedia({
|
||||
'audio' : true
|
||||
}, streamReceived, streamDenied);
|
||||
|
||||
// Handle stream creation/rejection via Promise for newer versions of
|
||||
// getUserMedia()
|
||||
if (promise && promise.then)
|
||||
promise.then(streamReceived, streamDenied);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops capturing audio, if the capture has started, freeing all associated
|
||||
* resources. If the capture has not started, this function simply ends the
|
||||
* underlying Guacamole stream.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var stopAudioCapture = function stopAudioCapture() {
|
||||
|
||||
// Disconnect media source node from script processor
|
||||
if (source)
|
||||
source.disconnect();
|
||||
|
||||
// Disconnect associated script processor node
|
||||
if (processor)
|
||||
processor.disconnect();
|
||||
|
||||
// Stop capture
|
||||
if (mediaStream) {
|
||||
var tracks = mediaStream.getTracks();
|
||||
for (var i = 0; i < tracks.length; i++)
|
||||
tracks[i].stop();
|
||||
}
|
||||
|
||||
// Remove references to now-unneeded components
|
||||
processor = null;
|
||||
source = null;
|
||||
mediaStream = null;
|
||||
|
||||
// End stream
|
||||
writer.sendEnd();
|
||||
|
||||
};
|
||||
|
||||
// Once audio stream is successfully open, request and begin reading audio
|
||||
writer.onack = function audioStreamAcknowledged(status) {
|
||||
|
||||
// Begin capture if successful response and not yet started
|
||||
if (status.code === Guacamole.Status.Code.SUCCESS && !mediaStream)
|
||||
beginAudioCapture();
|
||||
|
||||
// Otherwise stop capture and cease handling any further acks
|
||||
else {
|
||||
|
||||
// Stop capturing audio
|
||||
stopAudioCapture();
|
||||
writer.onack = null;
|
||||
|
||||
// Notify if stream has closed normally
|
||||
if (status.code === Guacamole.Status.Code.RESOURCE_CLOSED) {
|
||||
if (recorder.onclose)
|
||||
recorder.onclose();
|
||||
}
|
||||
|
||||
// Otherwise notify of closure due to error
|
||||
else {
|
||||
if (recorder.onerror)
|
||||
recorder.onerror();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
Guacamole.RawAudioRecorder.prototype = new Guacamole.AudioRecorder();
|
||||
|
||||
/**
|
||||
* Determines whether the given mimetype is supported by
|
||||
* Guacamole.RawAudioRecorder.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype to check.
|
||||
*
|
||||
* @returns {!boolean}
|
||||
* true if the given mimetype is supported by Guacamole.RawAudioRecorder,
|
||||
* false otherwise.
|
||||
*/
|
||||
Guacamole.RawAudioRecorder.isSupportedType = function isSupportedType(mimetype) {
|
||||
|
||||
// No supported types if no Web Audio API
|
||||
if (!Guacamole.AudioContextFactory.getAudioContext())
|
||||
return false;
|
||||
|
||||
return Guacamole.RawAudioFormat.parse(mimetype) !== null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of all mimetypes supported by Guacamole.RawAudioRecorder. Only
|
||||
* the core mimetypes themselves will be listed. Any mimetype parameters, even
|
||||
* required ones, will not be included in the list. For example, "audio/L8" is
|
||||
* a raw audio mimetype that may be supported, but it is invalid without
|
||||
* additional parameters. Something like "audio/L8;rate=44100" would be valid,
|
||||
* however (see https://tools.ietf.org/html/rfc4856).
|
||||
*
|
||||
* @returns {!string[]}
|
||||
* A list of all mimetypes supported by Guacamole.RawAudioRecorder,
|
||||
* excluding any parameters. If the necessary JavaScript APIs for recording
|
||||
* raw audio are absent, this list will be empty.
|
||||
*/
|
||||
Guacamole.RawAudioRecorder.getSupportedTypes = function getSupportedTypes() {
|
||||
|
||||
// No supported types if no Web Audio API
|
||||
if (!Guacamole.AudioContextFactory.getAudioContext())
|
||||
return [];
|
||||
|
||||
// We support 8-bit and 16-bit raw PCM
|
||||
return [
|
||||
'audio/L8',
|
||||
'audio/L16'
|
||||
];
|
||||
|
||||
};
|
||||
139
guacamole-common-js/src/main/webapp/modules/BlobReader.js
Normal file
139
guacamole-common-js/src/main/webapp/modules/BlobReader.js
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A reader which automatically handles the given input stream, assembling all
|
||||
* received blobs into a single blob by appending them to each other in order.
|
||||
* Note that this object will overwrite any installed event handlers on the
|
||||
* given Guacamole.InputStream.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.InputStream} stream
|
||||
* The stream that data will be read from.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype of the blob being built.
|
||||
*/
|
||||
Guacamole.BlobReader = function(stream, mimetype) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.InputStream.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.BlobReader}
|
||||
*/
|
||||
var guac_reader = this;
|
||||
|
||||
/**
|
||||
* The length of this Guacamole.InputStream in bytes.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var length = 0;
|
||||
|
||||
// Get blob builder
|
||||
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 = [];
|
||||
|
||||
/** @ignore */
|
||||
this.append = function(data) {
|
||||
blobs.push(new Blob([data], {"type": mimetype}));
|
||||
};
|
||||
|
||||
/** @ignore */
|
||||
this.getBlob = function() {
|
||||
return new Blob(blobs, {"type": mimetype});
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
// Append received blobs
|
||||
stream.onblob = function(data) {
|
||||
|
||||
// Convert to ArrayBuffer
|
||||
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;
|
||||
|
||||
// Call handler, if present
|
||||
if (guac_reader.onprogress)
|
||||
guac_reader.onprogress(arrayBuffer.byteLength);
|
||||
|
||||
// Send success response
|
||||
stream.sendAck("OK", 0x0000);
|
||||
|
||||
};
|
||||
|
||||
// Simply call onend when end received
|
||||
stream.onend = function() {
|
||||
if (guac_reader.onend)
|
||||
guac_reader.onend();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current length of this Guacamole.InputStream, in bytes.
|
||||
*
|
||||
* @return {!number}
|
||||
* The current length of this Guacamole.InputStream.
|
||||
*/
|
||||
this.getLength = function() {
|
||||
return length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the contents of this Guacamole.BlobReader as a Blob.
|
||||
*
|
||||
* @return {!Blob}
|
||||
* The contents of this Guacamole.BlobReader.
|
||||
*/
|
||||
this.getBlob = function() {
|
||||
return blob_builder.getBlob();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired once for every blob of data received.
|
||||
*
|
||||
* @event
|
||||
* @param {!number} length
|
||||
* The number of bytes received.
|
||||
*/
|
||||
this.onprogress = null;
|
||||
|
||||
/**
|
||||
* Fired once this stream is finished and no further data will be written.
|
||||
* @event
|
||||
*/
|
||||
this.onend = null;
|
||||
|
||||
};
|
||||
245
guacamole-common-js/src/main/webapp/modules/BlobWriter.js
Normal file
245
guacamole-common-js/src/main/webapp/modules/BlobWriter.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A writer which automatically writes to the given output stream with the
|
||||
* contents of provided Blob objects.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.OutputStream} stream
|
||||
* The stream that data will be written to.
|
||||
*/
|
||||
Guacamole.BlobWriter = function BlobWriter(stream) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.BlobWriter.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.BlobWriter}
|
||||
*/
|
||||
var guacWriter = this;
|
||||
|
||||
/**
|
||||
* Wrapped Guacamole.ArrayBufferWriter which will be used to send any
|
||||
* provided file data.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.ArrayBufferWriter}
|
||||
*/
|
||||
var arrayBufferWriter = new Guacamole.ArrayBufferWriter(stream);
|
||||
|
||||
// Initially, simply call onack for acknowledgements
|
||||
arrayBufferWriter.onack = function(status) {
|
||||
if (guacWriter.onack)
|
||||
guacWriter.onack(status);
|
||||
};
|
||||
|
||||
/**
|
||||
* Browser-independent implementation of Blob.slice() which uses an end
|
||||
* offset to determine the span of the resulting slice, rather than a
|
||||
* length.
|
||||
*
|
||||
* @private
|
||||
* @param {!Blob} blob
|
||||
* The Blob to slice.
|
||||
*
|
||||
* @param {!number} start
|
||||
* The starting offset of the slice, in bytes, inclusive.
|
||||
*
|
||||
* @param {!number} end
|
||||
* The ending offset of the slice, in bytes, exclusive.
|
||||
*
|
||||
* @returns {!Blob}
|
||||
* A Blob containing the data within the given Blob starting at
|
||||
* <code>start</code> and ending at <code>end - 1</code>.
|
||||
*/
|
||||
var slice = function slice(blob, start, end) {
|
||||
|
||||
// Use prefixed implementations if necessary
|
||||
var sliceImplementation = (
|
||||
blob.slice
|
||||
|| blob.webkitSlice
|
||||
|| blob.mozSlice
|
||||
).bind(blob);
|
||||
|
||||
var length = end - start;
|
||||
|
||||
// The old Blob.slice() was length-based (not end-based). Try the
|
||||
// length version first, if the two calls are not equivalent.
|
||||
if (length !== end) {
|
||||
|
||||
// If the result of the slice() call matches the expected length,
|
||||
// trust that result. It must be correct.
|
||||
var sliceResult = sliceImplementation(start, length);
|
||||
if (sliceResult.size === length)
|
||||
return sliceResult;
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, use the most-recent standard: end-based slice()
|
||||
return sliceImplementation(start, end);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends the contents of the given blob over the underlying stream.
|
||||
*
|
||||
* @param {!Blob} blob
|
||||
* The blob to send.
|
||||
*/
|
||||
this.sendBlob = function sendBlob(blob) {
|
||||
|
||||
var offset = 0;
|
||||
var reader = new FileReader();
|
||||
|
||||
/**
|
||||
* Reads the next chunk of the blob provided to
|
||||
* [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. The chunk itself
|
||||
* is read asynchronously, and will not be available until
|
||||
* reader.onload fires.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var readNextChunk = function readNextChunk() {
|
||||
|
||||
// If no further chunks remain, inform of completion and stop
|
||||
if (offset >= blob.size) {
|
||||
|
||||
// Fire completion event for completed blob
|
||||
if (guacWriter.oncomplete)
|
||||
guacWriter.oncomplete(blob);
|
||||
|
||||
// No further chunks to read
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Obtain reference to next chunk as a new blob
|
||||
var chunk = slice(blob, offset, offset + arrayBufferWriter.blobLength);
|
||||
offset += arrayBufferWriter.blobLength;
|
||||
|
||||
// Attempt to read the blob contents represented by the blob into
|
||||
// a new array buffer
|
||||
reader.readAsArrayBuffer(chunk);
|
||||
|
||||
};
|
||||
|
||||
// Send each chunk over the stream, continue reading the next chunk
|
||||
reader.onload = function chunkLoadComplete() {
|
||||
|
||||
// Send the successfully-read chunk
|
||||
arrayBufferWriter.sendData(reader.result);
|
||||
|
||||
// Continue sending more chunks after the latest chunk is
|
||||
// acknowledged
|
||||
arrayBufferWriter.onack = function sendMoreChunks(status) {
|
||||
|
||||
if (guacWriter.onack)
|
||||
guacWriter.onack(status);
|
||||
|
||||
// Abort transfer if an error occurs
|
||||
if (status.isError())
|
||||
return;
|
||||
|
||||
// Inform of blob upload progress via progress events
|
||||
if (guacWriter.onprogress)
|
||||
guacWriter.onprogress(blob, offset - arrayBufferWriter.blobLength);
|
||||
|
||||
// Queue the next chunk for reading
|
||||
readNextChunk();
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// If an error prevents further reading, inform of error and stop
|
||||
reader.onerror = function chunkLoadFailed() {
|
||||
|
||||
// Fire error event, including the context of the error
|
||||
if (guacWriter.onerror)
|
||||
guacWriter.onerror(blob, offset, reader.error);
|
||||
|
||||
};
|
||||
|
||||
// Begin reading the first chunk
|
||||
readNextChunk();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Signals that no further text will be sent, effectively closing the
|
||||
* stream.
|
||||
*/
|
||||
this.sendEnd = function sendEnd() {
|
||||
arrayBufferWriter.sendEnd();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired for received data, if acknowledged by the server.
|
||||
*
|
||||
* @event
|
||||
* @param {!Guacamole.Status} status
|
||||
* The status of the operation.
|
||||
*/
|
||||
this.onack = null;
|
||||
|
||||
/**
|
||||
* Fired when an error occurs reading a blob passed to
|
||||
* [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. The transfer for the
|
||||
* the given blob will cease, but the stream will remain open.
|
||||
*
|
||||
* @event
|
||||
* @param {!Blob} blob
|
||||
* The blob that was being read when the error occurred.
|
||||
*
|
||||
* @param {!number} offset
|
||||
* The offset of the failed read attempt within the blob, in bytes.
|
||||
*
|
||||
* @param {!DOMError} error
|
||||
* The error that occurred.
|
||||
*/
|
||||
this.onerror = null;
|
||||
|
||||
/**
|
||||
* Fired for each successfully-read chunk of data as a blob is being sent
|
||||
* via [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}.
|
||||
*
|
||||
* @event
|
||||
* @param {!Blob} blob
|
||||
* The blob that is being read.
|
||||
*
|
||||
* @param {!number} offset
|
||||
* The offset of the read that just succeeded.
|
||||
*/
|
||||
this.onprogress = null;
|
||||
|
||||
/**
|
||||
* Fired when a blob passed to
|
||||
* [sendBlob()]{@link Guacamole.BlobWriter#sendBlob} has finished being
|
||||
* sent.
|
||||
*
|
||||
* @event
|
||||
* @param {!Blob} blob
|
||||
* The blob that was sent.
|
||||
*/
|
||||
this.oncomplete = null;
|
||||
|
||||
};
|
||||
2081
guacamole-common-js/src/main/webapp/modules/Client.js
Normal file
2081
guacamole-common-js/src/main/webapp/modules/Client.js
Normal file
File diff suppressed because it is too large
Load Diff
89
guacamole-common-js/src/main/webapp/modules/DataURIReader.js
Normal file
89
guacamole-common-js/src/main/webapp/modules/DataURIReader.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A reader which automatically handles the given input stream, returning
|
||||
* received blobs as a single data URI built over the course of the stream.
|
||||
* Note that this object will overwrite any installed event handlers on the
|
||||
* given Guacamole.InputStream.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.InputStream} stream
|
||||
* The stream that data will be read from.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype of the data being received.
|
||||
*/
|
||||
Guacamole.DataURIReader = function(stream, mimetype) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.DataURIReader.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.DataURIReader}
|
||||
*/
|
||||
var guac_reader = this;
|
||||
|
||||
/**
|
||||
* Current data URI.
|
||||
*
|
||||
* @private
|
||||
* @type {!string}
|
||||
*/
|
||||
var uri = 'data:' + mimetype + ';base64,';
|
||||
|
||||
// Receive blobs as array buffers
|
||||
stream.onblob = function dataURIReaderBlob(data) {
|
||||
|
||||
// Currently assuming data will ALWAYS be safe to simply append. This
|
||||
// will not be true if the received base64 data encodes a number of
|
||||
// bytes that isn't a multiple of three (as base64 expands in a ratio
|
||||
// of exactly 3:4).
|
||||
uri += data;
|
||||
|
||||
};
|
||||
|
||||
// Simply call onend when end received
|
||||
stream.onend = function dataURIReaderEnd() {
|
||||
if (guac_reader.onend)
|
||||
guac_reader.onend();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the data URI of all data received through the underlying stream
|
||||
* thus far.
|
||||
*
|
||||
* @returns {!string}
|
||||
* The data URI of all data received through the underlying stream thus
|
||||
* far.
|
||||
*/
|
||||
this.getURI = function getURI() {
|
||||
return uri;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired once this stream is finished and no further data will be written.
|
||||
*
|
||||
* @event
|
||||
*/
|
||||
this.onend = null;
|
||||
|
||||
};
|
||||
2143
guacamole-common-js/src/main/webapp/modules/Display.js
Normal file
2143
guacamole-common-js/src/main/webapp/modules/Display.js
Normal file
File diff suppressed because it is too large
Load Diff
326
guacamole-common-js/src/main/webapp/modules/Event.js
Normal file
326
guacamole-common-js/src/main/webapp/modules/Event.js
Normal file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* An arbitrary event, emitted by a {@link Guacamole.Event.Target}. This object
|
||||
* should normally serve as the base class for a different object that is more
|
||||
* specific to the event type.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!string} type
|
||||
* The unique name of this event type.
|
||||
*/
|
||||
Guacamole.Event = function Event(type) {
|
||||
|
||||
/**
|
||||
* The unique name of this event type.
|
||||
*
|
||||
* @type {!string}
|
||||
*/
|
||||
this.type = type;
|
||||
|
||||
/**
|
||||
* An arbitrary timestamp in milliseconds, indicating this event's
|
||||
* position in time relative to other events.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.timestamp = new Date().getTime();
|
||||
|
||||
/**
|
||||
* Returns the number of milliseconds elapsed since this event was created.
|
||||
*
|
||||
* @return {!number}
|
||||
* The number of milliseconds elapsed since this event was created.
|
||||
*/
|
||||
this.getAge = function getAge() {
|
||||
return new Date().getTime() - this.timestamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests that the legacy event handler associated with this event be
|
||||
* invoked on the given event target. This function will be invoked
|
||||
* automatically by implementations of {@link Guacamole.Event.Target}
|
||||
* whenever {@link Guacamole.Event.Target#emit emit()} is invoked.
|
||||
* <p>
|
||||
* Older versions of Guacamole relied on single event handlers with the
|
||||
* prefix "on", such as "onmousedown" or "onkeyup". If a Guacamole.Event
|
||||
* implementation is replacing the event previously represented by one of
|
||||
* these handlers, this function gives the implementation the opportunity
|
||||
* to provide backward compatibility with the old handler.
|
||||
* <p>
|
||||
* Unless overridden, this function does nothing.
|
||||
*
|
||||
* @param {!Guacamole.Event.Target} eventTarget
|
||||
* The {@link Guacamole.Event.Target} that emitted this event.
|
||||
*/
|
||||
this.invokeLegacyHandler = function invokeLegacyHandler(eventTarget) {
|
||||
// Do nothing
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A {@link Guacamole.Event} that may relate to one or more DOM events.
|
||||
* Continued propagation and default behavior of the related DOM events may be
|
||||
* prevented with {@link Guacamole.Event.DOMEvent#stopPropagation stopPropagation()}
|
||||
* and {@link Guacamole.Event.DOMEvent#preventDefault preventDefault()}
|
||||
* respectively.
|
||||
*
|
||||
* @constructor
|
||||
* @augments Guacamole.Event
|
||||
*
|
||||
* @param {!string} type
|
||||
* The unique name of this event type.
|
||||
*
|
||||
* @param {Event|Event[]} [events=[]]
|
||||
* The DOM events that are related to this event, if any. Future calls to
|
||||
* {@link Guacamole.Event.DOMEvent#preventDefault preventDefault()} and
|
||||
* {@link Guacamole.Event.DOMEvent#stopPropagation stopPropagation()} will
|
||||
* affect these events.
|
||||
*/
|
||||
Guacamole.Event.DOMEvent = function DOMEvent(type, events) {
|
||||
|
||||
Guacamole.Event.call(this, type);
|
||||
|
||||
// Default to empty array
|
||||
events = events || [];
|
||||
|
||||
// Automatically wrap non-array single Event in an array
|
||||
if (!Array.isArray(events))
|
||||
events = [ events ];
|
||||
|
||||
/**
|
||||
* Requests that the default behavior of related DOM events be prevented.
|
||||
* Whether this request will be honored by the browser depends on the
|
||||
* nature of those events and the timing of the request.
|
||||
*/
|
||||
this.preventDefault = function preventDefault() {
|
||||
events.forEach(function applyPreventDefault(event) {
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
event.returnValue = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops further propagation of related events through the DOM. Only events
|
||||
* that are directly related to this event will be stopped.
|
||||
*/
|
||||
this.stopPropagation = function stopPropagation() {
|
||||
events.forEach(function applyStopPropagation(event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience function for cancelling all further processing of a given DOM
|
||||
* event. Invoking this function prevents the default behavior of the event and
|
||||
* stops any further propagation.
|
||||
*
|
||||
* @param {!Event} event
|
||||
* The DOM event to cancel.
|
||||
*/
|
||||
Guacamole.Event.DOMEvent.cancelEvent = function cancelEvent(event) {
|
||||
event.stopPropagation();
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
event.returnValue = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* An object which can dispatch {@link Guacamole.Event} objects. Listeners
|
||||
* registered with {@link Guacamole.Event.Target#on on()} will automatically
|
||||
* be invoked based on the type of {@link Guacamole.Event} passed to
|
||||
* {@link Guacamole.Event.Target#dispatch dispatch()}. It is normally
|
||||
* subclasses of Guacamole.Event.Target that will dispatch events, and usages
|
||||
* of those subclasses that will catch dispatched events with on().
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
Guacamole.Event.Target = function Target() {
|
||||
|
||||
/**
|
||||
* A callback function which handles an event dispatched by an event
|
||||
* target.
|
||||
*
|
||||
* @callback Guacamole.Event.Target~listener
|
||||
* @param {!Guacamole.Event} event
|
||||
* The event that was dispatched.
|
||||
*
|
||||
* @param {!Guacamole.Event.Target} target
|
||||
* The object that dispatched the event.
|
||||
*/
|
||||
|
||||
/**
|
||||
* All listeners (callback functions) registered for each event type passed
|
||||
* to {@link Guacamole.Event.Targer#on on()}.
|
||||
*
|
||||
* @private
|
||||
* @type {!Object.<string, Guacamole.Event.Target~listener[]>}
|
||||
*/
|
||||
var listeners = {};
|
||||
|
||||
/**
|
||||
* Registers a listener for events having the given type, as dictated by
|
||||
* the {@link Guacamole.Event#type type} property of {@link Guacamole.Event}
|
||||
* provided to {@link Guacamole.Event.Target#dispatch dispatch()}.
|
||||
*
|
||||
* @param {!string} type
|
||||
* The unique name of this event type.
|
||||
*
|
||||
* @param {!Guacamole.Event.Target~listener} listener
|
||||
* The function to invoke when an event having the given type is
|
||||
* dispatched. The {@link Guacamole.Event} object provided to
|
||||
* {@link Guacamole.Event.Target#dispatch dispatch()} will be passed to
|
||||
* this function, along with the dispatching Guacamole.Event.Target.
|
||||
*/
|
||||
this.on = function on(type, listener) {
|
||||
|
||||
var relevantListeners = listeners[type];
|
||||
if (!relevantListeners)
|
||||
listeners[type] = relevantListeners = [];
|
||||
|
||||
relevantListeners.push(listener);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a listener for events having the given types, as dictated by
|
||||
* the {@link Guacamole.Event#type type} property of {@link Guacamole.Event}
|
||||
* provided to {@link Guacamole.Event.Target#dispatch dispatch()}.
|
||||
* <p>
|
||||
* Invoking this function is equivalent to manually invoking
|
||||
* {@link Guacamole.Event.Target#on on()} for each of the provided types.
|
||||
*
|
||||
* @param {!string[]} types
|
||||
* The unique names of the event types to associate with the given
|
||||
* listener.
|
||||
*
|
||||
* @param {!Guacamole.Event.Target~listener} listener
|
||||
* The function to invoke when an event having any of the given types
|
||||
* is dispatched. The {@link Guacamole.Event} object provided to
|
||||
* {@link Guacamole.Event.Target#dispatch dispatch()} will be passed to
|
||||
* this function, along with the dispatching Guacamole.Event.Target.
|
||||
*/
|
||||
this.onEach = function onEach(types, listener) {
|
||||
types.forEach(function addListener(type) {
|
||||
this.on(type, listener);
|
||||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches the given event, invoking all event handlers registered with
|
||||
* this Guacamole.Event.Target for that event's
|
||||
* {@link Guacamole.Event#type type}.
|
||||
*
|
||||
* @param {!Guacamole.Event} event
|
||||
* The event to dispatch.
|
||||
*/
|
||||
this.dispatch = function dispatch(event) {
|
||||
|
||||
// Invoke any relevant legacy handler for the event
|
||||
event.invokeLegacyHandler(this);
|
||||
|
||||
// Invoke all registered listeners
|
||||
var relevantListeners = listeners[event.type];
|
||||
if (relevantListeners) {
|
||||
for (var i = 0; i < relevantListeners.length; i++) {
|
||||
relevantListeners[i](event, this);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregisters a listener that was previously registered with
|
||||
* {@link Guacamole.Event.Target#on on()} or
|
||||
* {@link Guacamole.Event.Target#onEach onEach()}. If no such listener was
|
||||
* registered, this function has no effect. If multiple copies of the same
|
||||
* listener were registered, the first listener still registered will be
|
||||
* removed.
|
||||
*
|
||||
* @param {!string} type
|
||||
* The unique name of the event type handled by the listener being
|
||||
* removed.
|
||||
*
|
||||
* @param {!Guacamole.Event.Target~listener} listener
|
||||
* The listener function previously provided to
|
||||
* {@link Guacamole.Event.Target#on on()}or
|
||||
* {@link Guacamole.Event.Target#onEach onEach()}.
|
||||
*
|
||||
* @returns {!boolean}
|
||||
* true if the specified listener was removed, false otherwise.
|
||||
*/
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregisters listeners that were previously registered with
|
||||
* {@link Guacamole.Event.Target#on on()} or
|
||||
* {@link Guacamole.Event.Target#onEach onEach()}. If no such listeners
|
||||
* were registered, this function has no effect. If multiple copies of the
|
||||
* same listener were registered for the same event type, the first
|
||||
* listener still registered will be removed.
|
||||
* <p>
|
||||
* Invoking this function is equivalent to manually invoking
|
||||
* {@link Guacamole.Event.Target#off off()} for each of the provided types.
|
||||
*
|
||||
* @param {!string[]} types
|
||||
* The unique names of the event types handled by the listeners being
|
||||
* removed.
|
||||
*
|
||||
* @param {!Guacamole.Event.Target~listener} listener
|
||||
* The listener function previously provided to
|
||||
* {@link Guacamole.Event.Target#on on()} or
|
||||
* {@link Guacamole.Event.Target#onEach onEach()}.
|
||||
*
|
||||
* @returns {!boolean}
|
||||
* true if any of the specified listeners were removed, false
|
||||
* otherwise.
|
||||
*/
|
||||
this.offEach = function offEach(types, listener) {
|
||||
|
||||
var changed = false;
|
||||
|
||||
types.forEach(function removeListener(type) {
|
||||
changed |= this.off(type, listener);
|
||||
}, this);
|
||||
|
||||
return changed;
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
129
guacamole-common-js/src/main/webapp/modules/InputSink.js
Normal file
129
guacamole-common-js/src/main/webapp/modules/InputSink.js
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A hidden input field which attempts to keep itself focused at all times,
|
||||
* except when another input field has been intentionally focused, whether
|
||||
* programatically or by the user. The actual underlying input field, returned
|
||||
* by getElement(), may be used as a reliable source of keyboard-related events,
|
||||
* particularly composition and input events which may require a focused input
|
||||
* field to be dispatched at all.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
Guacamole.InputSink = function InputSink() {
|
||||
|
||||
/**
|
||||
* Reference to this instance of Guacamole.InputSink.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.InputSink}
|
||||
*/
|
||||
var sink = this;
|
||||
|
||||
/**
|
||||
* The underlying input field, styled to be invisible.
|
||||
*
|
||||
* @private
|
||||
* @type {!Element}
|
||||
*/
|
||||
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';
|
||||
|
||||
// Keep field clear when modified via normal keypresses
|
||||
field.addEventListener("keypress", function clearKeypress(e) {
|
||||
field.value = '';
|
||||
}, false);
|
||||
|
||||
// Keep field clear when modofied via composition events
|
||||
field.addEventListener("compositionend", function clearCompletedComposition(e) {
|
||||
if (e.data)
|
||||
field.value = '';
|
||||
}, false);
|
||||
|
||||
// Keep field clear when modofied via input events
|
||||
field.addEventListener("input", function clearCompletedInput(e) {
|
||||
if (e.data && !e.isComposing)
|
||||
field.value = '';
|
||||
}, false);
|
||||
|
||||
// Whenever focus is gained, automatically click to ensure cursor is
|
||||
// actually placed within the field (the field may simply be highlighted or
|
||||
// outlined otherwise)
|
||||
field.addEventListener("focus", function focusReceived() {
|
||||
window.setTimeout(function deferRefocus() {
|
||||
field.click();
|
||||
field.select();
|
||||
}, 0);
|
||||
}, true);
|
||||
|
||||
/**
|
||||
* Attempts to focus the underlying input field. The focus attempt occurs
|
||||
* asynchronously, and may silently fail depending on browser restrictions.
|
||||
*/
|
||||
this.focus = function focus() {
|
||||
window.setTimeout(function deferRefocus() {
|
||||
field.focus(); // Focus must be deferred to work reliably across browsers
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the underlying input field. This input field MUST be manually
|
||||
* added to the DOM for the Guacamole.InputSink to have any effect.
|
||||
*
|
||||
* @returns {!Element}
|
||||
* The underlying input field.
|
||||
*/
|
||||
this.getElement = function getElement() {
|
||||
return field;
|
||||
};
|
||||
|
||||
// Automatically refocus input sink if part of DOM
|
||||
document.addEventListener("keydown", function refocusSink(e) {
|
||||
|
||||
// Do not refocus if focus is on an input field
|
||||
var focused = document.activeElement;
|
||||
if (focused && focused !== document.body) {
|
||||
|
||||
// Only consider focused input fields which are actually visible
|
||||
var rect = focused.getBoundingClientRect();
|
||||
if (rect.left + rect.width > 0 && rect.top + rect.height > 0)
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Refocus input sink instead of handling click
|
||||
sink.focus();
|
||||
|
||||
}, true);
|
||||
|
||||
};
|
||||
141
guacamole-common-js/src/main/webapp/modules/InputStream.js
Normal file
141
guacamole-common-js/src/main/webapp/modules/InputStream.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* An input stream abstraction used by the Guacamole client to facilitate
|
||||
* transfer of files or other binary data.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.Client} client
|
||||
* The client owning this stream.
|
||||
*
|
||||
* @param {!number} index
|
||||
* The index of this stream.
|
||||
*/
|
||||
Guacamole.InputStream = function(client, index) {
|
||||
|
||||
/**
|
||||
* Reference to this stream.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.InputStream}
|
||||
*/
|
||||
var guac_stream = this;
|
||||
|
||||
/**
|
||||
* The index of this stream.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.index = index;
|
||||
|
||||
/**
|
||||
* Called when a blob of data is received.
|
||||
*
|
||||
* @event
|
||||
* @param {!string} data
|
||||
* The received base64 data.
|
||||
*/
|
||||
this.onblob = null;
|
||||
|
||||
/**
|
||||
* Called when this stream is closed.
|
||||
*
|
||||
* @event
|
||||
*/
|
||||
this.onend = null;
|
||||
|
||||
/**
|
||||
* Acknowledges the receipt of a blob.
|
||||
*
|
||||
* @param {!string} message
|
||||
* A human-readable message describing the error or status.
|
||||
*
|
||||
* @param {!number} code
|
||||
* The error code, if any, or 0 for success.
|
||||
*/
|
||||
this.sendAck = function(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();
|
||||
};
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
79
guacamole-common-js/src/main/webapp/modules/IntegerPool.js
Normal file
79
guacamole-common-js/src/main/webapp/modules/IntegerPool.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Integer pool which returns consistently increasing integers while integers
|
||||
* are in use, and previously-used integers when possible.
|
||||
* @constructor
|
||||
*/
|
||||
Guacamole.IntegerPool = function() {
|
||||
|
||||
/**
|
||||
* Reference to this integer pool.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var guac_pool = this;
|
||||
|
||||
/**
|
||||
* Array of available integers.
|
||||
*
|
||||
* @private
|
||||
* @type {!number[]}
|
||||
*/
|
||||
var pool = [];
|
||||
|
||||
/**
|
||||
* The next integer to return if no more integers remain.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.next_int = 0;
|
||||
|
||||
/**
|
||||
* Returns the next available integer in the pool. If possible, a previously
|
||||
* used integer will be returned.
|
||||
*
|
||||
* @return {!number}
|
||||
* The next available integer.
|
||||
*/
|
||||
this.next = function() {
|
||||
|
||||
// If free'd integers exist, return one of those
|
||||
if (pool.length > 0)
|
||||
return pool.shift();
|
||||
|
||||
// Otherwise, return a new integer
|
||||
return guac_pool.next_int++;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Frees the given integer, allowing it to be reused.
|
||||
*
|
||||
* @param {!number} integer
|
||||
* The integer to free.
|
||||
*/
|
||||
this.free = function(integer) {
|
||||
pool.push(integer);
|
||||
};
|
||||
|
||||
};
|
||||
114
guacamole-common-js/src/main/webapp/modules/JSONReader.js
Normal file
114
guacamole-common-js/src/main/webapp/modules/JSONReader.js
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A reader which automatically handles the given input stream, assembling all
|
||||
* received blobs into a JavaScript object by appending them to each other, in
|
||||
* order, and decoding the result as JSON. Note that this object will overwrite
|
||||
* any installed event handlers on the given Guacamole.InputStream.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Guacamole.InputStream} stream
|
||||
* The stream that JSON will be read from.
|
||||
*/
|
||||
Guacamole.JSONReader = function guacamoleJSONReader(stream) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.JSONReader.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.JSONReader}
|
||||
*/
|
||||
var guacReader = this;
|
||||
|
||||
/**
|
||||
* Wrapped Guacamole.StringReader.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.StringReader}
|
||||
*/
|
||||
var stringReader = new Guacamole.StringReader(stream);
|
||||
|
||||
/**
|
||||
* All JSON read thus far.
|
||||
*
|
||||
* @private
|
||||
* @type {!string}
|
||||
*/
|
||||
var json = '';
|
||||
|
||||
/**
|
||||
* Returns the current length of this Guacamole.JSONReader, in characters.
|
||||
*
|
||||
* @return {!number}
|
||||
* The current length of this Guacamole.JSONReader.
|
||||
*/
|
||||
this.getLength = function getLength() {
|
||||
return json.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the contents of this Guacamole.JSONReader as a JavaScript
|
||||
* object.
|
||||
*
|
||||
* @return {object}
|
||||
* The contents of this Guacamole.JSONReader, as parsed from the JSON
|
||||
* contents of the input stream.
|
||||
*/
|
||||
this.getJSON = function getJSON() {
|
||||
return JSON.parse(json);
|
||||
};
|
||||
|
||||
// Append all received text
|
||||
stringReader.ontext = function ontext(text) {
|
||||
|
||||
// Append received text
|
||||
json += text;
|
||||
|
||||
// Call handler, if present
|
||||
if (guacReader.onprogress)
|
||||
guacReader.onprogress(text.length);
|
||||
|
||||
};
|
||||
|
||||
// Simply call onend when end received
|
||||
stringReader.onend = function onend() {
|
||||
if (guacReader.onend)
|
||||
guacReader.onend();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired once for every blob of data received.
|
||||
*
|
||||
* @event
|
||||
* @param {!number} length
|
||||
* The number of characters received.
|
||||
*/
|
||||
this.onprogress = null;
|
||||
|
||||
/**
|
||||
* Fired once this stream is finished and no further data will be written.
|
||||
*
|
||||
* @event
|
||||
*/
|
||||
this.onend = null;
|
||||
|
||||
};
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* An object that will accept raw key events and produce a chronologically
|
||||
* ordered array of key event objects. These events can be obtained by
|
||||
* calling getEvents().
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} [startTimestamp=0]
|
||||
* The starting timestamp for the recording being intepreted. If provided,
|
||||
* the timestamp of each intepreted event will be relative to this timestamp.
|
||||
* If not provided, the raw recording timestamp will be used.
|
||||
*/
|
||||
Guacamole.KeyEventInterpreter = function KeyEventInterpreter(startTimestamp) {
|
||||
|
||||
// Default to 0 seconds to keep the raw timestamps
|
||||
if (startTimestamp === undefined || startTimestamp === null)
|
||||
startTimestamp = 0;
|
||||
|
||||
/**
|
||||
* A precursor array to the KNOWN_KEYS map. The objects contained within
|
||||
* will be constructed into full KeyDefinition objects.
|
||||
*
|
||||
* @constant
|
||||
* @private
|
||||
* @type {Object[]}
|
||||
*/
|
||||
var _KNOWN_KEYS = [
|
||||
{keysym: 0xFE03, name: 'AltGr' },
|
||||
{keysym: 0xFF08, name: 'Backspace' },
|
||||
{keysym: 0xFF09, name: 'Tab' },
|
||||
{keysym: 0xFF0B, name: 'Clear' },
|
||||
{keysym: 0xFF0D, name: 'Return', value: "\n" },
|
||||
{keysym: 0xFF13, name: 'Pause' },
|
||||
{keysym: 0xFF14, name: 'Scroll' },
|
||||
{keysym: 0xFF15, name: 'SysReq' },
|
||||
{keysym: 0xFF1B, name: 'Escape' },
|
||||
{keysym: 0xFF50, name: 'Home' },
|
||||
{keysym: 0xFF51, name: 'Left' },
|
||||
{keysym: 0xFF52, name: 'Up' },
|
||||
{keysym: 0xFF53, name: 'Right' },
|
||||
{keysym: 0xFF54, name: 'Down' },
|
||||
{keysym: 0xFF55, name: 'Page Up' },
|
||||
{keysym: 0xFF56, name: 'Page Down' },
|
||||
{keysym: 0xFF57, name: 'End' },
|
||||
{keysym: 0xFF63, name: 'Insert' },
|
||||
{keysym: 0xFF65, name: 'Undo' },
|
||||
{keysym: 0xFF6A, name: 'Help' },
|
||||
{keysym: 0xFF7F, name: 'Num' },
|
||||
{keysym: 0xFF80, name: 'Space', value: " " },
|
||||
{keysym: 0xFF8D, name: 'Enter', value: "\n" },
|
||||
{keysym: 0xFF95, name: 'Home' },
|
||||
{keysym: 0xFF96, name: 'Left' },
|
||||
{keysym: 0xFF97, name: 'Up' },
|
||||
{keysym: 0xFF98, name: 'Right' },
|
||||
{keysym: 0xFF99, name: 'Down' },
|
||||
{keysym: 0xFF9A, name: 'Page Up' },
|
||||
{keysym: 0xFF9B, name: 'Page Down' },
|
||||
{keysym: 0xFF9C, name: 'End' },
|
||||
{keysym: 0xFF9E, name: 'Insert' },
|
||||
{keysym: 0xFFAA, name: '*', value: "*" },
|
||||
{keysym: 0xFFAB, name: '+', value: "+" },
|
||||
{keysym: 0xFFAD, name: '-', value: "-" },
|
||||
{keysym: 0xFFAE, name: '.', value: "." },
|
||||
{keysym: 0xFFAF, name: '/', value: "/" },
|
||||
{keysym: 0xFFB0, name: '0', value: "0" },
|
||||
{keysym: 0xFFB1, name: '1', value: "1" },
|
||||
{keysym: 0xFFB2, name: '2', value: "2" },
|
||||
{keysym: 0xFFB3, name: '3', value: "3" },
|
||||
{keysym: 0xFFB4, name: '4', value: "4" },
|
||||
{keysym: 0xFFB5, name: '5', value: "5" },
|
||||
{keysym: 0xFFB6, name: '6', value: "6" },
|
||||
{keysym: 0xFFB7, name: '7', value: "7" },
|
||||
{keysym: 0xFFB8, name: '8', value: "8" },
|
||||
{keysym: 0xFFB9, name: '9', value: "9" },
|
||||
{keysym: 0xFFBE, name: 'F1' },
|
||||
{keysym: 0xFFBF, name: 'F2' },
|
||||
{keysym: 0xFFC0, name: 'F3' },
|
||||
{keysym: 0xFFC1, name: 'F4' },
|
||||
{keysym: 0xFFC2, name: 'F5' },
|
||||
{keysym: 0xFFC3, name: 'F6' },
|
||||
{keysym: 0xFFC4, name: 'F7' },
|
||||
{keysym: 0xFFC5, name: 'F8' },
|
||||
{keysym: 0xFFC6, name: 'F9' },
|
||||
{keysym: 0xFFC7, name: 'F10' },
|
||||
{keysym: 0xFFC8, name: 'F11' },
|
||||
{keysym: 0xFFC9, name: 'F12' },
|
||||
{keysym: 0xFFCA, name: 'F13' },
|
||||
{keysym: 0xFFCB, name: 'F14' },
|
||||
{keysym: 0xFFCC, name: 'F15' },
|
||||
{keysym: 0xFFCD, name: 'F16' },
|
||||
{keysym: 0xFFCE, name: 'F17' },
|
||||
{keysym: 0xFFCF, name: 'F18' },
|
||||
{keysym: 0xFFD0, name: 'F19' },
|
||||
{keysym: 0xFFD1, name: 'F20' },
|
||||
{keysym: 0xFFD2, name: 'F21' },
|
||||
{keysym: 0xFFD3, name: 'F22' },
|
||||
{keysym: 0xFFD4, name: 'F23' },
|
||||
{keysym: 0xFFD5, name: 'F24' },
|
||||
{keysym: 0xFFE1, name: 'Shift' },
|
||||
{keysym: 0xFFE2, name: 'Shift' },
|
||||
{keysym: 0xFFE3, name: 'Ctrl' },
|
||||
{keysym: 0xFFE4, name: 'Ctrl' },
|
||||
{keysym: 0xFFE5, name: 'Caps' },
|
||||
{keysym: 0xFFE7, name: 'Meta' },
|
||||
{keysym: 0xFFE8, name: 'Meta' },
|
||||
{keysym: 0xFFE9, name: 'Alt' },
|
||||
{keysym: 0xFFEA, name: 'Alt' },
|
||||
{keysym: 0xFFEB, name: 'Super' },
|
||||
{keysym: 0xFFEC, name: 'Super' },
|
||||
{keysym: 0xFFED, name: 'Hyper' },
|
||||
{keysym: 0xFFEE, name: 'Hyper' },
|
||||
{keysym: 0xFFFF, name: 'Delete' }
|
||||
];
|
||||
|
||||
/**
|
||||
* All known keys, as a map of X11 keysym to KeyDefinition.
|
||||
*
|
||||
* @constant
|
||||
* @private
|
||||
* @type {Object.<String, KeyDefinition>}
|
||||
*/
|
||||
var KNOWN_KEYS = {};
|
||||
_KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
|
||||
|
||||
// Construct a map of keysym to KeyDefinition object
|
||||
KNOWN_KEYS[keyDefinition.keysym] = (
|
||||
new Guacamole.KeyEventInterpreter.KeyDefinition(keyDefinition));
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* All key events parsed as of the most recent handleKeyEvent() invocation.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.KeyEventInterpreter.KeyEvent[]}
|
||||
*/
|
||||
var parsedEvents = [];
|
||||
|
||||
/**
|
||||
* If the provided keysym corresponds to a valid UTF-8 character, return
|
||||
* a KeyDefinition for that keysym. Otherwise, return null.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} keysym
|
||||
* The keysym to produce a UTF-8 KeyDefinition for, if valid.
|
||||
*
|
||||
* @returns {Guacamole.KeyEventInterpreter.KeyDefinition}
|
||||
* A KeyDefinition for the provided keysym, if it's a valid UTF-8
|
||||
* keysym, or null otherwise.
|
||||
*/
|
||||
function getUnicodeKeyDefinition(keysym) {
|
||||
|
||||
// Translate only if keysym maps to Unicode
|
||||
if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
|
||||
return null;
|
||||
|
||||
// Convert to UTF8 string
|
||||
var codepoint = keysym & 0xFFFF;
|
||||
var name = String.fromCharCode(codepoint);
|
||||
|
||||
// Create and return the definition
|
||||
return new Guacamole.KeyEventInterpreter.KeyDefinition({
|
||||
keysym: keysym, name: name, value: name});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a KeyDefinition corresponding to the provided keysym.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} keysym
|
||||
* The keysym to return a KeyDefinition for.
|
||||
*
|
||||
* @returns {KeyDefinition}
|
||||
* A KeyDefinition corresponding to the provided keysym.
|
||||
*/
|
||||
function getKeyDefinitionByKeysym(keysym) {
|
||||
|
||||
// If it's a known type, return the existing definition
|
||||
if (keysym in KNOWN_KEYS)
|
||||
return KNOWN_KEYS[keysym];
|
||||
|
||||
// Return a UTF-8 KeyDefinition, if valid
|
||||
var definition = getUnicodeKeyDefinition(keysym);
|
||||
if (definition != null)
|
||||
return definition;
|
||||
|
||||
// If it's not UTF-8, return an unknown definition, with the name
|
||||
// just set to the hex value of the keysym
|
||||
return new Guacamole.KeyEventInterpreter.KeyDefinition({
|
||||
keysym: keysym,
|
||||
name: '0x' + String(keysym.toString(16))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a raw key event, appending a new key event object for every
|
||||
* handled raw event.
|
||||
*
|
||||
* @param {!string[]} args
|
||||
* The arguments of the key event.
|
||||
*/
|
||||
this.handleKeyEvent = function handleKeyEvent(args) {
|
||||
|
||||
// The X11 keysym
|
||||
var keysym = parseInt(args[0]);
|
||||
|
||||
// Either 1 or 0 for pressed or released, respectively
|
||||
var pressed = parseInt(args[1]);
|
||||
|
||||
// The timestamp when this key event occured
|
||||
var timestamp = parseInt(args[2]);
|
||||
|
||||
// The timestamp relative to the provided initial timestamp
|
||||
var relativeTimestap = timestamp - startTimestamp;
|
||||
|
||||
// Known information about the parsed key
|
||||
var definition = getKeyDefinitionByKeysym(keysym);
|
||||
|
||||
// Push the latest parsed event into the list
|
||||
parsedEvents.push(new Guacamole.KeyEventInterpreter.KeyEvent({
|
||||
definition: definition,
|
||||
pressed: pressed,
|
||||
timestamp: relativeTimestap
|
||||
}));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the current batch of typed text. Note that the batch may be
|
||||
* incomplete, as more key events might be processed before the next
|
||||
* batch starts.
|
||||
*
|
||||
* @returns {Guacamole.KeyEventInterpreter.KeyEvent[]}
|
||||
* The current batch of text.
|
||||
*/
|
||||
this.getEvents = function getEvents() {
|
||||
return parsedEvents;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A definition for a known key.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Guacamole.KeyEventInterpreter.KeyDefinition|object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* KeyDefinition.
|
||||
*/
|
||||
Guacamole.KeyEventInterpreter.KeyDefinition = function KeyDefinition(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* The X11 keysym of the key.
|
||||
* @type {!number}
|
||||
*/
|
||||
this.keysym = parseInt(template.keysym);
|
||||
|
||||
/**
|
||||
* A human-readable name for the key.
|
||||
* @type {!String}
|
||||
*/
|
||||
this.name = template.name;
|
||||
|
||||
/**
|
||||
* The value which would be typed in a typical text editor, if any. If the
|
||||
* key is not associated with any typeable value, this will be undefined.
|
||||
* @type {String}
|
||||
*/
|
||||
this.value = template.value;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A granular description of an extracted key event, including a human-readable
|
||||
* text representation of the event, whether the event is directly typed or not,
|
||||
* and the timestamp when the event occured.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Guacamole.KeyEventInterpreter.KeyEvent|object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* KeyEvent.
|
||||
*/
|
||||
Guacamole.KeyEventInterpreter.KeyEvent = function KeyEvent(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* The key definition for the pressed key.
|
||||
*
|
||||
* @type {!Guacamole.KeyEventInterpreter.KeyDefinition}
|
||||
*/
|
||||
this.definition = template.definition;
|
||||
|
||||
/**
|
||||
* True if the key was pressed to create this event, or false if it was
|
||||
* released.
|
||||
*
|
||||
* @type {!boolean}
|
||||
*/
|
||||
this.pressed = !!template.pressed;
|
||||
|
||||
/**
|
||||
* The timestamp from the recording when this event occured.
|
||||
*
|
||||
* @type {!Number}
|
||||
*/
|
||||
this.timestamp = template.timestamp;
|
||||
|
||||
};
|
||||
1527
guacamole-common-js/src/main/webapp/modules/Keyboard.js
Normal file
1527
guacamole-common-js/src/main/webapp/modules/Keyboard.js
Normal file
File diff suppressed because it is too large
Load Diff
1152
guacamole-common-js/src/main/webapp/modules/Layer.js
Normal file
1152
guacamole-common-js/src/main/webapp/modules/Layer.js
Normal file
File diff suppressed because it is too large
Load Diff
1272
guacamole-common-js/src/main/webapp/modules/Mouse.js
Normal file
1272
guacamole-common-js/src/main/webapp/modules/Mouse.js
Normal file
File diff suppressed because it is too large
Load Diff
26
guacamole-common-js/src/main/webapp/modules/Namespace.js
Normal file
26
guacamole-common-js/src/main/webapp/modules/Namespace.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The namespace used by the Guacamole JavaScript API. Absolutely all classes
|
||||
* defined by the Guacamole JavaScript API will be within this namespace.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
var Guacamole = Guacamole || {};
|
||||
210
guacamole-common-js/src/main/webapp/modules/Object.js
Normal file
210
guacamole-common-js/src/main/webapp/modules/Object.js
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* An object used by the Guacamole client to house arbitrarily-many named
|
||||
* input and output streams.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.Client} client
|
||||
* The client owning this object.
|
||||
*
|
||||
* @param {!number} index
|
||||
* The index of this object.
|
||||
*/
|
||||
Guacamole.Object = function guacamoleObject(client, index) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.Object.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.Object}
|
||||
*/
|
||||
var guacObject = this;
|
||||
|
||||
/**
|
||||
* Map of stream name to corresponding queue of callbacks. The queue of
|
||||
* callbacks is guaranteed to be in order of request.
|
||||
*
|
||||
* @private
|
||||
* @type {!Object.<string, function[]>}
|
||||
*/
|
||||
var bodyCallbacks = {};
|
||||
|
||||
/**
|
||||
* Removes and returns the callback at the head of the callback queue for
|
||||
* the stream having the given name. If no such callbacks exist, null is
|
||||
* returned.
|
||||
*
|
||||
* @private
|
||||
* @param {!string} name
|
||||
* The name of the stream to retrieve a callback for.
|
||||
*
|
||||
* @returns {function}
|
||||
* The next callback associated with the stream having the given name,
|
||||
* or null if no such callback exists.
|
||||
*/
|
||||
var dequeueBodyCallback = function dequeueBodyCallback(name) {
|
||||
|
||||
// If no callbacks defined, simply return null
|
||||
var callbacks = bodyCallbacks[name];
|
||||
if (!callbacks)
|
||||
return null;
|
||||
|
||||
// Otherwise, pull off first callback, deleting the queue if empty
|
||||
var callback = callbacks.shift();
|
||||
if (callbacks.length === 0)
|
||||
delete bodyCallbacks[name];
|
||||
|
||||
// Return found callback
|
||||
return callback;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the given callback to the tail of the callback queue for the stream
|
||||
* having the given name.
|
||||
*
|
||||
* @private
|
||||
* @param {!string} name
|
||||
* The name of the stream to associate with the given callback.
|
||||
*
|
||||
* @param {!function} callback
|
||||
* The callback to add to the queue of the stream with the given name.
|
||||
*/
|
||||
var enqueueBodyCallback = function enqueueBodyCallback(name, callback) {
|
||||
|
||||
// Get callback queue by name, creating first if necessary
|
||||
var callbacks = bodyCallbacks[name];
|
||||
if (!callbacks) {
|
||||
callbacks = [];
|
||||
bodyCallbacks[name] = callbacks;
|
||||
}
|
||||
|
||||
// Add callback to end of queue
|
||||
callbacks.push(callback);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The index of this object.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.index = index;
|
||||
|
||||
/**
|
||||
* Called when this object receives the body of a requested input stream.
|
||||
* By default, all objects will invoke the callbacks provided to their
|
||||
* requestInputStream() functions based on the name of the stream
|
||||
* requested. This behavior can be overridden by specifying a different
|
||||
* handler here.
|
||||
*
|
||||
* @event
|
||||
* @param {!Guacamole.InputStream} inputStream
|
||||
* The input stream of the received body.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype of the data being received.
|
||||
*
|
||||
* @param {!string} name
|
||||
* The name of the stream whose body has been received.
|
||||
*/
|
||||
this.onbody = function defaultBodyHandler(inputStream, mimetype, name) {
|
||||
|
||||
// Call queued callback for the received body, if any
|
||||
var callback = dequeueBodyCallback(name);
|
||||
if (callback)
|
||||
callback(inputStream, mimetype);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when this object is being undefined. Once undefined, no further
|
||||
* communication involving this object may occur.
|
||||
*
|
||||
* @event
|
||||
*/
|
||||
this.onundefine = null;
|
||||
|
||||
/**
|
||||
* Requests read access to the input stream having the given name. If
|
||||
* successful, a new input stream will be created.
|
||||
*
|
||||
* @param {!string} name
|
||||
* The name of the input stream to request.
|
||||
*
|
||||
* @param {function} [bodyCallback]
|
||||
* The callback to invoke when the body of the requested input stream
|
||||
* is received. This callback will be provided a Guacamole.InputStream
|
||||
* and its mimetype as its two only arguments. If the onbody handler of
|
||||
* this object is overridden, this callback will not be invoked.
|
||||
*/
|
||||
this.requestInputStream = function requestInputStream(name, bodyCallback) {
|
||||
|
||||
// Queue body callback if provided
|
||||
if (bodyCallback)
|
||||
enqueueBodyCallback(name, bodyCallback);
|
||||
|
||||
// Send request for input stream
|
||||
client.requestObjectInputStream(guacObject.index, name);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new output stream associated with this object and having the
|
||||
* given mimetype and name. The legality of a mimetype and name is dictated
|
||||
* by the object itself.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype of the data which will be sent to the output stream.
|
||||
*
|
||||
* @param {!string} name
|
||||
* The defined name of an output stream within this object.
|
||||
*
|
||||
* @returns {!Guacamole.OutputStream}
|
||||
* An output stream which will write blobs to the named output stream
|
||||
* of this object.
|
||||
*/
|
||||
this.createOutputStream = function createOutputStream(mimetype, name) {
|
||||
return client.createObjectOutputStream(guacObject.index, mimetype, name);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The reserved name denoting the root stream of any object. The contents of
|
||||
* the root stream MUST be a JSON map of stream name to mimetype.
|
||||
*
|
||||
* @constant
|
||||
* @type {!string}
|
||||
*/
|
||||
Guacamole.Object.ROOT_STREAM = '/';
|
||||
|
||||
/**
|
||||
* The mimetype of a stream containing JSON which maps available stream names
|
||||
* to their corresponding mimetype. The root stream of a Guacamole.Object MUST
|
||||
* have this mimetype.
|
||||
*
|
||||
* @constant
|
||||
* @type {!string}
|
||||
*/
|
||||
Guacamole.Object.STREAM_INDEX_MIMETYPE = 'application/vnd.glyptodon.guacamole.stream-index+json';
|
||||
947
guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js
Normal file
947
guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js
Normal file
@@ -0,0 +1,947 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Dynamic on-screen keyboard. Given the layout object for an on-screen
|
||||
* keyboard, this object will construct a clickable on-screen keyboard with its
|
||||
* own key events.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.OnScreenKeyboard.Layout} layout
|
||||
* The layout of the on-screen keyboard to display.
|
||||
*/
|
||||
Guacamole.OnScreenKeyboard = function(layout) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.OnScreenKeyboard.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.OnScreenKeyboard}
|
||||
*/
|
||||
var osk = this;
|
||||
|
||||
/**
|
||||
* Map of currently-set modifiers to the keysym associated with their
|
||||
* original press. When the modifier is cleared, this keysym must be
|
||||
* released.
|
||||
*
|
||||
* @private
|
||||
* @type {!Object.<String, Number>}
|
||||
*/
|
||||
var modifierKeysyms = {};
|
||||
|
||||
/**
|
||||
* Map of all key names to their current pressed states. If a key is not
|
||||
* pressed, it may not be in this map at all, but all pressed keys will
|
||||
* have a corresponding mapping to true.
|
||||
*
|
||||
* @private
|
||||
* @type {!Object.<String, Boolean>}
|
||||
*/
|
||||
var pressed = {};
|
||||
|
||||
/**
|
||||
* All scalable elements which are part of the on-screen keyboard. Each
|
||||
* scalable element is carefully controlled to ensure the interface layout
|
||||
* and sizing remains constant, even on browsers that would otherwise
|
||||
* experience rounding error due to unit conversions.
|
||||
*
|
||||
* @private
|
||||
* @type {!ScaledElement[]}
|
||||
*/
|
||||
var scaledElements = [];
|
||||
|
||||
/**
|
||||
* Adds a CSS class to an element.
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
* @param {!Element} element
|
||||
* The element to add a class to.
|
||||
*
|
||||
* @param {!string} classname
|
||||
* The name of the class to add.
|
||||
*/
|
||||
var addClass = function addClass(element, classname) {
|
||||
|
||||
// If classList supported, use that
|
||||
if (element.classList)
|
||||
element.classList.add(classname);
|
||||
|
||||
// Otherwise, simply append the class
|
||||
else
|
||||
element.className += " " + classname;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a CSS class from an element.
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
* @param {!Element} element
|
||||
* The element to remove a class from.
|
||||
*
|
||||
* @param {!string} classname
|
||||
* The name of the class to remove.
|
||||
*/
|
||||
var removeClass = function removeClass(element, classname) {
|
||||
|
||||
// If classList supported, use that
|
||||
if (element.classList)
|
||||
element.classList.remove(classname);
|
||||
|
||||
// Otherwise, manually filter out classes with given name
|
||||
else {
|
||||
element.className = element.className.replace(/([^ ]+)[ ]*/g,
|
||||
function removeMatchingClasses(match, testClassname) {
|
||||
|
||||
// If same class, remove
|
||||
if (testClassname === classname)
|
||||
return "";
|
||||
|
||||
// Otherwise, allow
|
||||
return match;
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Counter of mouse events to ignore. This decremented by mousemove, and
|
||||
* while non-zero, mouse events will have no effect.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var ignoreMouse = 0;
|
||||
|
||||
/**
|
||||
* Ignores all pending mouse events when touch events are the apparent
|
||||
* source. Mouse events are ignored until at least touchMouseThreshold
|
||||
* mouse events occur without corresponding touch events.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var ignorePendingMouseEvents = function ignorePendingMouseEvents() {
|
||||
ignoreMouse = osk.touchMouseThreshold;
|
||||
};
|
||||
|
||||
/**
|
||||
* An element whose dimensions are maintained according to an arbitrary
|
||||
* scale. The conversion factor for these arbitrary units to pixels is
|
||||
* provided later via a call to scale().
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {!Element} element
|
||||
* The element whose scale should be maintained.
|
||||
*
|
||||
* @param {!number} width
|
||||
* The width of the element, in arbitrary units, relative to other
|
||||
* ScaledElements.
|
||||
*
|
||||
* @param {!number} height
|
||||
* The height of the element, in arbitrary units, relative to other
|
||||
* ScaledElements.
|
||||
*
|
||||
* @param {boolean} [scaleFont=false]
|
||||
* Whether the line height and font size should be scaled as well.
|
||||
*/
|
||||
var ScaledElement = function ScaledElement(element, width, height, scaleFont) {
|
||||
|
||||
/**
|
||||
* The width of this ScaledElement, in arbitrary units, relative to
|
||||
* other ScaledElements.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.width = width;
|
||||
|
||||
/**
|
||||
* The height of this ScaledElement, in arbitrary units, relative to
|
||||
* other ScaledElements.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.height = height;
|
||||
|
||||
/**
|
||||
* Resizes the associated element, updating its dimensions according to
|
||||
* the given pixels per unit.
|
||||
*
|
||||
* @param {!number} pixels
|
||||
* The number of pixels to assign per arbitrary unit.
|
||||
*/
|
||||
this.scale = function(pixels) {
|
||||
|
||||
// Scale element width/height
|
||||
element.style.width = (width * pixels) + "px";
|
||||
element.style.height = (height * pixels) + "px";
|
||||
|
||||
// Scale font, if requested
|
||||
if (scaleFont) {
|
||||
element.style.lineHeight = (height * pixels) + "px";
|
||||
element.style.fontSize = pixels + "px";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether all modifiers having the given names are currently
|
||||
* active.
|
||||
*
|
||||
* @private
|
||||
* @param {!string[]} names
|
||||
* The names of all modifiers to test.
|
||||
*
|
||||
* @returns {!boolean}
|
||||
* true if all specified modifiers are pressed, false otherwise.
|
||||
*/
|
||||
var modifiersPressed = function modifiersPressed(names) {
|
||||
|
||||
// If any required modifiers are not pressed, return false
|
||||
for (var i=0; i < names.length; i++) {
|
||||
|
||||
// Test whether current modifier is pressed
|
||||
var name = names[i];
|
||||
if (!(name in modifierKeysyms))
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, all required modifiers are pressed
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the single matching Key object associated with the key of the
|
||||
* given name, where that Key object's requirements (such as pressed
|
||||
* modifiers) are all currently satisfied.
|
||||
*
|
||||
* @private
|
||||
* @param {!string} keyName
|
||||
* The name of the key to retrieve.
|
||||
*
|
||||
* @returns {Guacamole.OnScreenKeyboard.Key}
|
||||
* The Key object associated with the given name, where that object's
|
||||
* requirements are all currently satisfied, or null if no such Key
|
||||
* can be found.
|
||||
*/
|
||||
var getActiveKey = function getActiveKey(keyName) {
|
||||
|
||||
// Get key array for given name
|
||||
var keys = osk.keys[keyName];
|
||||
if (!keys)
|
||||
return null;
|
||||
|
||||
// Find last matching key
|
||||
for (var i = keys.length - 1; i >= 0; i--) {
|
||||
|
||||
// Get candidate key
|
||||
var candidate = keys[i];
|
||||
|
||||
// If all required modifiers are pressed, use that key
|
||||
if (modifiersPressed(candidate.requires))
|
||||
return candidate;
|
||||
|
||||
}
|
||||
|
||||
// No valid key
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Presses the key having the given name, updating the associated key
|
||||
* element with the "guac-keyboard-pressed" CSS class. If the key is
|
||||
* already pressed, this function has no effect.
|
||||
*
|
||||
* @private
|
||||
* @param {!string} keyName
|
||||
* The name of the key to press.
|
||||
*
|
||||
* @param {!string} keyElement
|
||||
* The element associated with the given key.
|
||||
*/
|
||||
var press = function press(keyName, keyElement) {
|
||||
|
||||
// Press key if not yet pressed
|
||||
if (!pressed[keyName]) {
|
||||
|
||||
addClass(keyElement, "guac-keyboard-pressed");
|
||||
|
||||
// Get current key based on modifier state
|
||||
var key = getActiveKey(keyName);
|
||||
|
||||
// Update modifier state
|
||||
if (key.modifier) {
|
||||
|
||||
// Construct classname for modifier
|
||||
var modifierClass = "guac-keyboard-modifier-" + getCSSName(key.modifier);
|
||||
|
||||
// Retrieve originally-pressed keysym, if modifier was already pressed
|
||||
var originalKeysym = modifierKeysyms[key.modifier];
|
||||
|
||||
// Activate modifier if not pressed
|
||||
if (originalKeysym === undefined) {
|
||||
|
||||
addClass(keyboard, modifierClass);
|
||||
modifierKeysyms[key.modifier] = key.keysym;
|
||||
|
||||
// Send key event only if keysym is meaningful
|
||||
if (key.keysym && osk.onkeydown)
|
||||
osk.onkeydown(key.keysym);
|
||||
|
||||
}
|
||||
|
||||
// Deactivate if not pressed
|
||||
else {
|
||||
|
||||
removeClass(keyboard, modifierClass);
|
||||
delete modifierKeysyms[key.modifier];
|
||||
|
||||
// Send key event only if original keysym is meaningful
|
||||
if (originalKeysym && osk.onkeyup)
|
||||
osk.onkeyup(originalKeysym);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If not modifier, send key event now
|
||||
else if (osk.onkeydown)
|
||||
osk.onkeydown(key.keysym);
|
||||
|
||||
// Mark key as pressed
|
||||
pressed[keyName] = true;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Releases the key having the given name, removing the
|
||||
* "guac-keyboard-pressed" CSS class from the associated element. If the
|
||||
* key is already released, this function has no effect.
|
||||
*
|
||||
* @private
|
||||
* @param {!string} keyName
|
||||
* The name of the key to release.
|
||||
*
|
||||
* @param {!string} keyElement
|
||||
* The element associated with the given key.
|
||||
*/
|
||||
var release = function release(keyName, keyElement) {
|
||||
|
||||
// Release key if currently pressed
|
||||
if (pressed[keyName]) {
|
||||
|
||||
removeClass(keyElement, "guac-keyboard-pressed");
|
||||
|
||||
// Get current key based on modifier state
|
||||
var key = getActiveKey(keyName);
|
||||
|
||||
// Send key event if not a modifier key
|
||||
if (!key.modifier && osk.onkeyup)
|
||||
osk.onkeyup(key.keysym);
|
||||
|
||||
// Mark key as released
|
||||
pressed[keyName] = false;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Create keyboard
|
||||
var keyboard = document.createElement("div");
|
||||
keyboard.className = "guac-keyboard";
|
||||
|
||||
// Do not allow selection or mouse movement to propagate/register.
|
||||
keyboard.onselectstart =
|
||||
keyboard.onmousemove =
|
||||
keyboard.onmouseup =
|
||||
keyboard.onmousedown = function handleMouseEvents(e) {
|
||||
|
||||
// If ignoring events, decrement counter
|
||||
if (ignoreMouse)
|
||||
ignoreMouse--;
|
||||
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The number of mousemove events to require before re-enabling mouse
|
||||
* event handling after receiving a touch event.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.touchMouseThreshold = 3;
|
||||
|
||||
/**
|
||||
* Fired whenever the user presses a key on this Guacamole.OnScreenKeyboard.
|
||||
*
|
||||
* @event
|
||||
* @param {!number} keysym
|
||||
* The keysym of the key being pressed.
|
||||
*/
|
||||
this.onkeydown = null;
|
||||
|
||||
/**
|
||||
* Fired whenever the user releases a key on this Guacamole.OnScreenKeyboard.
|
||||
*
|
||||
* @event
|
||||
* @param {!number} keysym
|
||||
* The keysym of the key being released.
|
||||
*/
|
||||
this.onkeyup = null;
|
||||
|
||||
/**
|
||||
* The keyboard layout provided at time of construction.
|
||||
*
|
||||
* @type {!Guacamole.OnScreenKeyboard.Layout}
|
||||
*/
|
||||
this.layout = new Guacamole.OnScreenKeyboard.Layout(layout);
|
||||
|
||||
/**
|
||||
* Returns the element containing the entire on-screen keyboard.
|
||||
*
|
||||
* @returns {!Element}
|
||||
* The element containing the entire on-screen keyboard.
|
||||
*/
|
||||
this.getElement = function() {
|
||||
return keyboard;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes all elements within this Guacamole.OnScreenKeyboard such that
|
||||
* the width is close to but does not exceed the specified width. The
|
||||
* height of the keyboard is determined based on the width.
|
||||
*
|
||||
* @param {!number} width
|
||||
* The width to resize this Guacamole.OnScreenKeyboard to, in pixels.
|
||||
*/
|
||||
this.resize = function(width) {
|
||||
|
||||
// Get pixel size of a unit
|
||||
var unit = Math.floor(width * 10 / osk.layout.width) / 10;
|
||||
|
||||
// Resize all scaled elements
|
||||
for (var i=0; i<scaledElements.length; i++) {
|
||||
var scaledElement = scaledElements[i];
|
||||
scaledElement.scale(unit);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Given the name of a key and its corresponding definition, which may be
|
||||
* an array of keys objects, a number (keysym), a string (key title), or a
|
||||
* single key object, returns an array of key objects, deriving any missing
|
||||
* properties as needed, and ensuring the key name is defined.
|
||||
*
|
||||
* @private
|
||||
* @param {!string} name
|
||||
* The name of the key being coerced into an array of Key objects.
|
||||
*
|
||||
* @param {!(number|string|Guacamole.OnScreenKeyboard.Key|Guacamole.OnScreenKeyboard.Key[])} object
|
||||
* The object defining the behavior of the key having the given name,
|
||||
* which may be the title of the key (a string), the keysym (a number),
|
||||
* a single Key object, or an array of Key objects.
|
||||
*
|
||||
* @returns {!Guacamole.OnScreenKeyboard.Key[]}
|
||||
* An array of all keys associated with the given name.
|
||||
*/
|
||||
var asKeyArray = function asKeyArray(name, object) {
|
||||
|
||||
// If already an array, just coerce into a true Key[]
|
||||
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;
|
||||
}
|
||||
|
||||
// Derive key object from keysym if that's all we have
|
||||
if (typeof object === 'number') {
|
||||
return [new Guacamole.OnScreenKeyboard.Key({
|
||||
name : name,
|
||||
keysym : object
|
||||
})];
|
||||
}
|
||||
|
||||
// Derive key object from title if that's all we have
|
||||
if (typeof object === 'string') {
|
||||
return [new Guacamole.OnScreenKeyboard.Key({
|
||||
name : name,
|
||||
title : object
|
||||
})];
|
||||
}
|
||||
|
||||
// Otherwise, assume it's already a key object, just not an array
|
||||
return [new Guacamole.OnScreenKeyboard.Key(object, name)];
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the rather forgiving key mapping allowed by
|
||||
* Guacamole.OnScreenKeyboard.Layout into a rigorous mapping of key name
|
||||
* to key definition, where the key definition is always an array of Key
|
||||
* objects.
|
||||
*
|
||||
* @private
|
||||
* @param {!Object.<string, number|string|Guacamole.OnScreenKeyboard.Key|Guacamole.OnScreenKeyboard.Key[]>} keys
|
||||
* A mapping of key name to key definition, where the key definition is
|
||||
* the title of the key (a string), the keysym (a number), a single
|
||||
* Key object, or an array of Key objects.
|
||||
*
|
||||
* @returns {!Object.<string, Guacamole.OnScreenKeyboard.Key[]>}
|
||||
* A more-predictable mapping of key name to key definition, where the
|
||||
* key definition is always simply an array of Key objects.
|
||||
*/
|
||||
var getKeys = function getKeys(keys) {
|
||||
|
||||
var keyArrays = {};
|
||||
|
||||
// Coerce all keys into individual key arrays
|
||||
for (var name in layout.keys) {
|
||||
keyArrays[name] = asKeyArray(name, keys[name]);
|
||||
}
|
||||
|
||||
return keyArrays;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Map of all key names to their corresponding set of keys. Each key name
|
||||
* may correspond to multiple keys due to the effect of modifiers.
|
||||
*
|
||||
* @type {!Object.<string, Guacamole.OnScreenKeyboard.Key[]>}
|
||||
*/
|
||||
this.keys = getKeys(layout.keys);
|
||||
|
||||
/**
|
||||
* Given an arbitrary string representing the name of some component of the
|
||||
* on-screen keyboard, returns a string formatted for use as a CSS class
|
||||
* name. The result will be lowercase. Word boundaries previously denoted
|
||||
* by CamelCase will be replaced by individual hyphens, as will all
|
||||
* contiguous non-alphanumeric characters.
|
||||
*
|
||||
* @private
|
||||
* @param {!string} name
|
||||
* An arbitrary string representing the name of some component of the
|
||||
* on-screen keyboard.
|
||||
*
|
||||
* @returns {!string}
|
||||
* A string formatted for use as a CSS class name.
|
||||
*/
|
||||
var getCSSName = function getCSSName(name) {
|
||||
|
||||
// Convert name from possibly-CamelCase to hyphenated lowercase
|
||||
var cssName = name
|
||||
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
||||
.replace(/[^A-Za-z0-9]+/g, '-')
|
||||
.toLowerCase();
|
||||
|
||||
return cssName;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends DOM elements to the given element as dictated by the layout
|
||||
* structure object provided. If a name is provided, an additional CSS
|
||||
* class, prepended with "guac-keyboard-", will be added to the top-level
|
||||
* element.
|
||||
*
|
||||
* If the layout structure object is an array, all elements within that
|
||||
* array will be recursively appended as children of a group, and the
|
||||
* top-level element will be given the CSS class "guac-keyboard-group".
|
||||
*
|
||||
* If the layout structure object is an object, all properties within that
|
||||
* object will be recursively appended as children of a group, and the
|
||||
* top-level element will be given the CSS class "guac-keyboard-group". The
|
||||
* name of each property will be applied as the name of each child object
|
||||
* for the sake of CSS. Each property will be added in sorted order.
|
||||
*
|
||||
* If the layout structure object is a string, the key having that name
|
||||
* will be appended. The key will be given the CSS class
|
||||
* "guac-keyboard-key" and "guac-keyboard-key-NAME", where NAME is the name
|
||||
* of the key. If the name of the key is a single character, this will
|
||||
* first be transformed into the C-style hexadecimal literal for the
|
||||
* Unicode codepoint of that character. For example, the key "A" would
|
||||
* become "guac-keyboard-key-0x41".
|
||||
*
|
||||
* If the layout structure object is a number, a gap of that size will be
|
||||
* inserted. The gap will be given the CSS class "guac-keyboard-gap", and
|
||||
* will be scaled according to the same size units as each key.
|
||||
*
|
||||
* @private
|
||||
* @param {!Element} element
|
||||
* The element to append elements to.
|
||||
*
|
||||
* @param {!(Array|object|string|number)} object
|
||||
* The layout structure object to use when constructing the elements to
|
||||
* append.
|
||||
*
|
||||
* @param {string} [name]
|
||||
* The name of the top-level element being appended, if any.
|
||||
*/
|
||||
var appendElements = function appendElements(element, object, name) {
|
||||
|
||||
var i;
|
||||
|
||||
// Create div which will become the group or key
|
||||
var div = document.createElement('div');
|
||||
|
||||
// Add class based on name, if name given
|
||||
if (name)
|
||||
addClass(div, 'guac-keyboard-' + getCSSName(name));
|
||||
|
||||
// If an array, append each element
|
||||
if (object instanceof Array) {
|
||||
|
||||
// Add group class
|
||||
addClass(div, 'guac-keyboard-group');
|
||||
|
||||
// Append all elements of array
|
||||
for (i=0; i < object.length; i++)
|
||||
appendElements(div, object[i]);
|
||||
|
||||
}
|
||||
|
||||
// If an object, append each property value
|
||||
else if (object instanceof Object) {
|
||||
|
||||
// Add group class
|
||||
addClass(div, 'guac-keyboard-group');
|
||||
|
||||
// Append all children, sorted by name
|
||||
var names = Object.keys(object).sort();
|
||||
for (i=0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
appendElements(div, object[name], name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If a number, create as a gap
|
||||
else if (typeof object === 'number') {
|
||||
|
||||
// Add gap class
|
||||
addClass(div, 'guac-keyboard-gap');
|
||||
|
||||
// Maintain scale
|
||||
scaledElements.push(new ScaledElement(div, object, object));
|
||||
|
||||
}
|
||||
|
||||
// If a string, create as a key
|
||||
else if (typeof object === 'string') {
|
||||
|
||||
// If key name is only one character, use codepoint for name
|
||||
var keyName = object;
|
||||
if (keyName.length === 1)
|
||||
keyName = '0x' + keyName.charCodeAt(0).toString(16);
|
||||
|
||||
// Add key container class
|
||||
addClass(div, 'guac-keyboard-key-container');
|
||||
|
||||
// Create key element which will contain all possible caps
|
||||
var keyElement = document.createElement('div');
|
||||
keyElement.className = 'guac-keyboard-key '
|
||||
+ 'guac-keyboard-key-' + getCSSName(keyName);
|
||||
|
||||
// Add all associated keys as caps within DOM
|
||||
var keys = osk.keys[object];
|
||||
if (keys) {
|
||||
for (i=0; i < keys.length; i++) {
|
||||
|
||||
// Get current key
|
||||
var key = keys[i];
|
||||
|
||||
// Create cap element for key
|
||||
var capElement = document.createElement('div');
|
||||
capElement.className = 'guac-keyboard-cap';
|
||||
capElement.textContent = key.title;
|
||||
|
||||
// Add classes for any requirements
|
||||
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));
|
||||
}
|
||||
|
||||
// Add cap to key within DOM
|
||||
keyElement.appendChild(capElement);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Add key to DOM, maintain scale
|
||||
div.appendChild(keyElement);
|
||||
scaledElements.push(new ScaledElement(div, osk.layout.keyWidths[object] || 1, 1, true));
|
||||
|
||||
/**
|
||||
* Handles a touch event which results in the pressing of an OSK
|
||||
* key. Touch events will result in mouse events being ignored for
|
||||
* touchMouseThreshold events.
|
||||
*
|
||||
* @private
|
||||
* @param {!TouchEvent} e
|
||||
* The touch event being handled.
|
||||
*/
|
||||
var touchPress = function touchPress(e) {
|
||||
e.preventDefault();
|
||||
ignoreMouse = osk.touchMouseThreshold;
|
||||
press(object, keyElement);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a touch event which results in the release of an OSK
|
||||
* key. Touch events will result in mouse events being ignored for
|
||||
* touchMouseThreshold events.
|
||||
*
|
||||
* @private
|
||||
* @param {!TouchEvent} e
|
||||
* The touch event being handled.
|
||||
*/
|
||||
var touchRelease = function touchRelease(e) {
|
||||
e.preventDefault();
|
||||
ignoreMouse = osk.touchMouseThreshold;
|
||||
release(object, keyElement);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a mouse event which results in the pressing of an OSK
|
||||
* key. If mouse events are currently being ignored, this handler
|
||||
* does nothing.
|
||||
*
|
||||
* @private
|
||||
* @param {!MouseEvent} e
|
||||
* The touch event being handled.
|
||||
*/
|
||||
var mousePress = function mousePress(e) {
|
||||
e.preventDefault();
|
||||
if (ignoreMouse === 0)
|
||||
press(object, keyElement);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a mouse event which results in the release of an OSK
|
||||
* key. If mouse events are currently being ignored, this handler
|
||||
* does nothing.
|
||||
*
|
||||
* @private
|
||||
* @param {!MouseEvent} e
|
||||
* The touch event being handled.
|
||||
*/
|
||||
var mouseRelease = function mouseRelease(e) {
|
||||
e.preventDefault();
|
||||
if (ignoreMouse === 0)
|
||||
release(object, keyElement);
|
||||
};
|
||||
|
||||
// Handle touch events on key
|
||||
keyElement.addEventListener("touchstart", touchPress, true);
|
||||
keyElement.addEventListener("touchend", touchRelease, true);
|
||||
|
||||
// Handle mouse events on key
|
||||
keyElement.addEventListener("mousedown", mousePress, true);
|
||||
keyElement.addEventListener("mouseup", mouseRelease, true);
|
||||
keyElement.addEventListener("mouseout", mouseRelease, true);
|
||||
|
||||
} // end if object is key name
|
||||
|
||||
// Add newly-created group/key
|
||||
element.appendChild(div);
|
||||
|
||||
};
|
||||
|
||||
// Create keyboard layout in DOM
|
||||
appendElements(keyboard, layout.layout);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an entire on-screen keyboard layout, including all available
|
||||
* keys, their behaviors, and their relative position and sizing.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!(Guacamole.OnScreenKeyboard.Layout|object)} template
|
||||
* The object whose identically-named properties will be used to initialize
|
||||
* the properties of this layout.
|
||||
*/
|
||||
Guacamole.OnScreenKeyboard.Layout = function(template) {
|
||||
|
||||
/**
|
||||
* The language of keyboard layout, such as "en_US". This property is for
|
||||
* informational purposes only, but it is recommend to conform to the
|
||||
* [language code]_[country code] format.
|
||||
*
|
||||
* @type {!string}
|
||||
*/
|
||||
this.language = template.language;
|
||||
|
||||
/**
|
||||
* The type of keyboard layout, such as "qwerty". This property is for
|
||||
* informational purposes only, and does not conform to any standard.
|
||||
*
|
||||
* @type {!string}
|
||||
*/
|
||||
this.type = template.type;
|
||||
|
||||
/**
|
||||
* Map of key name to corresponding keysym, title, or key object. If only
|
||||
* the keysym or title is provided, the key object will be created
|
||||
* implicitly. In all cases, the name property of the key object will be
|
||||
* taken from the name given in the mapping.
|
||||
*
|
||||
* @type {!Object.<string, number|string|Guacamole.OnScreenKeyboard.Key|Guacamole.OnScreenKeyboard.Key[]>}
|
||||
*/
|
||||
this.keys = template.keys;
|
||||
|
||||
/**
|
||||
* Arbitrarily nested, arbitrarily grouped key names. The contents of the
|
||||
* layout will be traversed to produce an identically-nested grouping of
|
||||
* keys in the DOM tree. All strings will be transformed into their
|
||||
* corresponding sets of keys, while all objects and arrays will be
|
||||
* transformed into named groups and anonymous groups respectively. Any
|
||||
* numbers present will be transformed into gaps of that size, scaled
|
||||
* according to the same units as each key.
|
||||
*
|
||||
* @type {!object}
|
||||
*/
|
||||
this.layout = template.layout;
|
||||
|
||||
/**
|
||||
* The width of the entire keyboard, in arbitrary units. The width of each
|
||||
* key is relative to this width, as both width values are assumed to be in
|
||||
* the same units. The conversion factor between these units and pixels is
|
||||
* derived later via a call to resize() on the Guacamole.OnScreenKeyboard.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.width = template.width;
|
||||
|
||||
/**
|
||||
* The width of each key, in arbitrary units, relative to other keys in
|
||||
* this layout. The true pixel size of each key will be determined by the
|
||||
* overall size of the keyboard. If not defined here, the width of each
|
||||
* key will default to 1.
|
||||
*
|
||||
* @type {!Object.<string, number>}
|
||||
*/
|
||||
this.keyWidths = template.keyWidths || {};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a single key, or a single possible behavior of a key. Each key
|
||||
* on the on-screen keyboard must have at least one associated
|
||||
* Guacamole.OnScreenKeyboard.Key, whether that key is explicitly defined or
|
||||
* implied, and may have multiple Guacamole.OnScreenKeyboard.Key if behavior
|
||||
* depends on modifier states.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!(Guacamole.OnScreenKeyboard.Key|object)} template
|
||||
* The object whose identically-named properties will be used to initialize
|
||||
* the properties of this key.
|
||||
*
|
||||
* @param {string} [name]
|
||||
* The name to use instead of any name provided within the template, if
|
||||
* any. If omitted, the name within the template will be used, assuming the
|
||||
* template contains a name.
|
||||
*/
|
||||
Guacamole.OnScreenKeyboard.Key = function(template, name) {
|
||||
|
||||
/**
|
||||
* The unique name identifying this key within the keyboard layout.
|
||||
*
|
||||
* @type {!string}
|
||||
*/
|
||||
this.name = name || template.name;
|
||||
|
||||
/**
|
||||
* The human-readable title that will be displayed to the user within the
|
||||
* key. If not provided, this will be derived from the key name.
|
||||
*
|
||||
* @type {!string}
|
||||
*/
|
||||
this.title = template.title || this.name;
|
||||
|
||||
/**
|
||||
* The keysym to be pressed/released when this key is pressed/released. If
|
||||
* not provided, this will be derived from the title if the title is a
|
||||
* single character.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.keysym = template.keysym || (function deriveKeysym(title) {
|
||||
|
||||
// Do not derive keysym if title is not exactly one character
|
||||
if (!title || title.length !== 1)
|
||||
return null;
|
||||
|
||||
// For characters between U+0000 and U+00FF, the keysym is the codepoint
|
||||
var charCode = title.charCodeAt(0);
|
||||
if (charCode >= 0x0000 && charCode <= 0x00FF)
|
||||
return charCode;
|
||||
|
||||
// For characters between U+0100 and U+10FFFF, the keysym is the codepoint or'd with 0x01000000
|
||||
if (charCode >= 0x0100 && charCode <= 0x10FFFF)
|
||||
return 0x01000000 | charCode;
|
||||
|
||||
// Unable to derive keysym
|
||||
return null;
|
||||
|
||||
})(this.title);
|
||||
|
||||
/**
|
||||
* The name of the modifier set when the key is pressed and cleared when
|
||||
* this key is released, if any. The names of modifiers are distinct from
|
||||
* the names of keys; both the "RightShift" and "LeftShift" keys may set
|
||||
* the "shift" modifier, for example. By default, the key will affect no
|
||||
* modifiers.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.modifier = template.modifier;
|
||||
|
||||
/**
|
||||
* An array containing the names of each modifier required for this key to
|
||||
* have an effect. For example, a lowercase letter may require nothing,
|
||||
* while an uppercase letter would require "shift", assuming the Shift key
|
||||
* is named "shift" within the layout. By default, the key will require
|
||||
* no modifiers.
|
||||
*
|
||||
* @type {!string[]}
|
||||
*/
|
||||
this.requires = template.requires || [];
|
||||
|
||||
};
|
||||
75
guacamole-common-js/src/main/webapp/modules/OutputStream.js
Normal file
75
guacamole-common-js/src/main/webapp/modules/OutputStream.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Abstract stream which can receive data.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.Client} client
|
||||
* The client owning this stream.
|
||||
*
|
||||
* @param {!number} index
|
||||
* The index of this stream.
|
||||
*/
|
||||
Guacamole.OutputStream = function(client, index) {
|
||||
|
||||
/**
|
||||
* Reference to this stream.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.OutputStream}
|
||||
*/
|
||||
var guac_stream = this;
|
||||
|
||||
/**
|
||||
* The index of this stream.
|
||||
* @type {!number}
|
||||
*/
|
||||
this.index = index;
|
||||
|
||||
/**
|
||||
* Fired whenever an acknowledgement is received from the server, indicating
|
||||
* that a stream operation has completed, or an error has occurred.
|
||||
*
|
||||
* @event
|
||||
* @param {!Guacamole.Status} status
|
||||
* The status of the operation.
|
||||
*/
|
||||
this.onack = null;
|
||||
|
||||
/**
|
||||
* Writes the given base64-encoded data to this stream as a blob.
|
||||
*
|
||||
* @param {!string} data
|
||||
* The base64-encoded data to send.
|
||||
*/
|
||||
this.sendBlob = function(data) {
|
||||
client.sendBlob(guac_stream.index, data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes this stream.
|
||||
*/
|
||||
this.sendEnd = function() {
|
||||
client.endStream(guac_stream.index);
|
||||
};
|
||||
|
||||
};
|
||||
348
guacamole-common-js/src/main/webapp/modules/Parser.js
Normal file
348
guacamole-common-js/src/main/webapp/modules/Parser.js
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Simple Guacamole protocol parser that invokes an oninstruction event when
|
||||
* full instructions are available from data received via receive().
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
Guacamole.Parser = function Parser() {
|
||||
|
||||
/**
|
||||
* Reference to this parser.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.Parser}
|
||||
*/
|
||||
var parser = this;
|
||||
|
||||
/**
|
||||
* Current buffer of received data. This buffer grows until a full
|
||||
* element is available. After a full element is available, that element
|
||||
* is flushed into the element buffer.
|
||||
*
|
||||
* @private
|
||||
* @type {!string}
|
||||
*/
|
||||
var buffer = '';
|
||||
|
||||
/**
|
||||
* Buffer of all received, complete elements. After an entire instruction
|
||||
* is read, this buffer is flushed, and a new instruction begins.
|
||||
*
|
||||
* @private
|
||||
* @type {!string[]}
|
||||
*/
|
||||
var elementBuffer = [];
|
||||
|
||||
/**
|
||||
* The character offset within the buffer of the current or most recently
|
||||
* parsed element's terminator. If sufficient characters have not yet been
|
||||
* read via calls to receive(), this may point to an offset well beyond the
|
||||
* end of the buffer. If no characters for an element have yet been read,
|
||||
* this will be -1.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var elementEnd = -1;
|
||||
|
||||
/**
|
||||
* The character offset within the buffer of the location that the parser
|
||||
* should start looking for the next element length search or next element
|
||||
* value.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var startIndex = 0;
|
||||
|
||||
/**
|
||||
* The declared length of the current element being parsed, in Unicode
|
||||
* codepoints.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var elementCodepoints = 0;
|
||||
|
||||
/**
|
||||
* The number of parsed characters that must accumulate in the begining of
|
||||
* the parse buffer before processing time is expended to truncate that
|
||||
* buffer and conserve memory.
|
||||
*
|
||||
* @private
|
||||
* @constant
|
||||
* @type {!number}
|
||||
*/
|
||||
var BUFFER_TRUNCATION_THRESHOLD = 4096;
|
||||
|
||||
/**
|
||||
* The lowest Unicode codepoint to require a surrogate pair when encoded
|
||||
* with UTF-16. In UTF-16, characters with codepoints at or above this
|
||||
* value are represented with a surrogate pair, while characters with
|
||||
* codepoints below this value are represented with a single character.
|
||||
*
|
||||
* @private
|
||||
* @constant
|
||||
* @type {!number}
|
||||
*/
|
||||
var MIN_CODEPOINT_REQUIRES_SURROGATE = 0x10000;
|
||||
|
||||
/**
|
||||
* Appends the given instruction data packet to the internal buffer of
|
||||
* this Guacamole.Parser, executing all completed instructions at
|
||||
* the beginning of this buffer, if any.
|
||||
*
|
||||
* @param {!string} packet
|
||||
* The instruction data to receive.
|
||||
*
|
||||
* @param {!boolean} [isBuffer=false]
|
||||
* Whether the provided data should be treated as an instruction buffer
|
||||
* that grows continuously. If true, the data provided to receive()
|
||||
* MUST always start with the data provided to the previous call. If
|
||||
* false (the default), only the new data should be provided to
|
||||
* receive(), and previously-received data will automatically be
|
||||
* buffered by the parser as needed.
|
||||
*/
|
||||
this.receive = function receive(packet, isBuffer) {
|
||||
|
||||
if (isBuffer)
|
||||
buffer = packet;
|
||||
|
||||
else {
|
||||
|
||||
// Truncate buffer as necessary
|
||||
if (startIndex > BUFFER_TRUNCATION_THRESHOLD && elementEnd >= startIndex) {
|
||||
|
||||
buffer = buffer.substring(startIndex);
|
||||
|
||||
// Reset parse relative to truncation
|
||||
elementEnd -= startIndex;
|
||||
startIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
// Append data to buffer ONLY if there is outstanding data present. It
|
||||
// is otherwise much faster to simply parse the received buffer as-is,
|
||||
// and tunnel implementations can take advantage of this by preferring
|
||||
// to send only complete instructions. Both the HTTP and WebSocket
|
||||
// tunnel implementations included with Guacamole already do this.
|
||||
if (buffer.length)
|
||||
buffer += packet;
|
||||
else
|
||||
buffer = packet;
|
||||
|
||||
}
|
||||
|
||||
// While search is within currently received data
|
||||
while (elementEnd < buffer.length) {
|
||||
|
||||
// If we are waiting for element data
|
||||
if (elementEnd >= startIndex) {
|
||||
|
||||
// If we have enough data in the buffer to fill the element
|
||||
// value, but the number of codepoints in the expected substring
|
||||
// containing the element value value is less that its declared
|
||||
// length, that can only be because the element contains
|
||||
// characters split between high and low surrogates, and the
|
||||
// actual end of the element value is further out. The minimum
|
||||
// number of additional characters that must be read to satisfy
|
||||
// the declared length is simply the difference between the
|
||||
// number of codepoints actually present vs. the expected
|
||||
// length.
|
||||
var codepoints = Guacamole.Parser.codePointCount(buffer, startIndex, elementEnd);
|
||||
if (codepoints < elementCodepoints) {
|
||||
elementEnd += elementCodepoints - codepoints;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the current element ends with a character involving both
|
||||
// a high and low surrogate, elementEnd points to the low
|
||||
// surrogate and NOT the element terminator. We must shift the
|
||||
// end and reevaluate.
|
||||
else if (elementCodepoints && buffer.codePointAt(elementEnd - 1) >= MIN_CODEPOINT_REQUIRES_SURROGATE) {
|
||||
elementEnd++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We now have enough data for the element. Parse.
|
||||
var element = buffer.substring(startIndex, elementEnd);
|
||||
var terminator = buffer.substring(elementEnd, elementEnd + 1);
|
||||
|
||||
// Add element to array
|
||||
elementBuffer.push(element);
|
||||
|
||||
// If last element, handle instruction
|
||||
if (terminator === ';') {
|
||||
|
||||
// Get opcode
|
||||
var opcode = elementBuffer.shift();
|
||||
|
||||
// Call instruction handler.
|
||||
if (parser.oninstruction !== null)
|
||||
parser.oninstruction(opcode, elementBuffer);
|
||||
|
||||
// Clear elements
|
||||
elementBuffer = [];
|
||||
|
||||
// Immediately truncate buffer if its contents have been
|
||||
// completely parsed, so that the next call to receive()
|
||||
// need not append to the buffer unnecessarily
|
||||
if (!isBuffer && elementEnd + 1 === buffer.length) {
|
||||
elementEnd = -1;
|
||||
buffer = '';
|
||||
}
|
||||
|
||||
}
|
||||
else if (terminator !== ',')
|
||||
throw new Error('Element terminator of instruction was not ";" nor ",".');
|
||||
|
||||
// Start searching for length at character after
|
||||
// element terminator
|
||||
startIndex = elementEnd + 1;
|
||||
|
||||
}
|
||||
|
||||
// Search for end of length
|
||||
var lengthEnd = buffer.indexOf('.', startIndex);
|
||||
if (lengthEnd !== -1) {
|
||||
|
||||
// Parse length
|
||||
elementCodepoints = parseInt(buffer.substring(elementEnd + 1, lengthEnd));
|
||||
if (isNaN(elementCodepoints))
|
||||
throw new Error('Non-numeric character in element length.');
|
||||
|
||||
// Calculate start of element
|
||||
startIndex = lengthEnd + 1;
|
||||
|
||||
// Calculate location of element terminator
|
||||
elementEnd = startIndex + elementCodepoints;
|
||||
|
||||
}
|
||||
|
||||
// If no period yet, continue search when more data
|
||||
// is received
|
||||
else {
|
||||
startIndex = buffer.length;
|
||||
break;
|
||||
}
|
||||
|
||||
} // end parse loop
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired once for every complete Guacamole instruction received, in order.
|
||||
*
|
||||
* @event
|
||||
* @param {!string} opcode
|
||||
* The Guacamole instruction opcode.
|
||||
*
|
||||
* @param {!string[]} parameters
|
||||
* The parameters provided for the instruction, if any.
|
||||
*/
|
||||
this.oninstruction = null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the number of Unicode codepoints (not code units) within the given
|
||||
* string. If character offsets are provided, only codepoints between those
|
||||
* offsets are counted. Unlike the length property of a string, this function
|
||||
* counts proper surrogate pairs as a single codepoint. High and low surrogate
|
||||
* characters that are not part of a proper surrogate pair are counted
|
||||
* separately as individual codepoints.
|
||||
*
|
||||
* @param {!string} str
|
||||
* The string whose contents should be inspected.
|
||||
*
|
||||
* @param {number} [start=0]
|
||||
* The index of the location in the given string where codepoint counting
|
||||
* should start. If omitted, counting will begin at the start of the
|
||||
* string.
|
||||
*
|
||||
* @param {number} [end]
|
||||
* The index of the first location in the given string after where counting
|
||||
* should stop (the character after the last character being counted). If
|
||||
* omitted, all characters after the start location will be counted.
|
||||
*
|
||||
* @returns {!number}
|
||||
* The number of Unicode codepoints within the requested portion of the
|
||||
* given string.
|
||||
*/
|
||||
Guacamole.Parser.codePointCount = function codePointCount(str, start, end) {
|
||||
|
||||
// Count only characters within the specified region
|
||||
str = str.substring(start || 0, end);
|
||||
|
||||
// Locate each proper Unicode surrogate pair (one high surrogate followed
|
||||
// by one low surrogate)
|
||||
var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
|
||||
|
||||
// Each surrogate pair represents a single codepoint but is represented by
|
||||
// two characters in a JavaScript string, and thus is counted twice toward
|
||||
// string length. Subtracting the number of surrogate pairs adjusts that
|
||||
// length value such that it gives us the number of codepoints.
|
||||
return str.length - (surrogatePairs ? surrogatePairs.length : 0);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts each of the values within the given array to strings, formatting
|
||||
* those strings as length-prefixed elements of a complete Guacamole
|
||||
* instruction.
|
||||
*
|
||||
* @param {!Array.<*>} elements
|
||||
* The values that should be encoded as the elements of a Guacamole
|
||||
* instruction. Order of these elements is preserved. This array MUST have
|
||||
* at least one element.
|
||||
*
|
||||
* @returns {!string}
|
||||
* A complete Guacamole instruction consisting of each of the provided
|
||||
* element values, in order.
|
||||
*/
|
||||
Guacamole.Parser.toInstruction = function toInstruction(elements) {
|
||||
|
||||
/**
|
||||
* Converts the given value to a length/string pair for use as an
|
||||
* element in a Guacamole instruction.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value
|
||||
* The value to convert.
|
||||
*
|
||||
* @return {!string}
|
||||
* The converted value.
|
||||
*/
|
||||
var toElement = function toElement(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 + ';';
|
||||
|
||||
};
|
||||
118
guacamole-common-js/src/main/webapp/modules/Position.js
Normal file
118
guacamole-common-js/src/main/webapp/modules/Position.js
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A position in 2-D space.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Guacamole.Position|object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* Guacamole.Position.
|
||||
*/
|
||||
Guacamole.Position = function Position(template) {
|
||||
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* The current X position, in pixels.
|
||||
*
|
||||
* @type {!number}
|
||||
* @default 0
|
||||
*/
|
||||
this.x = template.x || 0;
|
||||
|
||||
/**
|
||||
* The current Y position, in pixels.
|
||||
*
|
||||
* @type {!number}
|
||||
* @default 0
|
||||
*/
|
||||
this.y = template.y || 0;
|
||||
|
||||
/**
|
||||
* Assigns the position represented by the given element and
|
||||
* clientX/clientY coordinates. The clientX and clientY coordinates are
|
||||
* relative to the browser viewport and are commonly available within
|
||||
* JavaScript event objects. The final position is translated to
|
||||
* coordinates that are relative the given element.
|
||||
*
|
||||
* @param {!Element} element
|
||||
* The element the coordinates should be relative to.
|
||||
*
|
||||
* @param {!number} clientX
|
||||
* The viewport-relative X coordinate to translate.
|
||||
*
|
||||
* @param {!number} clientY
|
||||
* The viewport-relative Y coordinate to translate.
|
||||
*/
|
||||
this.fromClientPosition = function fromClientPosition(element, clientX, clientY) {
|
||||
|
||||
this.x = clientX - element.offsetLeft;
|
||||
this.y = clientY - element.offsetTop;
|
||||
|
||||
// This is all JUST so we can get the position within the element
|
||||
var parent = element.offsetParent;
|
||||
while (parent && !(parent === document.body)) {
|
||||
this.x -= parent.offsetLeft - parent.scrollLeft;
|
||||
this.y -= parent.offsetTop - parent.scrollTop;
|
||||
|
||||
parent = parent.offsetParent;
|
||||
}
|
||||
|
||||
// Element ultimately depends on positioning within document body,
|
||||
// take document scroll into account.
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a new {@link Guacamole.Position} representing the relative position
|
||||
* of the given clientX/clientY coordinates within the given element. The
|
||||
* clientX and clientY coordinates are relative to the browser viewport and are
|
||||
* commonly available within JavaScript event objects. The final position is
|
||||
* translated to coordinates that are relative the given element.
|
||||
*
|
||||
* @param {!Element} element
|
||||
* The element the coordinates should be relative to.
|
||||
*
|
||||
* @param {!number} clientX
|
||||
* The viewport-relative X coordinate to translate.
|
||||
*
|
||||
* @param {!number} clientY
|
||||
* The viewport-relative Y coordinate to translate.
|
||||
*
|
||||
* @returns {!Guacamole.Position}
|
||||
* A new Guacamole.Position representing the relative position of the given
|
||||
* client coordinates.
|
||||
*/
|
||||
Guacamole.Position.fromClientPosition = function fromClientPosition(element, clientX, clientY) {
|
||||
var position = new Guacamole.Position();
|
||||
position.fromClientPosition(element, clientX, clientY);
|
||||
return position;
|
||||
};
|
||||
146
guacamole-common-js/src/main/webapp/modules/RawAudioFormat.js
Normal file
146
guacamole-common-js/src/main/webapp/modules/RawAudioFormat.js
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A description of the format of raw PCM audio, such as that used by
|
||||
* Guacamole.RawAudioPlayer and Guacamole.RawAudioRecorder. This object
|
||||
* describes the number of bytes per sample, the number of channels, and the
|
||||
* overall sample rate.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!(Guacamole.RawAudioFormat|object)} template
|
||||
* The object whose properties should be copied into the corresponding
|
||||
* properties of the new Guacamole.RawAudioFormat.
|
||||
*/
|
||||
Guacamole.RawAudioFormat = function RawAudioFormat(template) {
|
||||
|
||||
/**
|
||||
* The number of bytes in each sample of audio data. This value is
|
||||
* independent of the number of channels.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.bytesPerSample = template.bytesPerSample;
|
||||
|
||||
/**
|
||||
* The number of audio channels (ie: 1 for mono, 2 for stereo).
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.channels = template.channels;
|
||||
|
||||
/**
|
||||
* The number of samples per second, per channel.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
this.rate = template.rate;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the given mimetype, returning a new Guacamole.RawAudioFormat
|
||||
* which describes the type of raw audio data represented by that mimetype. If
|
||||
* the mimetype is not a supported raw audio data mimetype, null is returned.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The audio mimetype to parse.
|
||||
*
|
||||
* @returns {Guacamole.RawAudioFormat}
|
||||
* A new Guacamole.RawAudioFormat which describes the type of raw
|
||||
* audio data represented by the given mimetype, or null if the given
|
||||
* mimetype is not supported.
|
||||
*/
|
||||
Guacamole.RawAudioFormat.parse = function parseFormat(mimetype) {
|
||||
|
||||
var bytesPerSample;
|
||||
|
||||
// Rate is absolutely required - if null is still present later, the
|
||||
// mimetype must not be supported
|
||||
var rate = null;
|
||||
|
||||
// Default for both "audio/L8" and "audio/L16" is one channel
|
||||
var channels = 1;
|
||||
|
||||
// "audio/L8" has one byte per sample
|
||||
if (mimetype.substring(0, 9) === 'audio/L8;') {
|
||||
mimetype = mimetype.substring(9);
|
||||
bytesPerSample = 1;
|
||||
}
|
||||
|
||||
// "audio/L16" has two bytes per sample
|
||||
else if (mimetype.substring(0, 10) === 'audio/L16;') {
|
||||
mimetype = mimetype.substring(10);
|
||||
bytesPerSample = 2;
|
||||
}
|
||||
|
||||
// All other types are unsupported
|
||||
else
|
||||
return null;
|
||||
|
||||
// Parse all parameters
|
||||
var parameters = mimetype.split(',');
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
|
||||
var parameter = parameters[i];
|
||||
|
||||
// All parameters must have an equals sign separating name from value
|
||||
var equals = parameter.indexOf('=');
|
||||
if (equals === -1)
|
||||
return null;
|
||||
|
||||
// Parse name and value from parameter string
|
||||
var name = parameter.substring(0, equals);
|
||||
var value = parameter.substring(equals+1);
|
||||
|
||||
// Handle each supported parameter
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// The rate parameter is required
|
||||
if (rate === null)
|
||||
return null;
|
||||
|
||||
// Return parsed format details
|
||||
return new Guacamole.RawAudioFormat({
|
||||
bytesPerSample : bytesPerSample,
|
||||
channels : channels,
|
||||
rate : rate
|
||||
});
|
||||
|
||||
};
|
||||
1348
guacamole-common-js/src/main/webapp/modules/SessionRecording.js
Normal file
1348
guacamole-common-js/src/main/webapp/modules/SessionRecording.js
Normal file
File diff suppressed because it is too large
Load Diff
322
guacamole-common-js/src/main/webapp/modules/Status.js
Normal file
322
guacamole-common-js/src/main/webapp/modules/Status.js
Normal file
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A Guacamole status. Each Guacamole status consists of a status code, defined
|
||||
* by the protocol, and an optional human-readable message, usually only
|
||||
* included for debugging convenience.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!number} code
|
||||
* The Guacamole status code, as defined by Guacamole.Status.Code.
|
||||
*
|
||||
* @param {string} [message]
|
||||
* An optional human-readable message.
|
||||
*/
|
||||
Guacamole.Status = function(code, message) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.Status.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.Status}
|
||||
*/
|
||||
var guac_status = this;
|
||||
|
||||
/**
|
||||
* The Guacamole status code.
|
||||
*
|
||||
* @see Guacamole.Status.Code
|
||||
* @type {!number}
|
||||
*/
|
||||
this.code = code;
|
||||
|
||||
/**
|
||||
* An arbitrary human-readable message associated with this status, if any.
|
||||
* The human-readable message is not required, and is generally provided
|
||||
* for debugging purposes only. For user feedback, it is better to translate
|
||||
* the Guacamole status code into a message.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.message = message;
|
||||
|
||||
/**
|
||||
* Returns whether this status represents an error.
|
||||
*
|
||||
* @returns {!boolean}
|
||||
* true if this status represents an error, false otherwise.
|
||||
*/
|
||||
this.isError = function() {
|
||||
return guac_status.code < 0 || guac_status.code > 0x00FF;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Enumeration of all Guacamole status codes.
|
||||
*/
|
||||
Guacamole.Status.Code = {
|
||||
|
||||
/**
|
||||
* The operation succeeded.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"SUCCESS": 0x0000,
|
||||
|
||||
/**
|
||||
* The requested operation is unsupported.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"UNSUPPORTED": 0x0100,
|
||||
|
||||
/**
|
||||
* The operation could not be performed due to an internal failure.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"SERVER_ERROR": 0x0200,
|
||||
|
||||
/**
|
||||
* The operation could not be performed as the server is busy.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"SERVER_BUSY": 0x0201,
|
||||
|
||||
/**
|
||||
* The operation could not be performed because the upstream server is not
|
||||
* responding.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"UPSTREAM_TIMEOUT": 0x0202,
|
||||
|
||||
/**
|
||||
* The operation was unsuccessful due to an error or otherwise unexpected
|
||||
* condition of the upstream server.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"UPSTREAM_ERROR": 0x0203,
|
||||
|
||||
/**
|
||||
* The operation could not be performed as the requested resource does not
|
||||
* exist.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"RESOURCE_NOT_FOUND": 0x0204,
|
||||
|
||||
/**
|
||||
* The operation could not be performed as the requested resource is
|
||||
* already in use.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"RESOURCE_CONFLICT": 0x0205,
|
||||
|
||||
/**
|
||||
* The operation could not be performed as the requested resource is now
|
||||
* closed.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"RESOURCE_CLOSED": 0x0206,
|
||||
|
||||
/**
|
||||
* The operation could not be performed because the upstream server does
|
||||
* not appear to exist.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"UPSTREAM_NOT_FOUND": 0x0207,
|
||||
|
||||
/**
|
||||
* The operation could not be performed because the upstream server is not
|
||||
* available to service the request.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"UPSTREAM_UNAVAILABLE": 0x0208,
|
||||
|
||||
/**
|
||||
* The session within the upstream server has ended because it conflicted
|
||||
* with another session.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"SESSION_CONFLICT": 0x0209,
|
||||
|
||||
/**
|
||||
* The session within the upstream server has ended because it appeared to
|
||||
* be inactive.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"SESSION_TIMEOUT": 0x020A,
|
||||
|
||||
/**
|
||||
* The session within the upstream server has been forcibly terminated.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"SESSION_CLOSED": 0x020B,
|
||||
|
||||
/**
|
||||
* The operation could not be performed because bad parameters were given.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"CLIENT_BAD_REQUEST": 0x0300,
|
||||
|
||||
/**
|
||||
* Permission was denied to perform the operation, as the user is not yet
|
||||
* authorized (not yet logged in, for example).
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"CLIENT_UNAUTHORIZED": 0x0301,
|
||||
|
||||
/**
|
||||
* Permission was denied to perform the operation, and this permission will
|
||||
* not be granted even if the user is authorized.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"CLIENT_FORBIDDEN": 0x0303,
|
||||
|
||||
/**
|
||||
* The client took too long to respond.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"CLIENT_TIMEOUT": 0x0308,
|
||||
|
||||
/**
|
||||
* The client sent too much data.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"CLIENT_OVERRUN": 0x030D,
|
||||
|
||||
/**
|
||||
* The client sent data of an unsupported or unexpected type.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"CLIENT_BAD_TYPE": 0x030F,
|
||||
|
||||
/**
|
||||
* The operation failed because the current client is already using too
|
||||
* many resources.
|
||||
*
|
||||
* @type {!number}
|
||||
*/
|
||||
"CLIENT_TOO_MANY": 0x031D
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the Guacamole protocol status code which most closely
|
||||
* represents the given HTTP status code.
|
||||
*
|
||||
* @param {!number} status
|
||||
* The HTTP status code to translate into a Guacamole protocol status
|
||||
* code.
|
||||
*
|
||||
* @returns {!number}
|
||||
* The Guacamole protocol status code which most closely represents the
|
||||
* given HTTP status code.
|
||||
*/
|
||||
Guacamole.Status.Code.fromHTTPCode = function fromHTTPCode(status) {
|
||||
|
||||
// Translate status codes with known equivalents
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
// Default all other codes to generic internal error
|
||||
return Guacamole.Status.Code.SERVER_ERROR;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the Guacamole protocol status code which most closely
|
||||
* represents the given WebSocket status code.
|
||||
*
|
||||
* @param {!number} code
|
||||
* The WebSocket status code to translate into a Guacamole protocol
|
||||
* status code.
|
||||
*
|
||||
* @returns {!number}
|
||||
* The Guacamole protocol status code which most closely represents the
|
||||
* given WebSocket status code.
|
||||
*/
|
||||
Guacamole.Status.Code.fromWebSocketCode = function fromWebSocketCode(code) {
|
||||
|
||||
// Translate status codes with known equivalents
|
||||
switch (code) {
|
||||
|
||||
// Successful disconnect (no error)
|
||||
case 1000: // Normal Closure
|
||||
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: // TLS Handshake
|
||||
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: // Bad Gateway
|
||||
return Guacamole.Status.Code.UPSTREAM_UNAVAILABLE;
|
||||
|
||||
}
|
||||
|
||||
// Default all other codes to generic internal error
|
||||
return Guacamole.Status.Code.SERVER_ERROR;
|
||||
|
||||
};
|
||||
89
guacamole-common-js/src/main/webapp/modules/StringReader.js
Normal file
89
guacamole-common-js/src/main/webapp/modules/StringReader.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A reader which automatically handles the given input stream, returning
|
||||
* strictly text data. Note that this object will overwrite any installed event
|
||||
* handlers on the given Guacamole.InputStream.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.InputStream} stream
|
||||
* The stream that data will be read from.
|
||||
*/
|
||||
Guacamole.StringReader = function(stream) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.InputStream.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.StringReader}
|
||||
*/
|
||||
var guac_reader = this;
|
||||
|
||||
/**
|
||||
* Parser for received UTF-8 data.
|
||||
*
|
||||
* @type {!Guacamole.UTF8Parser}
|
||||
*/
|
||||
var utf8Parser = new Guacamole.UTF8Parser();
|
||||
|
||||
/**
|
||||
* Wrapped Guacamole.ArrayBufferReader.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.ArrayBufferReader}
|
||||
*/
|
||||
var array_reader = new Guacamole.ArrayBufferReader(stream);
|
||||
|
||||
// Receive blobs as strings
|
||||
array_reader.ondata = function(buffer) {
|
||||
|
||||
// Decode UTF-8
|
||||
var text = utf8Parser.decode(buffer);
|
||||
|
||||
// Call handler, if present
|
||||
if (guac_reader.ontext)
|
||||
guac_reader.ontext(text);
|
||||
|
||||
};
|
||||
|
||||
// Simply call onend when end received
|
||||
array_reader.onend = function() {
|
||||
if (guac_reader.onend)
|
||||
guac_reader.onend();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired once for every blob of text data received.
|
||||
*
|
||||
* @event
|
||||
* @param {!string} text
|
||||
* The data packet received.
|
||||
*/
|
||||
this.ontext = null;
|
||||
|
||||
/**
|
||||
* Fired once this stream is finished and no further data will be written.
|
||||
* @event
|
||||
*/
|
||||
this.onend = null;
|
||||
|
||||
};
|
||||
205
guacamole-common-js/src/main/webapp/modules/StringWriter.js
Normal file
205
guacamole-common-js/src/main/webapp/modules/StringWriter.js
Normal file
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* A writer which automatically writes to the given output stream with text
|
||||
* data.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Guacamole.OutputStream} stream
|
||||
* The stream that data will be written to.
|
||||
*/
|
||||
Guacamole.StringWriter = function(stream) {
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.StringWriter.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.StringWriter}
|
||||
*/
|
||||
var guac_writer = this;
|
||||
|
||||
/**
|
||||
* Wrapped Guacamole.ArrayBufferWriter.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.ArrayBufferWriter}
|
||||
*/
|
||||
var array_writer = new Guacamole.ArrayBufferWriter(stream);
|
||||
|
||||
/**
|
||||
* Internal buffer for UTF-8 output.
|
||||
*
|
||||
* @private
|
||||
* @type {!Uint8Array}
|
||||
*/
|
||||
var buffer = new Uint8Array(8192);
|
||||
|
||||
/**
|
||||
* The number of bytes currently in the buffer.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var length = 0;
|
||||
|
||||
// Simply call onack for acknowledgements
|
||||
array_writer.onack = function(status) {
|
||||
if (guac_writer.onack)
|
||||
guac_writer.onack(status);
|
||||
};
|
||||
|
||||
/**
|
||||
* Expands the size of the underlying buffer by the given number of bytes,
|
||||
* updating the length appropriately.
|
||||
*
|
||||
* @private
|
||||
* @param {!number} bytes
|
||||
* The number of bytes to add to the underlying buffer.
|
||||
*/
|
||||
function __expand(bytes) {
|
||||
|
||||
// Resize buffer if more space needed
|
||||
if (length+bytes >= buffer.length) {
|
||||
var new_buffer = new Uint8Array((length+bytes)*2);
|
||||
new_buffer.set(buffer);
|
||||
buffer = new_buffer;
|
||||
}
|
||||
|
||||
length += bytes;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a single Unicode character to the current buffer, resizing the
|
||||
* buffer if necessary. The character will be encoded as UTF-8.
|
||||
*
|
||||
* @private
|
||||
* @param {!number} codepoint
|
||||
* The codepoint of the Unicode character to append.
|
||||
*/
|
||||
function __append_utf8(codepoint) {
|
||||
|
||||
var mask;
|
||||
var bytes;
|
||||
|
||||
// 1 byte
|
||||
if (codepoint <= 0x7F) {
|
||||
mask = 0x00;
|
||||
bytes = 1;
|
||||
}
|
||||
|
||||
// 2 byte
|
||||
else if (codepoint <= 0x7FF) {
|
||||
mask = 0xC0;
|
||||
bytes = 2;
|
||||
}
|
||||
|
||||
// 3 byte
|
||||
else if (codepoint <= 0xFFFF) {
|
||||
mask = 0xE0;
|
||||
bytes = 3;
|
||||
}
|
||||
|
||||
// 4 byte
|
||||
else if (codepoint <= 0x1FFFFF) {
|
||||
mask = 0xF0;
|
||||
bytes = 4;
|
||||
}
|
||||
|
||||
// If invalid codepoint, append replacement character
|
||||
else {
|
||||
__append_utf8(0xFFFD);
|
||||
return;
|
||||
}
|
||||
|
||||
// Offset buffer by size
|
||||
__expand(bytes);
|
||||
var offset = length - 1;
|
||||
|
||||
// Add trailing bytes, if any
|
||||
for (var i=1; i<bytes; i++) {
|
||||
buffer[offset--] = 0x80 | (codepoint & 0x3F);
|
||||
codepoint >>= 6;
|
||||
}
|
||||
|
||||
// Set initial byte
|
||||
buffer[offset] = mask | codepoint;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given string as UTF-8, returning an ArrayBuffer containing
|
||||
* the resulting bytes.
|
||||
*
|
||||
* @private
|
||||
* @param {!string} text
|
||||
* The string to encode as UTF-8.
|
||||
*
|
||||
* @return {!Uint8Array}
|
||||
* The encoded UTF-8 data.
|
||||
*/
|
||||
function __encode_utf8(text) {
|
||||
|
||||
// Fill buffer with UTF-8
|
||||
for (var i=0; i<text.length; i++) {
|
||||
var codepoint = text.charCodeAt(i);
|
||||
__append_utf8(codepoint);
|
||||
}
|
||||
|
||||
// Flush buffer
|
||||
if (length > 0) {
|
||||
var out_buffer = buffer.subarray(0, length);
|
||||
length = 0;
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the given text.
|
||||
*
|
||||
* @param {!string} text
|
||||
* The text to send.
|
||||
*/
|
||||
this.sendText = function(text) {
|
||||
if (text.length)
|
||||
array_writer.sendData(__encode_utf8(text));
|
||||
};
|
||||
|
||||
/**
|
||||
* Signals that no further text will be sent, effectively closing the
|
||||
* stream.
|
||||
*/
|
||||
this.sendEnd = function() {
|
||||
array_writer.sendEnd();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired for received data, if acknowledged by the server.
|
||||
*
|
||||
* @event
|
||||
* @param {!Guacamole.Status} status
|
||||
* The status of the operation.
|
||||
*/
|
||||
this.onack = null;
|
||||
|
||||
};
|
||||
280
guacamole-common-js/src/main/webapp/modules/Touch.js
Normal file
280
guacamole-common-js/src/main/webapp/modules/Touch.js
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Provides cross-browser multi-touch events for a given element. The events of
|
||||
* the given element are automatically populated with handlers that translate
|
||||
* touch events into a non-browser-specific event provided by the
|
||||
* Guacamole.Touch instance.
|
||||
*
|
||||
* @constructor
|
||||
* @augments Guacamole.Event.Target
|
||||
* @param {!Element} element
|
||||
* The Element to use to provide touch events.
|
||||
*/
|
||||
Guacamole.Touch = function Touch(element) {
|
||||
|
||||
Guacamole.Event.Target.call(this);
|
||||
|
||||
/**
|
||||
* Reference to this Guacamole.Touch.
|
||||
*
|
||||
* @private
|
||||
* @type {!Guacamole.Touch}
|
||||
*/
|
||||
var guacTouch = this;
|
||||
|
||||
/**
|
||||
* The default X/Y radius of each touch if the device or browser does not
|
||||
* expose the size of the contact area.
|
||||
*
|
||||
* @private
|
||||
* @constant
|
||||
* @type {!number}
|
||||
*/
|
||||
var DEFAULT_CONTACT_RADIUS = Math.floor(16 * window.devicePixelRatio);
|
||||
|
||||
/**
|
||||
* The set of all active touches, stored by their unique identifiers.
|
||||
*
|
||||
* @type {!Object.<Number, Guacamole.Touch.State>}
|
||||
*/
|
||||
this.touches = {};
|
||||
|
||||
/**
|
||||
* The number of active touches currently stored within
|
||||
* {@link Guacamole.Touch#touches touches}.
|
||||
*/
|
||||
this.activeTouches = 0;
|
||||
|
||||
/**
|
||||
* Fired whenever a new touch contact is initiated on the element
|
||||
* associated with this Guacamole.Touch.
|
||||
*
|
||||
* @event Guacamole.Touch#touchstart
|
||||
* @param {!Guacamole.Touch.Event} event
|
||||
* A {@link Guacamole.Touch.Event} object representing the "touchstart"
|
||||
* event.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired whenever an established touch contact moves within the element
|
||||
* associated with this Guacamole.Touch.
|
||||
*
|
||||
* @event Guacamole.Touch#touchmove
|
||||
* @param {!Guacamole.Touch.Event} event
|
||||
* A {@link Guacamole.Touch.Event} object representing the "touchmove"
|
||||
* event.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired whenever an established touch contact is lifted from the element
|
||||
* associated with this Guacamole.Touch.
|
||||
*
|
||||
* @event Guacamole.Touch#touchend
|
||||
* @param {!Guacamole.Touch.Event} event
|
||||
* A {@link Guacamole.Touch.Event} object representing the "touchend"
|
||||
* event.
|
||||
*/
|
||||
|
||||
element.addEventListener('touchstart', function touchstart(e) {
|
||||
|
||||
// Fire "ontouchstart" events for all new touches
|
||||
for (var i = 0; i < e.changedTouches.length; i++) {
|
||||
|
||||
var changedTouch = e.changedTouches[i];
|
||||
var identifier = changedTouch.identifier;
|
||||
|
||||
// Ignore duplicated touches
|
||||
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.0,
|
||||
force : changedTouch.force || 1.0 /* 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) {
|
||||
|
||||
// Fire "ontouchmove" events for all updated touches
|
||||
for (var i = 0; i < e.changedTouches.length; i++) {
|
||||
|
||||
var changedTouch = e.changedTouches[i];
|
||||
var identifier = changedTouch.identifier;
|
||||
|
||||
// Ignore any unrecognized touches
|
||||
var touch = guacTouch.touches[identifier];
|
||||
if (!touch)
|
||||
continue;
|
||||
|
||||
// Update force only if supported by browser (otherwise, assume
|
||||
// force is unchanged)
|
||||
if (changedTouch.force)
|
||||
touch.force = changedTouch.force;
|
||||
|
||||
// Update touch area, if supported by browser and device
|
||||
touch.angle = changedTouch.angle || 0.0;
|
||||
touch.radiusX = changedTouch.radiusX || DEFAULT_CONTACT_RADIUS;
|
||||
touch.radiusY = changedTouch.radiusY || DEFAULT_CONTACT_RADIUS;
|
||||
|
||||
// Update with any change in position
|
||||
touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY);
|
||||
guacTouch.dispatch(new Guacamole.Touch.Event('touchmove', e, touch));
|
||||
|
||||
}
|
||||
|
||||
}, false);
|
||||
|
||||
element.addEventListener('touchend', function touchstart(e) {
|
||||
|
||||
// Fire "ontouchend" events for all updated touches
|
||||
for (var i = 0; i < e.changedTouches.length; i++) {
|
||||
|
||||
var changedTouch = e.changedTouches[i];
|
||||
var identifier = changedTouch.identifier;
|
||||
|
||||
// Ignore any unrecognized touches
|
||||
var touch = guacTouch.touches[identifier];
|
||||
if (!touch)
|
||||
continue;
|
||||
|
||||
// Stop tracking this particular touch
|
||||
delete guacTouch.touches[identifier];
|
||||
guacTouch.activeTouches--;
|
||||
|
||||
// Touch has ended
|
||||
touch.force = 0.0;
|
||||
|
||||
// Update with final position
|
||||
touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY);
|
||||
guacTouch.dispatch(new Guacamole.Touch.Event('touchend', e, touch));
|
||||
|
||||
}
|
||||
|
||||
}, false);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The current state of a touch contact.
|
||||
*
|
||||
* @constructor
|
||||
* @augments Guacamole.Position
|
||||
* @param {Guacamole.Touch.State|object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* Guacamole.Touch.State.
|
||||
*/
|
||||
Guacamole.Touch.State = function State(template) {
|
||||
|
||||
template = template || {};
|
||||
|
||||
Guacamole.Position.call(this, template);
|
||||
|
||||
/**
|
||||
* An arbitrary integer ID which uniquely identifies this contact relative
|
||||
* to other active contacts.
|
||||
*
|
||||
* @type {!number}
|
||||
* @default 0
|
||||
*/
|
||||
this.id = template.id || 0;
|
||||
|
||||
/**
|
||||
* The Y radius of the ellipse covering the general area of the touch
|
||||
* contact, in pixels.
|
||||
*
|
||||
* @type {!number}
|
||||
* @default 0
|
||||
*/
|
||||
this.radiusX = template.radiusX || 0;
|
||||
|
||||
/**
|
||||
* The X radius of the ellipse covering the general area of the touch
|
||||
* contact, in pixels.
|
||||
*
|
||||
* @type {!number}
|
||||
* @default 0
|
||||
*/
|
||||
this.radiusY = template.radiusY || 0;
|
||||
|
||||
/**
|
||||
* The rough angle of clockwise rotation of the general area of the touch
|
||||
* contact, in degrees.
|
||||
*
|
||||
* @type {!number}
|
||||
* @default 0.0
|
||||
*/
|
||||
this.angle = template.angle || 0.0;
|
||||
|
||||
/**
|
||||
* The relative force exerted by the touch contact, where 0 is no force
|
||||
* (the touch has been lifted) and 1 is maximum force (the maximum amount
|
||||
* of force representable by the device).
|
||||
*
|
||||
* @type {!number}
|
||||
* @default 1.0
|
||||
*/
|
||||
this.force = template.force || 1.0;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* An event which represents a change in state of a single touch contact,
|
||||
* including the creation or removal of that contact. If multiple contacts are
|
||||
* involved in a touch interaction, each contact will be associated with its
|
||||
* own event.
|
||||
*
|
||||
* @constructor
|
||||
* @augments Guacamole.Event.DOMEvent
|
||||
* @param {!string} type
|
||||
* The name of the touch event type. Possible values are "touchstart",
|
||||
* "touchmove", and "touchend".
|
||||
*
|
||||
* @param {!TouchEvent} event
|
||||
* The DOM touch event that produced this Guacamole.Touch.Event.
|
||||
*
|
||||
* @param {!Guacamole.Touch.State} state
|
||||
* The state of the touch contact associated with this event.
|
||||
*/
|
||||
Guacamole.Touch.Event = function TouchEvent(type, event, state) {
|
||||
|
||||
Guacamole.Event.DOMEvent.call(this, type, [ event ]);
|
||||
|
||||
/**
|
||||
* The state of the touch contact associated with this event.
|
||||
*
|
||||
* @type {!Guacamole.Touch.State}
|
||||
*/
|
||||
this.state = state;
|
||||
|
||||
};
|
||||
1377
guacamole-common-js/src/main/webapp/modules/Tunnel.js
Normal file
1377
guacamole-common-js/src/main/webapp/modules/Tunnel.js
Normal file
File diff suppressed because it is too large
Load Diff
126
guacamole-common-js/src/main/webapp/modules/UTF8Parser.js
Normal file
126
guacamole-common-js/src/main/webapp/modules/UTF8Parser.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Parser that decodes UTF-8 text from a series of provided ArrayBuffers.
|
||||
* Multi-byte characters that continue from one buffer to the next are handled
|
||||
* correctly.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
Guacamole.UTF8Parser = function UTF8Parser() {
|
||||
|
||||
/**
|
||||
* The number of bytes remaining for the current codepoint.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var bytesRemaining = 0;
|
||||
|
||||
/**
|
||||
* The current codepoint value, as calculated from bytes read so far.
|
||||
*
|
||||
* @private
|
||||
* @type {!number}
|
||||
*/
|
||||
var codepoint = 0;
|
||||
|
||||
/**
|
||||
* Decodes the given UTF-8 data into a Unicode string, returning a string
|
||||
* containing all complete UTF-8 characters within the provided data. The
|
||||
* data may end in the middle of a multi-byte character, in which case the
|
||||
* complete character will be returned from a later call to decode() after
|
||||
* enough bytes have been provided.
|
||||
*
|
||||
* @private
|
||||
* @param {!ArrayBuffer} buffer
|
||||
* Arbitrary UTF-8 data.
|
||||
*
|
||||
* @return {!string}
|
||||
* The decoded Unicode string.
|
||||
*/
|
||||
this.decode = function decode(buffer) {
|
||||
|
||||
var text = '';
|
||||
|
||||
var bytes = new Uint8Array(buffer);
|
||||
for (var i=0; i<bytes.length; i++) {
|
||||
|
||||
// Get current byte
|
||||
var value = bytes[i];
|
||||
|
||||
// Start new codepoint if nothing yet read
|
||||
if (bytesRemaining === 0) {
|
||||
|
||||
// 1 byte (0xxxxxxx)
|
||||
if ((value | 0x7F) === 0x7F)
|
||||
text += String.fromCharCode(value);
|
||||
|
||||
// 2 byte (110xxxxx)
|
||||
else if ((value | 0x1F) === 0xDF) {
|
||||
codepoint = value & 0x1F;
|
||||
bytesRemaining = 1;
|
||||
}
|
||||
|
||||
// 3 byte (1110xxxx)
|
||||
else if ((value | 0x0F )=== 0xEF) {
|
||||
codepoint = value & 0x0F;
|
||||
bytesRemaining = 2;
|
||||
}
|
||||
|
||||
// 4 byte (11110xxx)
|
||||
else if ((value | 0x07) === 0xF7) {
|
||||
codepoint = value & 0x07;
|
||||
bytesRemaining = 3;
|
||||
}
|
||||
|
||||
// Invalid byte
|
||||
else
|
||||
text += '\uFFFD';
|
||||
|
||||
}
|
||||
|
||||
// Continue existing codepoint (10xxxxxx)
|
||||
else if ((value | 0x3F) === 0xBF) {
|
||||
|
||||
codepoint = (codepoint << 6) | (value & 0x3F);
|
||||
bytesRemaining--;
|
||||
|
||||
// Write codepoint if finished
|
||||
if (bytesRemaining === 0)
|
||||
text += String.fromCharCode(codepoint);
|
||||
|
||||
}
|
||||
|
||||
// Invalid byte
|
||||
else {
|
||||
bytesRemaining = 0;
|
||||
text += '\uFFFD';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return text;
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
30
guacamole-common-js/src/main/webapp/modules/Version.js
Normal file
30
guacamole-common-js/src/main/webapp/modules/Version.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* The unique ID of this version of the Guacamole JavaScript API. This ID will
|
||||
* be the version string of the guacamole-common-js Maven project, and can be
|
||||
* used in downstream applications as a sanity check that the proper version
|
||||
* of the APIs is being used (in case an older version is cached, for example).
|
||||
*
|
||||
* @type {!string}
|
||||
*/
|
||||
Guacamole.API_VERSION = "1.6.0";
|
||||
108
guacamole-common-js/src/main/webapp/modules/VideoPlayer.js
Normal file
108
guacamole-common-js/src/main/webapp/modules/VideoPlayer.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var Guacamole = Guacamole || {};
|
||||
|
||||
/**
|
||||
* Abstract video player which accepts, queues and plays back arbitrary video
|
||||
* data. It is up to implementations of this class to provide some means of
|
||||
* handling a provided Guacamole.InputStream and rendering the received data to
|
||||
* the provided Guacamole.Display.VisibleLayer. Data received along the
|
||||
* provided stream is to be played back immediately.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
Guacamole.VideoPlayer = function VideoPlayer() {
|
||||
|
||||
/**
|
||||
* Notifies this Guacamole.VideoPlayer that all video up to the current
|
||||
* point in time has been given via the underlying stream, and that any
|
||||
* difference in time between queued video data and the current time can be
|
||||
* considered latency.
|
||||
*/
|
||||
this.sync = function sync() {
|
||||
// Default implementation - do nothing
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether the given mimetype is supported by any built-in
|
||||
* implementation of Guacamole.VideoPlayer, and thus will be properly handled
|
||||
* by Guacamole.VideoPlayer.getInstance().
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype to check.
|
||||
*
|
||||
* @returns {!boolean}
|
||||
* true if the given mimetype is supported by any built-in
|
||||
* Guacamole.VideoPlayer, false otherwise.
|
||||
*/
|
||||
Guacamole.VideoPlayer.isSupportedType = function isSupportedType(mimetype) {
|
||||
|
||||
// There are currently no built-in video players (and therefore no
|
||||
// supported types)
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of all mimetypes supported by any built-in
|
||||
* Guacamole.VideoPlayer, in rough order of priority. Beware that only the core
|
||||
* mimetypes themselves will be listed. Any mimetype parameters, even required
|
||||
* ones, will not be included in the list.
|
||||
*
|
||||
* @returns {!string[]}
|
||||
* A list of all mimetypes supported by any built-in Guacamole.VideoPlayer,
|
||||
* excluding any parameters.
|
||||
*/
|
||||
Guacamole.VideoPlayer.getSupportedTypes = function getSupportedTypes() {
|
||||
|
||||
// There are currently no built-in video players (and therefore no
|
||||
// supported types)
|
||||
return [];
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an instance of Guacamole.VideoPlayer providing support for the given
|
||||
* video format. If support for the given video format is not available, null
|
||||
* is returned.
|
||||
*
|
||||
* @param {!Guacamole.InputStream} stream
|
||||
* The Guacamole.InputStream to read video data from.
|
||||
*
|
||||
* @param {!Guacamole.Display.VisibleLayer} layer
|
||||
* The destination layer in which this Guacamole.VideoPlayer should play
|
||||
* the received video data.
|
||||
*
|
||||
* @param {!string} mimetype
|
||||
* The mimetype of the video data in the provided stream.
|
||||
*
|
||||
* @return {Guacamole.VideoPlayer}
|
||||
* A Guacamole.VideoPlayer instance supporting the given mimetype and
|
||||
* reading from the given stream, or null if support for the given mimetype
|
||||
* is absent.
|
||||
*/
|
||||
Guacamole.VideoPlayer.getInstance = function getInstance(stream, layer, mimetype) {
|
||||
|
||||
// There are currently no built-in video players
|
||||
return null;
|
||||
|
||||
};
|
||||
141
guacamole-common-js/src/test/javascript/EventSpec.js
Normal file
141
guacamole-common-js/src/test/javascript/EventSpec.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/* global Guacamole, jasmine, expect */
|
||||
|
||||
describe("Guacamole.Event", function EventSpec() {
|
||||
|
||||
/**
|
||||
* Test subclass of {@link Guacamole.Event} which provides a single
|
||||
* "value" property supports an "ontest" legacy event handler.
|
||||
*
|
||||
* @constructor
|
||||
* @augments Guacamole.Event
|
||||
* @param {object} value
|
||||
* An arbitrary value to expose to the handler of the event.
|
||||
*/
|
||||
var TestEvent = function TestEvent(value) {
|
||||
|
||||
Guacamole.Event.apply(this, [ 'test' ]);
|
||||
|
||||
/**
|
||||
* An arbitrary value to expose to the handler of this event.
|
||||
*
|
||||
* @type {object}
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
this.invokeLegacyHandler = function invokeLegacyHandler(target) {
|
||||
if (target.ontest)
|
||||
target.ontest(value);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Event target instance which will receive each fired {@link TestEvent}.
|
||||
*
|
||||
* @type {Guacamole.Event.Target}
|
||||
*/
|
||||
var eventTarget;
|
||||
|
||||
beforeEach(function() {
|
||||
eventTarget = new Guacamole.Event.Target();
|
||||
});
|
||||
|
||||
describe("when an event is dispatched", function(){
|
||||
|
||||
it("should invoke the legacy handler for matching events", function() {
|
||||
|
||||
eventTarget.ontest = jasmine.createSpy('ontest');
|
||||
eventTarget.dispatch(new TestEvent('event1'));
|
||||
expect(eventTarget.ontest).toHaveBeenCalledWith('event1');
|
||||
|
||||
});
|
||||
|
||||
it("should invoke all listeners for matching events", function() {
|
||||
|
||||
var listener1 = jasmine.createSpy('listener1');
|
||||
var listener2 = jasmine.createSpy('listener2');
|
||||
|
||||
eventTarget.on('test', listener1);
|
||||
eventTarget.on('test', listener2);
|
||||
|
||||
eventTarget.dispatch(new TestEvent('event2'));
|
||||
|
||||
expect(listener1).toHaveBeenCalledWith(jasmine.objectContaining({ type : 'test', value : 'event2' }), eventTarget);
|
||||
expect(listener2).toHaveBeenCalledWith(jasmine.objectContaining({ type : 'test', value : 'event2' }), eventTarget);
|
||||
|
||||
});
|
||||
|
||||
it("should not invoke any listeners for non-matching events", function() {
|
||||
|
||||
var listener1 = jasmine.createSpy('listener1');
|
||||
var listener2 = jasmine.createSpy('listener2');
|
||||
|
||||
eventTarget.on('test2', listener1);
|
||||
eventTarget.on('test2', listener2);
|
||||
|
||||
eventTarget.dispatch(new TestEvent('event3'));
|
||||
|
||||
expect(listener1).not.toHaveBeenCalled();
|
||||
expect(listener2).not.toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
it("should not invoke any listeners that have been removed", function() {
|
||||
|
||||
var listener1 = jasmine.createSpy('listener1');
|
||||
var listener2 = jasmine.createSpy('listener2');
|
||||
|
||||
eventTarget.on('test', listener1);
|
||||
eventTarget.on('test', listener2);
|
||||
eventTarget.off('test', listener1);
|
||||
|
||||
eventTarget.dispatch(new TestEvent('event4'));
|
||||
|
||||
expect(listener1).not.toHaveBeenCalled();
|
||||
expect(listener2).toHaveBeenCalledWith(jasmine.objectContaining({ type : 'test', value : 'event4' }), eventTarget);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("when listeners are removed", function(){
|
||||
|
||||
it("should return whether a listener is successfully removed", function() {
|
||||
|
||||
var listener1 = jasmine.createSpy('listener1');
|
||||
var listener2 = jasmine.createSpy('listener2');
|
||||
|
||||
eventTarget.on('test', listener1);
|
||||
eventTarget.on('test', listener2);
|
||||
|
||||
expect(eventTarget.off('test', listener1)).toBe(true);
|
||||
expect(eventTarget.off('test', listener1)).toBe(false);
|
||||
expect(eventTarget.off('test', listener2)).toBe(true);
|
||||
expect(eventTarget.off('test', listener2)).toBe(false);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
221
guacamole-common-js/src/test/javascript/ParserSpec.js
Normal file
221
guacamole-common-js/src/test/javascript/ParserSpec.js
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/* global Guacamole, jasmine, expect */
|
||||
|
||||
describe('Guacamole.Parser', function ParserSpec() {
|
||||
|
||||
/**
|
||||
* A single Unicode high surrogate character (any character between U+D800
|
||||
* and U+DB7F).
|
||||
*
|
||||
* @constant
|
||||
* @type {!string}
|
||||
*/
|
||||
const HIGH_SURROGATE = '\uD802';
|
||||
|
||||
/**
|
||||
* A single Unicode low surrogate character (any character between U+DC00
|
||||
* and U+DFFF).
|
||||
*
|
||||
* @constant
|
||||
* @type {!string}
|
||||
*/
|
||||
const LOW_SURROGATE = '\uDF00';
|
||||
|
||||
/**
|
||||
* A Unicode surrogate pair, consisting of a high and low surrogate.
|
||||
*
|
||||
* @constant
|
||||
* @type {!string}
|
||||
*/
|
||||
const SURROGATE_PAIR = HIGH_SURROGATE + LOW_SURROGATE;
|
||||
|
||||
/**
|
||||
* A 4-character test string containing Unicode characters that require
|
||||
* multiple bytes when encoded as UTF-8, including at least one character
|
||||
* that is encoded as a surrogate pair in UTF-16.
|
||||
*
|
||||
* @constant
|
||||
* @type {!string}
|
||||
*/
|
||||
const UTF8_MULTIBYTE = '\u72AC' + SURROGATE_PAIR + 'z\u00C1';
|
||||
|
||||
/**
|
||||
* The Guacamole.Parser instance to test. This instance is (re)created prior
|
||||
* to each test via beforeEach().
|
||||
*
|
||||
* @type {Guacamole.Parser}
|
||||
*/
|
||||
var parser;
|
||||
|
||||
// Provide each test with a fresh parser
|
||||
beforeEach(function() {
|
||||
parser = new Guacamole.Parser();
|
||||
});
|
||||
|
||||
// Empty instruction
|
||||
describe('when an empty instruction is received', function() {
|
||||
|
||||
it('should parse the single empty opcode and invoke oninstruction', function() {
|
||||
parser.oninstruction = jasmine.createSpy('oninstruction');
|
||||
parser.receive('0.;');
|
||||
expect(parser.oninstruction).toHaveBeenCalledOnceWith('', [ ]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Instruction using basic Latin characters
|
||||
describe('when an instruction is containing only basic Latin characters', function() {
|
||||
|
||||
it('should correctly parse each element and invoke oninstruction', function() {
|
||||
parser.oninstruction = jasmine.createSpy('oninstruction');
|
||||
parser.receive('5.test2,'
|
||||
+ '10.hellohello,'
|
||||
+ '15.worldworldworld;'
|
||||
);
|
||||
expect(parser.oninstruction).toHaveBeenCalledOnceWith('test2', [
|
||||
'hellohello',
|
||||
'worldworldworld'
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Instruction using characters requiring multiple bytes in UTF-8 and
|
||||
// surrogate pairs in UTF-16, including an element ending with a surrogate
|
||||
// pair
|
||||
describe('when an instruction is received containing elements that '
|
||||
+ 'contain characters involving surrogate pairs', function() {
|
||||
|
||||
it('should correctly parse each element and invoke oninstruction', function() {
|
||||
parser.oninstruction = jasmine.createSpy('oninstruction');
|
||||
parser.receive('4.test,'
|
||||
+ '6.a' + UTF8_MULTIBYTE + 'b,'
|
||||
+ '5.1234' + SURROGATE_PAIR + ','
|
||||
+ '10.a' + UTF8_MULTIBYTE + UTF8_MULTIBYTE + 'c;'
|
||||
);
|
||||
expect(parser.oninstruction).toHaveBeenCalledOnceWith('test', [
|
||||
'a' + UTF8_MULTIBYTE + 'b',
|
||||
'1234' + SURROGATE_PAIR,
|
||||
'a' + UTF8_MULTIBYTE + UTF8_MULTIBYTE + 'c'
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Instruction with an element values ending with an incomplete surrogate
|
||||
// pair (high or low surrogate only)
|
||||
describe('when an instruction is received containing elements that end '
|
||||
+ 'with incomplete surrogate pairs', function() {
|
||||
|
||||
it('should correctly parse each element and invoke oninstruction', function() {
|
||||
parser.oninstruction = jasmine.createSpy('oninstruction');
|
||||
parser.receive('4.test,'
|
||||
+ '5.1234' + HIGH_SURROGATE + ','
|
||||
+ '5.4567' + LOW_SURROGATE + ';'
|
||||
);
|
||||
expect(parser.oninstruction).toHaveBeenCalledOnceWith('test', [
|
||||
'1234' + HIGH_SURROGATE,
|
||||
'4567' + LOW_SURROGATE
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Instruction with element values containing incomplete surrogate pairs,
|
||||
describe('when an instruction is received containing incomplete surrogate pairs', function() {
|
||||
|
||||
it('should correctly parse each element and invoke oninstruction', function() {
|
||||
parser.oninstruction = jasmine.createSpy('oninstruction');
|
||||
parser.receive('5.te' + LOW_SURROGATE + 'st,'
|
||||
+ '5.12' + HIGH_SURROGATE + '3' + LOW_SURROGATE + ','
|
||||
+ '6.5' + LOW_SURROGATE + LOW_SURROGATE + '4' + HIGH_SURROGATE + HIGH_SURROGATE + ','
|
||||
+ '10.' + UTF8_MULTIBYTE + HIGH_SURROGATE + UTF8_MULTIBYTE + HIGH_SURROGATE + ';',
|
||||
);
|
||||
expect(parser.oninstruction).toHaveBeenCalledOnceWith('te' + LOW_SURROGATE + 'st', [
|
||||
'12' + HIGH_SURROGATE + '3' + LOW_SURROGATE,
|
||||
'5' + LOW_SURROGATE + LOW_SURROGATE + '4' + HIGH_SURROGATE + HIGH_SURROGATE,
|
||||
UTF8_MULTIBYTE + HIGH_SURROGATE + UTF8_MULTIBYTE + HIGH_SURROGATE
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Instruction fed via blocks of characters that accumulate via an external
|
||||
// buffer
|
||||
describe('when an instruction is received via an external buffer', function() {
|
||||
|
||||
it('should correctly parse each element and invoke oninstruction once ready', function() {
|
||||
parser.oninstruction = jasmine.createSpy('oninstruction');
|
||||
parser.receive('5.test2,10.hello', true);
|
||||
expect(parser.oninstruction).not.toHaveBeenCalled();
|
||||
parser.receive('5.test2,10.hellohello,15', true);
|
||||
expect(parser.oninstruction).not.toHaveBeenCalled();
|
||||
parser.receive('5.test2,10.hellohello,15.worldworldworld;', true);
|
||||
expect(parser.oninstruction).toHaveBeenCalledOnceWith('test2', [ 'hellohello', 'worldworldworld' ]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Verify codePointCount() utility function correctly counts codepoints in
|
||||
// full strings
|
||||
describe('when a string is provided to codePointCount()', function() {
|
||||
|
||||
it('should return the number of codepoints in that string', function() {
|
||||
expect(Guacamole.Parser.codePointCount('')).toBe(0);
|
||||
expect(Guacamole.Parser.codePointCount('test string')).toBe(11);
|
||||
expect(Guacamole.Parser.codePointCount('surrogate' + SURROGATE_PAIR + 'pair')).toBe(14);
|
||||
expect(Guacamole.Parser.codePointCount('missing' + HIGH_SURROGATE + 'surrogates' + LOW_SURROGATE)).toBe(19);
|
||||
expect(Guacamole.Parser.codePointCount(HIGH_SURROGATE + LOW_SURROGATE + HIGH_SURROGATE)).toBe(2);
|
||||
expect(Guacamole.Parser.codePointCount(HIGH_SURROGATE + HIGH_SURROGATE + LOW_SURROGATE)).toBe(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Verify codePointCount() utility function correctly counts codepoints in
|
||||
// substrings
|
||||
describe('when a substring is provided to codePointCount()', function() {
|
||||
|
||||
it('should return the number of codepoints in that substring', function() {
|
||||
expect(Guacamole.Parser.codePointCount('test string', 0)).toBe(11);
|
||||
expect(Guacamole.Parser.codePointCount('surrogate' + SURROGATE_PAIR + 'pair', 5)).toBe(9);
|
||||
expect(Guacamole.Parser.codePointCount('missing' + HIGH_SURROGATE + 'surrogates' + LOW_SURROGATE, 2, 17)).toBe(15);
|
||||
expect(Guacamole.Parser.codePointCount(HIGH_SURROGATE + LOW_SURROGATE + HIGH_SURROGATE, 0, 2)).toBe(1);
|
||||
expect(Guacamole.Parser.codePointCount(HIGH_SURROGATE + HIGH_SURROGATE + LOW_SURROGATE, 1, 2)).toBe(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Verify toInstruction() utility function correctly encodes instructions
|
||||
describe('when an array of elements is provided to toInstruction()', function() {
|
||||
|
||||
it('should return a correctly-encoded Guacamole instruction', function() {
|
||||
expect(Guacamole.Parser.toInstruction([ 'test', 'instruction' ])).toBe('4.test,11.instruction;');
|
||||
expect(Guacamole.Parser.toInstruction([ 'test' + SURROGATE_PAIR, 'instruction' ]))
|
||||
.toBe('5.test' + SURROGATE_PAIR + ',11.instruction;');
|
||||
expect(Guacamole.Parser.toInstruction([ UTF8_MULTIBYTE, HIGH_SURROGATE + 'xyz' + LOW_SURROGATE ]))
|
||||
.toBe('4.' + UTF8_MULTIBYTE + ',5.' + HIGH_SURROGATE + 'xyz' + LOW_SURROGATE + ';');
|
||||
expect(Guacamole.Parser.toInstruction([ UTF8_MULTIBYTE, LOW_SURROGATE + 'xyz' + HIGH_SURROGATE ]))
|
||||
.toBe('4.' + UTF8_MULTIBYTE + ',5.' + LOW_SURROGATE + 'xyz' + HIGH_SURROGATE + ';');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/* global Guacamole, expect */
|
||||
|
||||
describe('Guacamole.SessionRecording', function() {
|
||||
|
||||
it('should create new SessionRecording instance from Blob', function() {
|
||||
|
||||
const blob = new Blob(['4.size,1.1,1.0,1.0;']);
|
||||
const recording = new Guacamole.SessionRecording(blob);
|
||||
expect(recording).not.toBeNull();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
42
guacamole-common-js/static.xml
Normal file
42
guacamole-common-js/static.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<assembly>
|
||||
<baseDirectory>guacamole-common-js</baseDirectory>
|
||||
<id>guacamole-common-js</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>src/main/webapp/modules/</directory>
|
||||
<includes>
|
||||
<include>*.js</include>
|
||||
</includes>
|
||||
<outputDirectory>modules/</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/${project.name}-${project.version}/</directory>
|
||||
<includes>
|
||||
<include>*.js</include>
|
||||
</includes>
|
||||
<outputDirectory></outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
Reference in New Issue
Block a user