├── .github
└── workflows
│ └── webapp.yml
├── .gitignore
├── LICENSE
├── README.md
├── README_OLD.md
├── gulpfile.js
├── html-minifier.conf
├── package-lock.json
├── package.json
└── src
├── html
├── donate.html
├── dot.gif
├── faq.html
├── guacamole.min.js
├── index.html
├── main.css
├── news.html
├── notify.m4a
├── notify.mp3
├── notify.ogg
├── rules.html
└── test.png
├── js
├── collab-vm
│ ├── collab-vm.js
│ ├── common.js
│ ├── en-us-qwerty.js
│ └── jquery.history.js
└── guacamole
│ ├── ArrayBufferReader.js
│ ├── ArrayBufferWriter.js
│ ├── AudioChannel.js
│ ├── BlobReader.js
│ ├── Client.js
│ ├── Display.js
│ ├── InputStream.js
│ ├── IntegerPool.js
│ ├── Keyboard.js
│ ├── Layer.js
│ ├── Mouse.js
│ ├── OnScreenKeyboard.js
│ ├── OutputStream.js
│ ├── Parser.js
│ ├── Status.js
│ ├── StringReader.js
│ ├── StringWriter.js
│ ├── Tunnel.js
│ └── Version.js
├── res
├── dot.gif
├── main.css
├── notify.m4a
├── notify.mp3
└── notify.ogg
└── templates
├── navbar.html
└── win-logo-ascii.html
/.github/workflows/webapp.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: "[collab-vm-web-app] - Node.js Gulp"
5 |
6 | on:
7 | push:
8 | branches: [ master, semantic ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | check_skip:
14 | runs-on: ubuntu-latest
15 | outputs:
16 | skip: ${{ steps.result_step.outputs.result }}
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | fetch-depth: 0
21 | - uses: mstachniuk/ci-skip@master
22 | with:
23 | commit-filter: '[skip ci];[ci skip];[no ci]'
24 | commit-filter-separator: ';'
25 | - run: echo "::set-output name=result::${CI_SKIP}"
26 | id: result_step
27 |
28 | gulp:
29 | needs: check_skip
30 | if: ${{ needs.check_skip.outputs.skip == 'false' }}
31 | runs-on: ubuntu-latest
32 | strategy:
33 | matrix:
34 | node-version: [10.x, 12.x, 14.x]
35 | steps:
36 | - uses: actions/checkout@v2
37 | - name: Use Node.js ${{ matrix.node-version }}
38 | uses: actions/setup-node@v1
39 | with:
40 | node-version: ${{ matrix.node-version }}
41 | - run: npm install --global gulp-cli
42 | - run: npm install
43 | - run: gulp
44 | - name: Upload Artifacts
45 | uses: 'actions/upload-artifact@v2'
46 | with:
47 | name: Web App built by Node ${{ matrix.node-version }}
48 | path: './build/*'
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/**/*
2 | node_modules/**/*
3 | build.*
4 |
5 | # HISTORIC old drop that the webapp doesn't use
6 | src/html/guacamole.min.js
7 | tmp/
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # collab-vm-classic-web-app
2 |
3 | This is the archived source code of the original CollabVM webapp. It is no longer maintained by computernewb and is not recommended for use.
4 |
5 | For the current webapp used at /collab-vm/, see [collab-vm-1.2-webapp](https://github.com/computernewb/collab-vm-1.2-webapp/)
6 |
7 | For the maintained fork of this webapp used at /collab-vm/classic/, see [yellows111/collab-vm-web-app](https://github.com/yellows111/collab-vm-web-app/)
--------------------------------------------------------------------------------
/README_OLD.md:
--------------------------------------------------------------------------------
1 | ![[collab-vm-web-app] - Node.js Gulp](https://github.com/yellows111/collab-vm-web-app/workflows/%5Bcollab-vm-web-app%5D%20-%20Node.js%20Gulp/badge.svg)
2 |
3 | # collab-vm-web-app
4 | The Collab VM Web App powers the website-portion of Collab VM and the Virus Farm.
5 |
6 | # What it contains
7 | * The scripts that power CollabVM.
8 |
9 | # Compilation requirements
10 | To compile this, you need a *nix or a Windows-like environment. This has only been compiled on x64 (amd64), but it should work on other architectures (like x86 or arm). You need a system that supports node.js and npm. node.js and npm run on Windows, Mac OS X, virtually any Linux distribution, FreeBSD, OpenBSD, Android, NetBSD, OpenSUSE, SmartOS, WebOS, NonStop OS, and other operating systems. (However, compilation has only been tested on x64 Windows 7 and x64 Ubuntu 14.04 and 16.04).
11 |
12 | That's basically all the requirements needed.
13 |
14 | # Compilation instructions
15 |
16 | To build the web app, follow these steps.
17 |
18 | * Step 1: Install node.js and npm. Windows and Mac OS X installers can be found here: https://nodejs.org/en/download/. For Linux, BSD, and other operating systems, visit this page: https://nodejs.org/en/download/package-manager/.
19 | * Step 2: Make sure npm is updated fully by typing npm install --global npm in the terminal.
20 | * Step 3: Install gulp from npm. You can do this by doing: npm install --global gulp-cli
21 | * Step 4: Open the web-app directory and type: npm install
22 | * Step 5: (Optionally) change the settings in common.js or set some of the DEBUG settings to true in collab-vm.js
23 | * Step 6: If you're testing this locally, you need to set DEBUG = true in collab-vm.js and do: gulp guacamole. (If you're not testing this locally, this isn't required.)
24 | * Step 7: Type gulp (or you can compile them indivdiually by typing gulp js, gulp html, etc).
25 |
26 | # License
27 | Collab VM Web App, as well as Collab VM Admin Web App and Collab VM Server are licensed under the [Apache License](https://www.apache.org/licenses/LICENSE-2.0).
28 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp'),
2 | rename = require('gulp-rename'),
3 | concat = require('gulp-concat'),
4 | inject = require('gulp-inject'),
5 | htmlmin = require('gulp-htmlmin'),
6 | terser = require('gulp-terser'),
7 | rollup = require('gulp-rollup'),
8 | gap = require('gulp-append-prepend')
9 | sourcemaps = require('gulp-sourcemaps')
10 | fs = require('fs');
11 |
12 | // Paths
13 | const paths = {
14 | html: "src/html/*.html",
15 |
16 | // Guacamole code.
17 | // This is bundleified using a abomination
18 | guacamole: [
19 | 'src/js/guacamole/Display.js',
20 | 'src/js/guacamole/Parser.js',
21 | 'src/js/guacamole/Client.js',
22 | 'src/js/guacamole/OnScreenKeyboard.js',
23 | 'src/js/guacamole/ArrayBufferReader.js',
24 | 'src/js/guacamole/OutputStream.js',
25 | 'src/js/guacamole/BlobReader.js',
26 | 'src/js/guacamole/StringReader.js',
27 | 'src/js/guacamole/Mouse.js',
28 | 'src/js/guacamole/Tunnel.js',
29 | 'src/js/guacamole/AudioChannel.js',
30 | 'src/js/guacamole/InputStream.js',
31 | 'src/js/guacamole/IntegerPool.js',
32 | 'src/js/guacamole/Keyboard.js',
33 | 'src/js/guacamole/Status.js',
34 | 'src/js/guacamole/ArrayBufferWriter.js',
35 | 'src/js/guacamole/Layer.js',
36 | 'src/js/guacamole/StringWriter.js',
37 | 'src/js/guacamole/Version.js'
38 | ],
39 |
40 | // It's important to note that these file paths are
41 | // in a virtual filesystem to rollup. Add any files here which
42 | // rollup needs to see.
43 | client: [
44 | 'tmp/guacamole.module.js',
45 | 'src/js/collab-vm/common.js',
46 | 'src/js/collab-vm/en-us-qwerty.js',
47 | 'src/js/collab-vm/collab-vm.js',
48 |
49 | 'src/js/collab-vm/jquery.history.js',
50 | ],
51 |
52 | client_entry: 'src/js/collab-vm/collab-vm.js'
53 | };
54 |
55 | function HtmlTask() {
56 | return gulp.src([paths.html])
57 | .pipe(inject(gulp.src(['build/all.min.js', 'build/*.css'], { read: false }), { ignorePath: 'build', addRootSlash: false }))
58 | .pipe(inject(gulp.src('src/templates/*.html').pipe(rename((path) => {
59 | // This is a trick to use gulp-inject as a simple html template engine
60 | // It switches the filename and extension so multiple templates can be used
61 | path.extname = '.' + path.basename;
62 | path.basename = 'html';
63 | })), {
64 | transform: function (filePath, file) {
65 | return file.contents.toString('utf8');
66 | }
67 | }))
68 | .pipe(htmlmin(JSON.parse(fs.readFileSync('html-minifier.conf', 'utf8'))))
69 | .pipe(gulp.dest('build', { overwrite: true }));
70 | }
71 |
72 | function CompressBundle() {
73 | return gulp.src('tmp/all.js')
74 | .pipe(sourcemaps.init())
75 | .pipe(terser({
76 | output: {
77 | comments: false
78 | },
79 | mangle: {
80 | eval: true,
81 | module: true,
82 | toplevel: true
83 | }
84 | }))
85 | .pipe(concat('all.min.js'))
86 | .pipe(sourcemaps.write('.'))
87 | .pipe(gulp.dest('build'));
88 | }
89 |
90 | function BuildBundle() {
91 | return gulp.src(paths.client)
92 | .pipe(sourcemaps.init())
93 | .pipe(rollup({
94 | input: paths.client_entry,
95 | output: {
96 | format: "iife",
97 | name: "e" // unused but we need it for IIFE/closure
98 | }
99 | }))
100 | .pipe(concat('all.js'))
101 | .pipe(sourcemaps.write('.'))
102 | .pipe(gulp.dest('tmp'));
103 | }
104 |
105 | function ResTask() {
106 | return gulp.src('src/res/**/*')
107 | .pipe(gulp.dest('build'));
108 | }
109 |
110 |
111 | // This task throws all of the Guacamole code together into a temporary file
112 | // which we later use to build the module.
113 | function ConcatGuacModule() {
114 | return gulp.src(paths.guacamole)
115 | .pipe(concat('guacamole.module.tmp.js'))
116 | .pipe(gulp.dest('tmp')); // todo...
117 | }
118 |
119 | // This task then takes the temporary file from the ConcatGuacModule step,
120 | // and wraps it with the code needed to turn it into a legitimate ES6 module.
121 | // After we do this, we now have code that can be integrated into some module space.
122 | function BuildGuacModule() {
123 | return gulp.src('tmp/guacamole.module.tmp.js')
124 | .pipe(gap.prependText('export function GetGuacamole() {'))
125 | .pipe(gap.appendText('return Guacamole; }'))
126 | .pipe(concat('guacamole.module.js'))
127 | .pipe(gulp.dest('tmp')); // todo...
128 | }
129 |
130 |
131 |
132 | // default task
133 | exports.default = gulp.series(
134 | ConcatGuacModule, // These two need to be done first before the client bundle.
135 | BuildGuacModule,
136 |
137 | BuildBundle,
138 | CompressBundle, // makes prod bundle
139 | ResTask,
140 | HtmlTask
141 | );
142 |
--------------------------------------------------------------------------------
/html-minifier.conf:
--------------------------------------------------------------------------------
1 | {
2 | "removeComments": true,
3 | "removeCommentsFromCDATA": true,
4 | "removeCDATASectionsFromCDATA": true,
5 | "collapseWhitespace": true,
6 | "conservativeCollapse": false,
7 | "collapseBooleanAttributes": false,
8 | "removeAttributeQuotes": false,
9 | "removeRedundantAttributes": false,
10 | "useShortDoctype": true,
11 | "removeEmptyAttributes": false,
12 | "removeOptionalTags": false,
13 | "removeEmptyElements": false,
14 | "lint": false,
15 | "keepClosingSlash": false,
16 | "caseSensitive": false,
17 | "minifyJS": false,
18 | "minifyCSS": true,
19 | "ignoreCustomComments": [],
20 | "processScripts": []
21 | }
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "collab-vm-web-app",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "gulp",
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "Dartz & Cosmic Sans",
9 | "license": "Apache-2.0",
10 | "devDependencies": {
11 | "gulp": "^4.0.2",
12 | "gulp-concat": "^2.6.1",
13 | "gulp-htmlmin": "^5.0.1",
14 | "gulp-inject": "^5.0.4",
15 | "gulp-rename": "^1.4.0",
16 | "gulp-sourcemaps": "^2.6.5",
17 | "gulp-terser": "^1.2.0"
18 | },
19 | "dependencies": {
20 | "gulp-append-prepend": "^1.0.8",
21 | "gulp-rollup": "^2.17.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/html/donate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
A few people asked me to put up a donate button, so here you go. Do you like Collab VM? Do you want to donate to the developers of the project? You can do so by clicking the button below. Please note that Collab VM is a completely free service! You have no obligation to donate even if you use the virtual machine a lot. Just going to the website is enough support. Only donate if you really want.
Soon there will be donor features, which will include longer turns, a colored name, and other things.
Here are some frequently asked questions that I receive about CollabVM.
41 |
Q1. What is CollabVM?
42 |
A. CollabVM is a website that lets you control a virtualized Windows 7 machine via your web browser, for fun, testing, and overall entertainment.
43 |
Q2. How do I use this website?
44 |
A. Just click on the screen and wait for your turn. If there are no turns you will be given one automatically.
45 |
Q3. The VM is really messed up, slow, or Windows will no longer start, now what?
46 |
A. If the VM will no longer start or is really messed up, you should click the "Vote for Reset" button to vote for a reset. If it succeeds, this will reset the VM to a clean, non-messed up state. If the VM really needs a manual reset or the vote reset is broken email me at rightowner@gmail.com.
47 |
Q4. Some names are blue or yellow or both, what does that mean?
48 |
A. A blue name means that person is the one who is currently in control of the virtual machine. Anybody waiting for a turn will receive a yellow name. If they have not requested a turn yet, they won't have any color.
49 |
Q5. What happens if the computer is shut down?
50 |
A. I have made it so turning off the computer is pretty hard. If it is bypassed however nothing happens, it just restarts even if "Shut Down" is clicked.
51 |
Q6. What are the specifications of the VM?
52 |
A. 1.5 gigabytes of RAM, 50 GB hard drive, QEMU64 CPU @ 2 GHz, and a QXL video card capable of supporting 2D graphics.
53 |
Q7. Why Windows 7? Why not Mac OS X, Linux, BSD, etc?
54 |
A. Windows was just the best choice for something like this. In the future however, there will be a wider variation of virtual machines.
55 |
Q8. Why won't CollabVM load for me? What's wrong?
56 |
A. If it doesn't load, please make sure you do not have any of the following addons installed:
57 | NoScript
58 | uMatrix
59 | Ghostery is also known to affect some users of CollabVM
60 | If you have any of these addons installed, make an exception for CollabVM in them and try connecting again.
61 | If you don't have any of those add-ons installed, the servers may be down. Please contact me at rightowner@gmail.com.
62 | An unlikely sceneraio is that you may be banned from CollabVM. To test this, you can use a VPN/proxy to connect. (If you are banned, please do not use a VPN/Proxy to control the virtual machine unless you have my explicit permission. Only do this to check if you are banned.)
63 | If you are banned, please contact me and we can see if we can sort it out.
64 |
Q9. What exactly is "off-limits"? Can I be banned?
65 |
A. It is possible to be banned, although it's pretty difficult to do so. Read the rules if you are concerned about them.
66 |
Q10. It's asking me to login to the computer. What is the password?
67 |
A. By default, there is no password on the computer, not on the Administrator account, nor the default "Dartz" account. If there is one, somebody put it there. If you cannot login just email me at rightowner@gmail.com and I'll fix it.
68 |
Q11. Who coded and designed CollabVM?
69 |
A. CollabVM was written by Cosmic Sans. The site was designed by Cosmic Sans & Dartz (me).
70 |
Q12. It's stuck at a blank screen and on the top left it says "serial console" or "parallel console", help?
71 |
A. Press CTRL + ALT + 1 to get out of it.
72 |
Q13. The keyboard is stuck or is opening shortcuts, help?
73 |
A. Press CTRL and ALT at the same time on your physical keyboard, this should unstick the keys. If Windows key is held down, press that as well.
74 |
Q14. Is it safe to view this at work, school, etc?
75 |
A. Sometimes it is, sometimes it isn't. Before you can connect, the computer screen is censored until you click continue, but it's probably best to avoid connecting from an office place, as anyone can do anything, including opening something NSFW (e.g. porn) or even setting it as the wallpaper, for an example.
76 |
Q15. Will I break the VM if I go to http://computernewb.com/collab-vm/ on it?
77 |
A. No, the VM will continue to operate as if nothing is happening, even if you request a turn it will be fine (although the cursor will just stay there during its turn).
78 |
Q16. How is this all hosted?
79 |
A. This VM is hosted from a LunaNode KVM VPS server with 4 GB of RAM, using a customly built HTML5 Guacamole.
80 |
Q17. Is the project open source?
81 |
A. Unfortunately at this time the project is not open source, but there are plans to release its source code in the future when more time can be made.
82 |
83 |
84 |
85 |
86 |
87 |
88 |
Good evening everyone. You may have noticed on the Navbar there is a new option called "Themes". Yes, it's true. Starting today, collab-vm officially supports a multitude of themes. This includes the classic theme, a dark theme, a XP-like theme, and a few others. We hope you enjoy and in the future we will probably add an option to customize your own. Enjoy it!
43 |
-Dartz
44 |
12/21/2015 - "Collab VM 1.1 released!"
45 |
Good evening everybody.
Today, me (Dartz) and Cosmic Sans (a good friend & the main coder) have officially released Collab VM 1.1. Collab VM 1.1 has several updates to enhance the experience of this site. Read more at the reddit post here
Here are the rules for CollabVM. If you break any of them, expect a ban.
41 |
R1. Do not do anything illegal in the United States or Canada.
42 |
A. Do not do any action that is illegal in the United States or Canada (e.g. downloading illegal content).
43 |
R2. No running DDoSing tools.
44 |
A. Do not use CollabVM to DDoS an indivdiual, business, company, or anyone else.
45 |
R3. No *coin miners.
46 |
A. Do not run any *coin miners (bitcoin, litecoin, dogecoin, etc).
47 |
R4. No email spamming.
48 |
A. Do not spam any emails using this service.
49 |
R5. Do not abuse any exploits.
50 |
A. Do not abuse any exploits you find. Instead, report them to me at rightowner@gmail.com. NOTE: RATs, TeamViewer, and other tools do not count as "exploiting" or "cheating".
51 |
R6. Don't impersonate other users.
52 |
A. Do not impersonate other members of CollabVM. If caught you'll be temporarily disconnected.
53 |
R7. One vote per person.
54 |
A. Do not use any tools (including, but not limited to, proxies, VPNs, VNCs, 2G/3G/4G connections, Tor nodes, etc) to bypass the vote once restriction. Only one vote per person is allowed, no matter what. Anybody who is caught doing this will be warned, then banned for two weeks.
55 |
What you will NOT be banned for.
56 |
Destroying Windows. Being a dick. (pls don't though its not nice.) Downloading viruses. (Unless they violate any of the rules above).
57 |
REMEMBER: You are responsible for everything you do on this machine!
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/html/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/computernewb/collab-vm-classic-web-app/24ec00a270ef25f8792e3b4cd4a73d19c5e89629/src/html/test.png
--------------------------------------------------------------------------------
/src/js/collab-vm/common.js:
--------------------------------------------------------------------------------
1 | // CollabVM webapp configuration
2 |
3 | const root="/collab-vm/";
4 |
5 | export default {
6 | DEBUG: false,
7 | DEBUG_NO_TIMEOUT: false,
8 | DEBUG_NO_NSFW: false,
9 | DEBUG_NO_CONNECT: false, // is this used?
10 | DEBUG_VM_LIST: false,
11 | DEBUG_VM_LIST: false,
12 |
13 | debugLog: function() {
14 | if(this.DEBUG)
15 | console.log.apply(null, arguments);
16 | },
17 |
18 | /** @const
19 | * The root directory of the collab-vm project with a
20 | * forward slash appended to it.
21 | * This is determined at runtime to allow the project to be
22 | * relocated without needing to recompile the javascript.
23 | * @type {string}
24 | */
25 | rootDir: root,
26 | chatSound: root + "notify",
27 |
28 | /**
29 | * The main node this webapp is configured to connect to.
30 | * @const
31 | */
32 | serverAddress: window.location.host,
33 |
34 | /**
35 | * Additional nodes to connect to.
36 | * Uses multicollab() to do so
37 | * @const
38 | */
39 | additionalNodes: [
40 | ]
41 | };
42 |
--------------------------------------------------------------------------------
/src/js/collab-vm/en-us-qwerty.js:
--------------------------------------------------------------------------------
1 | export var en_us_qwerty_keyboard = {
2 |
3 | "language" : "en_US",
4 | "type" : "qwerty",
5 | "width" : 22,
6 |
7 | "keys" : {
8 |
9 | "Back" : 65288,
10 | "Tab" : 65289,
11 | "Enter" : 65293,
12 | "Esc" : 65307,
13 | "Home" : 65360,
14 | "PgUp" : 65365,
15 | "PgDn" : 65366,
16 | "End" : 65367,
17 | "Ins" : 65379,
18 | "F1" : 65470,
19 | "F2" : 65471,
20 | "F3" : 65472,
21 | "F4" : 65473,
22 | "F5" : 65474,
23 | "F6" : 65475,
24 | "F7" : 65476,
25 | "F8" : 65477,
26 | "F9" : 65478,
27 | "F10" : 65479,
28 | "F11" : 65480,
29 | "F12" : 65481,
30 | "Del" : 65535,
31 |
32 | "Space" : " ",
33 |
34 | "Left" : [{
35 | "title" : "←",
36 | "keysym" : 65361
37 | }],
38 | "Up" : [{
39 | "title" : "↑",
40 | "keysym" : 65362
41 | }],
42 | "Right" : [{
43 | "title" : "→",
44 | "keysym" : 65363
45 | }],
46 | "Down" : [{
47 | "title" : "↓",
48 | "keysym" : 65364
49 | }],
50 |
51 | "Menu" : [{
52 | "title" : "Menu",
53 | "modifier" : "super",
54 | "keysym" : 65383
55 | }],
56 | "LShift" : [{
57 | "title" : "Shift",
58 | "modifier" : "shift",
59 | "keysym" : 65505
60 | }],
61 | "RShift" : [{
62 | "title" : "Shift",
63 | "modifier" : "shift",
64 | "keysym" : 65506
65 | }],
66 | "LCtrl" : [{
67 | "title" : "Ctrl",
68 | "modifier" : "control",
69 | "keysym" : 65507
70 | }],
71 | "RCtrl" : [{
72 | "title" : "Ctrl",
73 | "modifier" : "control",
74 | "keysym" : 65508
75 | }],
76 | "Caps" : [{
77 | "title" : "Caps",
78 | "modifier" : "caps",
79 | "keysym" : 65509
80 | }],
81 | "LAlt" : [{
82 | "title" : "Alt",
83 | "modifier" : "alt",
84 | "keysym" : 65513
85 | }],
86 | "RAlt" : [{
87 | "title" : "Alt",
88 | "modifier" : "alt",
89 | "keysym" : 65514
90 | }],
91 | "Super" : [{
92 | "title" : "Super",
93 | "modifier" : "super",
94 | "keysym" : 65515
95 | }],
96 |
97 | "`" : [
98 | { "title" : "`", "requires" : [ ] },
99 | { "title" : "~", "requires" : [ "shift" ] }
100 | ],
101 | "1" : [
102 | { "title" : "1", "requires" : [ ] },
103 | { "title" : "!", "requires" : [ "shift" ] }
104 | ],
105 | "2" : [
106 | { "title" : "2", "requires" : [ ] },
107 | { "title" : "@", "requires" : [ "shift" ] }
108 | ],
109 | "3" : [
110 | { "title" : "3", "requires" : [ ] },
111 | { "title" : "#", "requires" : [ "shift" ] }
112 | ],
113 | "4" : [
114 | { "title" : "4", "requires" : [ ] },
115 | { "title" : "$", "requires" : [ "shift" ] }
116 | ],
117 | "5" : [
118 | { "title" : "5", "requires" : [ ] },
119 | { "title" : "%", "requires" : [ "shift" ] }
120 | ],
121 | "6" : [
122 | { "title" : "6", "requires" : [ ] },
123 | { "title" : "^", "requires" : [ "shift" ] }
124 | ],
125 | "7" : [
126 | { "title" : "7", "requires" : [ ] },
127 | { "title" : "&", "requires" : [ "shift" ] }
128 | ],
129 | "8" : [
130 | { "title" : "8", "requires" : [ ] },
131 | { "title" : "*", "requires" : [ "shift" ] }
132 | ],
133 | "9" : [
134 | { "title" : "9", "requires" : [ ] },
135 | { "title" : "(", "requires" : [ "shift" ] }
136 | ],
137 | "0" : [
138 | { "title" : "0", "requires" : [ ] },
139 | { "title" : ")", "requires" : [ "shift" ] }
140 | ],
141 | "-" : [
142 | { "title" : "-", "requires" : [ ] },
143 | { "title" : "_", "requires" : [ "shift" ] }
144 | ],
145 | "=" : [
146 | { "title" : "=", "requires" : [ ] },
147 | { "title" : "+", "requires" : [ "shift" ] }
148 | ],
149 | "," : [
150 | { "title" : ",", "requires" : [ ] },
151 | { "title" : "<", "requires" : [ "shift" ] }
152 | ],
153 | "." : [
154 | { "title" : ".", "requires" : [ ] },
155 | { "title" : ">", "requires" : [ "shift" ] }
156 | ],
157 | "/" : [
158 | { "title" : "/", "requires" : [ ] },
159 | { "title" : "?", "requires" : [ "shift" ] }
160 | ],
161 | "[" : [
162 | { "title" : "[", "requires" : [ ] },
163 | { "title" : "{", "requires" : [ "shift" ] }
164 | ],
165 | "]" : [
166 | { "title" : "]", "requires" : [ ] },
167 | { "title" : "}", "requires" : [ "shift" ] }
168 | ],
169 | "\\" : [
170 | { "title" : "\\", "requires" : [ ] },
171 | { "title" : "|", "requires" : [ "shift" ] }
172 | ],
173 | ";" : [
174 | { "title" : ";", "requires" : [ ] },
175 | { "title" : ":", "requires" : [ "shift" ] }
176 | ],
177 | "'" : [
178 | { "title" : "'", "requires" : [ ] },
179 | { "title" : "\"", "requires" : [ "shift" ] }
180 | ],
181 |
182 | "q" : [
183 | { "title" : "q", "requires" : [ ] },
184 | { "title" : "Q", "requires" : [ "caps" ] },
185 | { "title" : "Q", "requires" : [ "shift" ] },
186 | { "title" : "q", "requires" : [ "caps", "shift" ] }
187 | ],
188 | "w" : [
189 | { "title" : "w", "requires" : [ ] },
190 | { "title" : "W", "requires" : [ "caps" ] },
191 | { "title" : "W", "requires" : [ "shift" ] },
192 | { "title" : "w", "requires" : [ "caps", "shift" ] }
193 | ],
194 | "e" : [
195 | { "title" : "e", "requires" : [ ] },
196 | { "title" : "E", "requires" : [ "caps" ] },
197 | { "title" : "E", "requires" : [ "shift" ] },
198 | { "title" : "e", "requires" : [ "caps", "shift" ] }
199 | ],
200 | "r" : [
201 | { "title" : "r", "requires" : [ ] },
202 | { "title" : "R", "requires" : [ "caps" ] },
203 | { "title" : "R", "requires" : [ "shift" ] },
204 | { "title" : "r", "requires" : [ "caps", "shift" ] }
205 | ],
206 | "t" : [
207 | { "title" : "t", "requires" : [ ] },
208 | { "title" : "T", "requires" : [ "caps" ] },
209 | { "title" : "T", "requires" : [ "shift" ] },
210 | { "title" : "t", "requires" : [ "caps", "shift" ] }
211 | ],
212 | "y" : [
213 | { "title" : "y", "requires" : [ ] },
214 | { "title" : "Y", "requires" : [ "caps" ] },
215 | { "title" : "Y", "requires" : [ "shift" ] },
216 | { "title" : "y", "requires" : [ "caps", "shift" ] }
217 | ],
218 | "u" : [
219 | { "title" : "u", "requires" : [ ] },
220 | { "title" : "U", "requires" : [ "caps" ] },
221 | { "title" : "U", "requires" : [ "shift" ] },
222 | { "title" : "u", "requires" : [ "caps", "shift" ] }
223 | ],
224 | "i" : [
225 | { "title" : "i", "requires" : [ ] },
226 | { "title" : "I", "requires" : [ "caps" ] },
227 | { "title" : "I", "requires" : [ "shift" ] },
228 | { "title" : "i", "requires" : [ "caps", "shift" ] }
229 | ],
230 | "o" : [
231 | { "title" : "o", "requires" : [ ] },
232 | { "title" : "O", "requires" : [ "caps" ] },
233 | { "title" : "O", "requires" : [ "shift" ] },
234 | { "title" : "o", "requires" : [ "caps", "shift" ] }
235 | ],
236 | "p" : [
237 | { "title" : "p", "requires" : [ ] },
238 | { "title" : "P", "requires" : [ "caps" ] },
239 | { "title" : "P", "requires" : [ "shift" ] },
240 | { "title" : "p", "requires" : [ "caps", "shift" ] }
241 | ],
242 | "a" : [
243 | { "title" : "a", "requires" : [ ] },
244 | { "title" : "A", "requires" : [ "caps" ] },
245 | { "title" : "A", "requires" : [ "shift" ] },
246 | { "title" : "a", "requires" : [ "caps", "shift" ] }
247 | ],
248 | "s" : [
249 | { "title" : "s", "requires" : [ ] },
250 | { "title" : "S", "requires" : [ "caps" ] },
251 | { "title" : "S", "requires" : [ "shift" ] },
252 | { "title" : "s", "requires" : [ "caps", "shift" ] }
253 | ],
254 | "d" : [
255 | { "title" : "d", "requires" : [ ] },
256 | { "title" : "D", "requires" : [ "caps" ] },
257 | { "title" : "D", "requires" : [ "shift" ] },
258 | { "title" : "d", "requires" : [ "caps", "shift" ] }
259 | ],
260 | "f" : [
261 | { "title" : "f", "requires" : [ ] },
262 | { "title" : "F", "requires" : [ "caps" ] },
263 | { "title" : "F", "requires" : [ "shift" ] },
264 | { "title" : "f", "requires" : [ "caps", "shift" ] }
265 | ],
266 | "g" : [
267 | { "title" : "g", "requires" : [ ] },
268 | { "title" : "G", "requires" : [ "caps" ] },
269 | { "title" : "G", "requires" : [ "shift" ] },
270 | { "title" : "g", "requires" : [ "caps", "shift" ] }
271 | ],
272 | "h" : [
273 | { "title" : "h", "requires" : [ ] },
274 | { "title" : "H", "requires" : [ "caps" ] },
275 | { "title" : "H", "requires" : [ "shift" ] },
276 | { "title" : "h", "requires" : [ "caps", "shift" ] }
277 | ],
278 | "j" : [
279 | { "title" : "j", "requires" : [ ] },
280 | { "title" : "J", "requires" : [ "caps" ] },
281 | { "title" : "J", "requires" : [ "shift" ] },
282 | { "title" : "j", "requires" : [ "caps", "shift" ] }
283 | ],
284 | "k" : [
285 | { "title" : "k", "requires" : [ ] },
286 | { "title" : "K", "requires" : [ "caps" ] },
287 | { "title" : "K", "requires" : [ "shift" ] },
288 | { "title" : "k", "requires" : [ "caps", "shift" ] }
289 | ],
290 | "l" : [
291 | { "title" : "l", "requires" : [ ] },
292 | { "title" : "L", "requires" : [ "caps" ] },
293 | { "title" : "L", "requires" : [ "shift" ] },
294 | { "title" : "l", "requires" : [ "caps", "shift" ] }
295 | ],
296 | "z" : [
297 | { "title" : "z", "requires" : [ ] },
298 | { "title" : "Z", "requires" : [ "caps" ] },
299 | { "title" : "Z", "requires" : [ "shift" ] },
300 | { "title" : "z", "requires" : [ "caps", "shift" ] }
301 | ],
302 | "x" : [
303 | { "title" : "x", "requires" : [ ] },
304 | { "title" : "X", "requires" : [ "caps" ] },
305 | { "title" : "X", "requires" : [ "shift" ] },
306 | { "title" : "x", "requires" : [ "caps", "shift" ] }
307 | ],
308 | "c" : [
309 | { "title" : "c", "requires" : [ ] },
310 | { "title" : "C", "requires" : [ "caps" ] },
311 | { "title" : "C", "requires" : [ "shift" ] },
312 | { "title" : "c", "requires" : [ "caps", "shift" ] }
313 | ],
314 | "v" : [
315 | { "title" : "v", "requires" : [ ] },
316 | { "title" : "V", "requires" : [ "caps" ] },
317 | { "title" : "V", "requires" : [ "shift" ] },
318 | { "title" : "v", "requires" : [ "caps", "shift" ] }
319 | ],
320 | "b" : [
321 | { "title" : "b", "requires" : [ ] },
322 | { "title" : "B", "requires" : [ "caps" ] },
323 | { "title" : "B", "requires" : [ "shift" ] },
324 | { "title" : "b", "requires" : [ "caps", "shift" ] }
325 | ],
326 | "n" : [
327 | { "title" : "n", "requires" : [ ] },
328 | { "title" : "N", "requires" : [ "caps" ] },
329 | { "title" : "N", "requires" : [ "shift" ] },
330 | { "title" : "n", "requires" : [ "caps", "shift" ] }
331 | ],
332 | "m" : [
333 | { "title" : "m", "requires" : [ ] },
334 | { "title" : "M", "requires" : [ "caps" ] },
335 | { "title" : "M", "requires" : [ "shift" ] },
336 | { "title" : "m", "requires" : [ "caps", "shift" ] }
337 | ]
338 |
339 | },
340 |
341 | "layout" : [
342 |
343 | [ "Esc", 0.7, "F1", "F2", "F3", "F4",
344 | 0.7, "F5", "F6", "F7", "F8",
345 | 0.7, "F9", "F10", "F11", "F12" ],
346 |
347 | [ 0.1 ],
348 |
349 | {
350 | "main" : {
351 | "alpha" : [
352 |
353 | [ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" ],
354 | [ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\" ],
355 | [ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter" ],
356 | [ "LShift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "RShift" ],
357 | [ "LCtrl", "Super", "LAlt", "Space", "RAlt", "Menu", "RCtrl" ]
358 |
359 | ],
360 |
361 | "movement" : [
362 | [ "Ins", "Home", "PgUp" ],
363 | [ "Del", "End", "PgDn" ],
364 | [ 1 ],
365 | [ "Up" ],
366 | [ "Left", "Down", "Right" ]
367 | ]
368 | }
369 | }
370 |
371 | ],
372 |
373 | "keyWidths" : {
374 |
375 | "Back" : 2,
376 | "Tab" : 1.5,
377 | "\\" : 1.5,
378 | "Caps" : 1.85,
379 | "Enter" : 2.25,
380 | "LShift" : 2.1,
381 | "RShift" : 3.1,
382 |
383 | "LCtrl" : 1.6,
384 | "Super" : 1.6,
385 | "LAlt" : 1.6,
386 | "Space" : 6.1,
387 | "RAlt" : 1.6,
388 | "Menu" : 1.6,
389 | "RCtrl" : 1.6,
390 |
391 | "Ins" : 1.6,
392 | "Home" : 1.6,
393 | "PgUp" : 1.6,
394 | "Del" : 1.6,
395 | "End" : 1.6,
396 | "PgDn" : 1.6
397 |
398 | }
399 |
400 | };
401 |
--------------------------------------------------------------------------------
/src/js/guacamole/ArrayBufferReader.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Glyptodon LLC
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 |
23 | var Guacamole = Guacamole || {};
24 |
25 | /**
26 | * A reader which automatically handles the given input stream, returning
27 | * strictly received packets as array buffers. Note that this object will
28 | * overwrite any installed event handlers on the given Guacamole.InputStream.
29 | *
30 | * @constructor
31 | * @param {Guacamole.InputStream} stream The stream that data will be read
32 | * from.
33 | */
34 | Guacamole.ArrayBufferReader = function(stream) {
35 |
36 | /**
37 | * Reference to this Guacamole.InputStream.
38 | * @private
39 | */
40 | var guac_reader = this;
41 |
42 | // Receive blobs as array buffers
43 | stream.onblob = function(data) {
44 |
45 | // Convert to ArrayBuffer
46 | var binary = window.atob(data);
47 | var arrayBuffer = new ArrayBuffer(binary.length);
48 | var bufferView = new Uint8Array(arrayBuffer);
49 |
50 | for (var i=0; i 0)
59 | return pool.shift();
60 |
61 | // Otherwise, return a new integer
62 | return guac_pool.next_int++;
63 |
64 | };
65 |
66 | /**
67 | * Frees the given integer, allowing it to be reused.
68 | *
69 | * @param {Number} integer The integer to free.
70 | */
71 | this.free = function(integer) {
72 | pool.push(integer);
73 | };
74 |
75 | };
76 |
--------------------------------------------------------------------------------
/src/js/guacamole/Layer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Glyptodon LLC
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 |
23 | var Guacamole = Guacamole || {};
24 |
25 | /**
26 | * Abstract ordered drawing surface. Each Layer contains a canvas element and
27 | * provides simple drawing instructions for drawing to that canvas element,
28 | * however unlike the canvas element itself, drawing operations on a Layer are
29 | * guaranteed to run in order, even if such an operation must wait for an image
30 | * to load before completing.
31 | *
32 | * @constructor
33 | *
34 | * @param {Number} width The width of the Layer, in pixels. The canvas element
35 | * backing this Layer will be given this width.
36 | *
37 | * @param {Number} height The height of the Layer, in pixels. The canvas element
38 | * backing this Layer will be given this height.
39 | */
40 | Guacamole.Layer = function(width, height) {
41 |
42 | /**
43 | * Reference to this Layer.
44 | * @private
45 | */
46 | var layer = this;
47 |
48 | /**
49 | * The canvas element backing this Layer.
50 | * @private
51 | */
52 | var canvas = document.createElement("canvas");
53 |
54 | /**
55 | * The 2D display context of the canvas element backing this Layer.
56 | * @private
57 | */
58 | var context = canvas.getContext("2d");
59 | context.save();
60 |
61 | /**
62 | * Whether a new path should be started with the next path drawing
63 | * operations.
64 | * @private
65 | */
66 | var pathClosed = true;
67 |
68 | /**
69 | * The number of states on the state stack.
70 | *
71 | * Note that there will ALWAYS be one element on the stack, but that
72 | * element is not exposed. It is only used to reset the layer to its
73 | * initial state.
74 | *
75 | * @private
76 | */
77 | var stackSize = 0;
78 |
79 | /**
80 | * Map of all Guacamole channel masks to HTML5 canvas composite operation
81 | * names. Not all channel mask combinations are currently implemented.
82 | * @private
83 | */
84 | var compositeOperation = {
85 | /* 0x0 NOT IMPLEMENTED */
86 | 0x1: "destination-in",
87 | 0x2: "destination-out",
88 | /* 0x3 NOT IMPLEMENTED */
89 | 0x4: "source-in",
90 | /* 0x5 NOT IMPLEMENTED */
91 | 0x6: "source-atop",
92 | /* 0x7 NOT IMPLEMENTED */
93 | 0x8: "source-out",
94 | 0x9: "destination-atop",
95 | 0xA: "xor",
96 | 0xB: "destination-over",
97 | 0xC: "copy",
98 | /* 0xD NOT IMPLEMENTED */
99 | 0xE: "source-over",
100 | 0xF: "lighter"
101 | };
102 |
103 | /**
104 | * Resizes the canvas element backing this Layer without testing the
105 | * new size. This function should only be used internally.
106 | *
107 | * @private
108 | * @param {Number} newWidth The new width to assign to this Layer.
109 | * @param {Number} newHeight The new height to assign to this Layer.
110 | */
111 | function resize(newWidth, newHeight) {
112 |
113 | // Only preserve old data if width/height are both non-zero
114 | var oldData = null;
115 | if (layer.width !== 0 && layer.height !== 0) {
116 |
117 | // Create canvas and context for holding old data
118 | oldData = document.createElement("canvas");
119 | oldData.width = layer.width;
120 | oldData.height = layer.height;
121 |
122 | var oldDataContext = oldData.getContext("2d");
123 |
124 | // Copy image data from current
125 | oldDataContext.drawImage(canvas,
126 | 0, 0, layer.width, layer.height,
127 | 0, 0, layer.width, layer.height);
128 |
129 | }
130 |
131 | // Preserve composite operation
132 | var oldCompositeOperation = context.globalCompositeOperation;
133 |
134 | // Resize canvas
135 | canvas.width = newWidth;
136 | canvas.height = newHeight;
137 |
138 | // Redraw old data, if any
139 | if (oldData)
140 | context.drawImage(oldData,
141 | 0, 0, layer.width, layer.height,
142 | 0, 0, layer.width, layer.height);
143 |
144 | // Restore composite operation
145 | context.globalCompositeOperation = oldCompositeOperation;
146 |
147 | layer.width = newWidth;
148 | layer.height = newHeight;
149 |
150 | // Acknowledge reset of stack (happens on resize of canvas)
151 | stackSize = 0;
152 | context.save();
153 |
154 | }
155 |
156 | /**
157 | * Given the X and Y coordinates of the upper-left corner of a rectangle
158 | * and the rectangle's width and height, resize the backing canvas element
159 | * as necessary to ensure that the rectangle fits within the canvas
160 | * element's coordinate space. This function will only make the canvas
161 | * larger. If the rectangle already fits within the canvas element's
162 | * coordinate space, the canvas is left unchanged.
163 | *
164 | * @private
165 | * @param {Number} x The X coordinate of the upper-left corner of the
166 | * rectangle to fit.
167 | * @param {Number} y The Y coordinate of the upper-left corner of the
168 | * rectangle to fit.
169 | * @param {Number} w The width of the the rectangle to fit.
170 | * @param {Number} h The height of the the rectangle to fit.
171 | */
172 | function fitRect(x, y, w, h) {
173 |
174 | // Calculate bounds
175 | var opBoundX = w + x;
176 | var opBoundY = h + y;
177 |
178 | // Determine max width
179 | var resizeWidth;
180 | if (opBoundX > layer.width)
181 | resizeWidth = opBoundX;
182 | else
183 | resizeWidth = layer.width;
184 |
185 | // Determine max height
186 | var resizeHeight;
187 | if (opBoundY > layer.height)
188 | resizeHeight = opBoundY;
189 | else
190 | resizeHeight = layer.height;
191 |
192 | // Resize if necessary
193 | layer.resize(resizeWidth, resizeHeight);
194 |
195 | }
196 |
197 | /**
198 | * Set to true if this Layer should resize itself to accomodate the
199 | * dimensions of any drawing operation, and false (the default) otherwise.
200 | *
201 | * Note that setting this property takes effect immediately, and thus may
202 | * take effect on operations that were started in the past but have not
203 | * yet completed. If you wish the setting of this flag to only modify
204 | * future operations, you will need to make the setting of this flag an
205 | * operation with sync().
206 | *
207 | * @example
208 | * // Set autosize to true for all future operations
209 | * layer.sync(function() {
210 | * layer.autosize = true;
211 | * });
212 | *
213 | * @type Boolean
214 | * @default false
215 | */
216 | this.autosize = false;
217 |
218 | /**
219 | * The current width of this layer.
220 | * @type Number
221 | */
222 | this.width = width;
223 |
224 | /**
225 | * The current height of this layer.
226 | * @type Number
227 | */
228 | this.height = height;
229 |
230 | /**
231 | * Returns the canvas element backing this Layer.
232 | * @returns {Element} The canvas element backing this Layer.
233 | */
234 | this.getCanvas = function() {
235 | return canvas;
236 | };
237 |
238 | /**
239 | * Changes the size of this Layer to the given width and height. Resizing
240 | * is only attempted if the new size provided is actually different from
241 | * the current size.
242 | *
243 | * @param {Number} newWidth The new width to assign to this Layer.
244 | * @param {Number} newHeight The new height to assign to this Layer.
245 | */
246 | this.resize = function(newWidth, newHeight) {
247 | if (newWidth !== layer.width || newHeight !== layer.height)
248 | resize(newWidth, newHeight);
249 | };
250 |
251 | /**
252 | * Draws the specified image at the given coordinates. The image specified
253 | * must already be loaded.
254 | *
255 | * @param {Number} x The destination X coordinate.
256 | * @param {Number} y The destination Y coordinate.
257 | * @param {Image} image The image to draw. Note that this is an Image
258 | * object - not a URL.
259 | */
260 | this.drawImage = function(x, y, image) {
261 | if (layer.autosize) fitRect(x, y, image.width, image.height);
262 | context.drawImage(image, x, y);
263 | };
264 |
265 | /**
266 | * Transfer a rectangle of image data from one Layer to this Layer using the
267 | * specified transfer function.
268 | *
269 | * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
270 | * @param {Number} srcx The X coordinate of the upper-left corner of the
271 | * rectangle within the source Layer's coordinate
272 | * space to copy data from.
273 | * @param {Number} srcy The Y coordinate of the upper-left corner of the
274 | * rectangle within the source Layer's coordinate
275 | * space to copy data from.
276 | * @param {Number} srcw The width of the rectangle within the source Layer's
277 | * coordinate space to copy data from.
278 | * @param {Number} srch The height of the rectangle within the source
279 | * Layer's coordinate space to copy data from.
280 | * @param {Number} x The destination X coordinate.
281 | * @param {Number} y The destination Y coordinate.
282 | * @param {Function} transferFunction The transfer function to use to
283 | * transfer data from source to
284 | * destination.
285 | */
286 | this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) {
287 |
288 | var srcCanvas = srcLayer.getCanvas();
289 |
290 | // If entire rectangle outside source canvas, stop
291 | if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
292 |
293 | // Otherwise, clip rectangle to area
294 | if (srcx + srcw > srcCanvas.width)
295 | srcw = srcCanvas.width - srcx;
296 |
297 | if (srcy + srch > srcCanvas.height)
298 | srch = srcCanvas.height - srcy;
299 |
300 | // Stop if nothing to draw.
301 | if (srcw === 0 || srch === 0) return;
302 |
303 | if (layer.autosize) fitRect(x, y, srcw, srch);
304 |
305 | // Get image data from src and dst
306 | var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
307 | var dst = context.getImageData(x , y, srcw, srch);
308 |
309 | // Apply transfer for each pixel
310 | for (var i=0; i= srcCanvas.width || srcy >= srcCanvas.height) return;
368 |
369 | // Otherwise, clip rectangle to area
370 | if (srcx + srcw > srcCanvas.width)
371 | srcw = srcCanvas.width - srcx;
372 |
373 | if (srcy + srch > srcCanvas.height)
374 | srch = srcCanvas.height - srcy;
375 |
376 | // Stop if nothing to draw.
377 | if (srcw === 0 || srch === 0) return;
378 |
379 | if (layer.autosize) fitRect(x, y, srcw, srch);
380 |
381 | // Get image data from src and dst
382 | var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
383 | context.putImageData(src, x, y);
384 |
385 | };
386 |
387 | /**
388 | * Copy a rectangle of image data from one Layer to this Layer. This
389 | * operation will copy exactly the image data that will be drawn once all
390 | * operations of the source Layer that were pending at the time this
391 | * function was called are complete. This operation will not alter the
392 | * size of the source Layer even if its autosize property is set to true.
393 | *
394 | * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
395 | * @param {Number} srcx The X coordinate of the upper-left corner of the
396 | * rectangle within the source Layer's coordinate
397 | * space to copy data from.
398 | * @param {Number} srcy The Y coordinate of the upper-left corner of the
399 | * rectangle within the source Layer's coordinate
400 | * space to copy data from.
401 | * @param {Number} srcw The width of the rectangle within the source Layer's
402 | * coordinate space to copy data from.
403 | * @param {Number} srch The height of the rectangle within the source
404 | * Layer's coordinate space to copy data from.
405 | * @param {Number} x The destination X coordinate.
406 | * @param {Number} y The destination Y coordinate.
407 | */
408 | this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
409 |
410 | var srcCanvas = srcLayer.getCanvas();
411 |
412 | // If entire rectangle outside source canvas, stop
413 | if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
414 |
415 | // Otherwise, clip rectangle to area
416 | if (srcx + srcw > srcCanvas.width)
417 | srcw = srcCanvas.width - srcx;
418 |
419 | if (srcy + srch > srcCanvas.height)
420 | srch = srcCanvas.height - srcy;
421 |
422 | // Stop if nothing to draw.
423 | if (srcw === 0 || srch === 0) return;
424 |
425 | if (layer.autosize) fitRect(x, y, srcw, srch);
426 | context.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
427 |
428 | };
429 |
430 | /**
431 | * Starts a new path at the specified point.
432 | *
433 | * @param {Number} x The X coordinate of the point to draw.
434 | * @param {Number} y The Y coordinate of the point to draw.
435 | */
436 | this.moveTo = function(x, y) {
437 |
438 | // Start a new path if current path is closed
439 | if (pathClosed) {
440 | context.beginPath();
441 | pathClosed = false;
442 | }
443 |
444 | if (layer.autosize) fitRect(x, y, 0, 0);
445 | context.moveTo(x, y);
446 |
447 | };
448 |
449 | /**
450 | * Add the specified line to the current path.
451 | *
452 | * @param {Number} x The X coordinate of the endpoint of the line to draw.
453 | * @param {Number} y The Y coordinate of the endpoint of the line to draw.
454 | */
455 | this.lineTo = function(x, y) {
456 |
457 | // Start a new path if current path is closed
458 | if (pathClosed) {
459 | context.beginPath();
460 | pathClosed = false;
461 | }
462 |
463 | if (layer.autosize) fitRect(x, y, 0, 0);
464 | context.lineTo(x, y);
465 |
466 | };
467 |
468 | /**
469 | * Add the specified arc to the current path.
470 | *
471 | * @param {Number} x The X coordinate of the center of the circle which
472 | * will contain the arc.
473 | * @param {Number} y The Y coordinate of the center of the circle which
474 | * will contain the arc.
475 | * @param {Number} radius The radius of the circle.
476 | * @param {Number} startAngle The starting angle of the arc, in radians.
477 | * @param {Number} endAngle The ending angle of the arc, in radians.
478 | * @param {Boolean} negative Whether the arc should be drawn in order of
479 | * decreasing angle.
480 | */
481 | this.arc = function(x, y, radius, startAngle, endAngle, negative) {
482 |
483 | // Start a new path if current path is closed
484 | if (pathClosed) {
485 | context.beginPath();
486 | pathClosed = false;
487 | }
488 |
489 | if (layer.autosize) fitRect(x, y, 0, 0);
490 | context.arc(x, y, radius, startAngle, endAngle, negative);
491 |
492 | };
493 |
494 | /**
495 | * Starts a new path at the specified point.
496 | *
497 | * @param {Number} cp1x The X coordinate of the first control point.
498 | * @param {Number} cp1y The Y coordinate of the first control point.
499 | * @param {Number} cp2x The X coordinate of the second control point.
500 | * @param {Number} cp2y The Y coordinate of the second control point.
501 | * @param {Number} x The X coordinate of the endpoint of the curve.
502 | * @param {Number} y The Y coordinate of the endpoint of the curve.
503 | */
504 | this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
505 |
506 | // Start a new path if current path is closed
507 | if (pathClosed) {
508 | context.beginPath();
509 | pathClosed = false;
510 | }
511 |
512 | if (layer.autosize) fitRect(x, y, 0, 0);
513 | context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
514 |
515 | };
516 |
517 | /**
518 | * Closes the current path by connecting the end point with the start
519 | * point (if any) with a straight line.
520 | */
521 | this.close = function() {
522 | context.closePath();
523 | pathClosed = true;
524 | };
525 |
526 | /**
527 | * Add the specified rectangle to the current path.
528 | *
529 | * @param {Number} x The X coordinate of the upper-left corner of the
530 | * rectangle to draw.
531 | * @param {Number} y The Y coordinate of the upper-left corner of the
532 | * rectangle to draw.
533 | * @param {Number} w The width of the rectangle to draw.
534 | * @param {Number} h The height of the rectangle to draw.
535 | */
536 | this.rect = function(x, y, w, h) {
537 |
538 | // Start a new path if current path is closed
539 | if (pathClosed) {
540 | context.beginPath();
541 | pathClosed = false;
542 | }
543 |
544 | if (layer.autosize) fitRect(x, y, w, h);
545 | context.rect(x, y, w, h);
546 |
547 | };
548 |
549 | /**
550 | * Clip all future drawing operations by the current path. The current path
551 | * is implicitly closed. The current path can continue to be reused
552 | * for other operations (such as fillColor()) but a new path will be started
553 | * once a path drawing operation (path() or rect()) is used.
554 | */
555 | this.clip = function() {
556 |
557 | // Set new clipping region
558 | context.clip();
559 |
560 | // Path now implicitly closed
561 | pathClosed = true;
562 |
563 | };
564 |
565 | /**
566 | * Stroke the current path with the specified color. The current path
567 | * is implicitly closed. The current path can continue to be reused
568 | * for other operations (such as clip()) but a new path will be started
569 | * once a path drawing operation (path() or rect()) is used.
570 | *
571 | * @param {String} cap The line cap style. Can be "round", "square",
572 | * or "butt".
573 | * @param {String} join The line join style. Can be "round", "bevel",
574 | * or "miter".
575 | * @param {Number} thickness The line thickness in pixels.
576 | * @param {Number} r The red component of the color to fill.
577 | * @param {Number} g The green component of the color to fill.
578 | * @param {Number} b The blue component of the color to fill.
579 | * @param {Number} a The alpha component of the color to fill.
580 | */
581 | this.strokeColor = function(cap, join, thickness, r, g, b, a) {
582 |
583 | // Stroke with color
584 | context.lineCap = cap;
585 | context.lineJoin = join;
586 | context.lineWidth = thickness;
587 | context.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
588 | context.stroke();
589 |
590 | // Path now implicitly closed
591 | pathClosed = true;
592 |
593 | };
594 |
595 | /**
596 | * Fills the current path with the specified color. The current path
597 | * is implicitly closed. The current path can continue to be reused
598 | * for other operations (such as clip()) but a new path will be started
599 | * once a path drawing operation (path() or rect()) is used.
600 | *
601 | * @param {Number} r The red component of the color to fill.
602 | * @param {Number} g The green component of the color to fill.
603 | * @param {Number} b The blue component of the color to fill.
604 | * @param {Number} a The alpha component of the color to fill.
605 | */
606 | this.fillColor = function(r, g, b, a) {
607 |
608 | // Fill with color
609 | context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
610 | context.fill();
611 |
612 | // Path now implicitly closed
613 | pathClosed = true;
614 |
615 | };
616 |
617 | /**
618 | * Stroke the current path with the image within the specified layer. The
619 | * image data will be tiled infinitely within the stroke. The current path
620 | * is implicitly closed. The current path can continue to be reused
621 | * for other operations (such as clip()) but a new path will be started
622 | * once a path drawing operation (path() or rect()) is used.
623 | *
624 | * @param {String} cap The line cap style. Can be "round", "square",
625 | * or "butt".
626 | * @param {String} join The line join style. Can be "round", "bevel",
627 | * or "miter".
628 | * @param {Number} thickness The line thickness in pixels.
629 | * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
630 | * within the stroke.
631 | */
632 | this.strokeLayer = function(cap, join, thickness, srcLayer) {
633 |
634 | // Stroke with image data
635 | context.lineCap = cap;
636 | context.lineJoin = join;
637 | context.lineWidth = thickness;
638 | context.strokeStyle = context.createPattern(
639 | srcLayer.getCanvas(),
640 | "repeat"
641 | );
642 | context.stroke();
643 |
644 | // Path now implicitly closed
645 | pathClosed = true;
646 |
647 | };
648 |
649 | /**
650 | * Fills the current path with the image within the specified layer. The
651 | * image data will be tiled infinitely within the stroke. The current path
652 | * is implicitly closed. The current path can continue to be reused
653 | * for other operations (such as clip()) but a new path will be started
654 | * once a path drawing operation (path() or rect()) is used.
655 | *
656 | * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
657 | * within the fill.
658 | */
659 | this.fillLayer = function(srcLayer) {
660 |
661 | // Fill with image data
662 | context.fillStyle = context.createPattern(
663 | srcLayer.getCanvas(),
664 | "repeat"
665 | );
666 | context.fill();
667 |
668 | // Path now implicitly closed
669 | pathClosed = true;
670 |
671 | };
672 |
673 | /**
674 | * Push current layer state onto stack.
675 | */
676 | this.push = function() {
677 |
678 | // Save current state onto stack
679 | context.save();
680 | stackSize++;
681 |
682 | };
683 |
684 | /**
685 | * Pop layer state off stack.
686 | */
687 | this.pop = function() {
688 |
689 | // Restore current state from stack
690 | if (stackSize > 0) {
691 | context.restore();
692 | stackSize--;
693 | }
694 |
695 | };
696 |
697 | /**
698 | * Reset the layer, clearing the stack, the current path, and any transform
699 | * matrix.
700 | */
701 | this.reset = function() {
702 |
703 | // Clear stack
704 | while (stackSize > 0) {
705 | context.restore();
706 | stackSize--;
707 | }
708 |
709 | // Restore to initial state
710 | context.restore();
711 | context.save();
712 |
713 | // Clear path
714 | context.beginPath();
715 | pathClosed = false;
716 |
717 | };
718 |
719 | /**
720 | * Sets the given affine transform (defined with six values from the
721 | * transform's matrix).
722 | *
723 | * @param {Number} a The first value in the affine transform's matrix.
724 | * @param {Number} b The second value in the affine transform's matrix.
725 | * @param {Number} c The third value in the affine transform's matrix.
726 | * @param {Number} d The fourth value in the affine transform's matrix.
727 | * @param {Number} e The fifth value in the affine transform's matrix.
728 | * @param {Number} f The sixth value in the affine transform's matrix.
729 | */
730 | this.setTransform = function(a, b, c, d, e, f) {
731 | context.setTransform(
732 | a, b, c,
733 | d, e, f
734 | /*0, 0, 1*/
735 | );
736 | };
737 |
738 | /**
739 | * Applies the given affine transform (defined with six values from the
740 | * transform's matrix).
741 | *
742 | * @param {Number} a The first value in the affine transform's matrix.
743 | * @param {Number} b The second value in the affine transform's matrix.
744 | * @param {Number} c The third value in the affine transform's matrix.
745 | * @param {Number} d The fourth value in the affine transform's matrix.
746 | * @param {Number} e The fifth value in the affine transform's matrix.
747 | * @param {Number} f The sixth value in the affine transform's matrix.
748 | */
749 | this.transform = function(a, b, c, d, e, f) {
750 | context.transform(
751 | a, b, c,
752 | d, e, f
753 | /*0, 0, 1*/
754 | );
755 | };
756 |
757 | /**
758 | * Sets the channel mask for future operations on this Layer.
759 | *
760 | * The channel mask is a Guacamole-specific compositing operation identifier
761 | * with a single bit representing each of four channels (in order): source
762 | * image where destination transparent, source where destination opaque,
763 | * destination where source transparent, and destination where source
764 | * opaque.
765 | *
766 | * @param {Number} mask The channel mask for future operations on this
767 | * Layer.
768 | */
769 | this.setChannelMask = function(mask) {
770 | context.globalCompositeOperation = compositeOperation[mask];
771 | };
772 |
773 | /**
774 | * Sets the miter limit for stroke operations using the miter join. This
775 | * limit is the maximum ratio of the size of the miter join to the stroke
776 | * width. If this ratio is exceeded, the miter will not be drawn for that
777 | * joint of the path.
778 | *
779 | * @param {Number} limit The miter limit for stroke operations using the
780 | * miter join.
781 | */
782 | this.setMiterLimit = function(limit) {
783 | context.miterLimit = limit;
784 | };
785 |
786 | // Initialize canvas dimensions
787 | canvas.width = width;
788 | canvas.height = height;
789 |
790 | // Explicitly render canvas below other elements in the layer (such as
791 | // child layers). Chrome and others may fail to render layers properly
792 | // without this.
793 | canvas.style.zIndex = -1;
794 |
795 | };
796 |
797 | /**
798 | * Channel mask for the composite operation "rout".
799 | */
800 | Guacamole.Layer.ROUT = 0x2;
801 |
802 | /**
803 | * Channel mask for the composite operation "atop".
804 | */
805 | Guacamole.Layer.ATOP = 0x6;
806 |
807 | /**
808 | * Channel mask for the composite operation "xor".
809 | */
810 | Guacamole.Layer.XOR = 0xA;
811 |
812 | /**
813 | * Channel mask for the composite operation "rover".
814 | */
815 | Guacamole.Layer.ROVER = 0xB;
816 |
817 | /**
818 | * Channel mask for the composite operation "over".
819 | */
820 | Guacamole.Layer.OVER = 0xE;
821 |
822 | /**
823 | * Channel mask for the composite operation "plus".
824 | */
825 | Guacamole.Layer.PLUS = 0xF;
826 |
827 | /**
828 | * Channel mask for the composite operation "rin".
829 | * Beware that WebKit-based browsers may leave the contents of the destionation
830 | * layer where the source layer is transparent, despite the definition of this
831 | * operation.
832 | */
833 | Guacamole.Layer.RIN = 0x1;
834 |
835 | /**
836 | * Channel mask for the composite operation "in".
837 | * Beware that WebKit-based browsers may leave the contents of the destionation
838 | * layer where the source layer is transparent, despite the definition of this
839 | * operation.
840 | */
841 | Guacamole.Layer.IN = 0x4;
842 |
843 | /**
844 | * Channel mask for the composite operation "out".
845 | * Beware that WebKit-based browsers may leave the contents of the destionation
846 | * layer where the source layer is transparent, despite the definition of this
847 | * operation.
848 | */
849 | Guacamole.Layer.OUT = 0x8;
850 |
851 | /**
852 | * Channel mask for the composite operation "ratop".
853 | * Beware that WebKit-based browsers may leave the contents of the destionation
854 | * layer where the source layer is transparent, despite the definition of this
855 | * operation.
856 | */
857 | Guacamole.Layer.RATOP = 0x9;
858 |
859 | /**
860 | * Channel mask for the composite operation "src".
861 | * Beware that WebKit-based browsers may leave the contents of the destionation
862 | * layer where the source layer is transparent, despite the definition of this
863 | * operation.
864 | */
865 | Guacamole.Layer.SRC = 0xC;
866 |
867 | /**
868 | * Represents a single pixel of image data. All components have a minimum value
869 | * of 0 and a maximum value of 255.
870 | *
871 | * @constructor
872 | *
873 | * @param {Number} r The red component of this pixel.
874 | * @param {Number} g The green component of this pixel.
875 | * @param {Number} b The blue component of this pixel.
876 | * @param {Number} a The alpha component of this pixel.
877 | */
878 | Guacamole.Layer.Pixel = function(r, g, b, a) {
879 |
880 | /**
881 | * The red component of this pixel, where 0 is the minimum value,
882 | * and 255 is the maximum.
883 | */
884 | this.red = r;
885 |
886 | /**
887 | * The green component of this pixel, where 0 is the minimum value,
888 | * and 255 is the maximum.
889 | */
890 | this.green = g;
891 |
892 | /**
893 | * The blue component of this pixel, where 0 is the minimum value,
894 | * and 255 is the maximum.
895 | */
896 | this.blue = b;
897 |
898 | /**
899 | * The alpha component of this pixel, where 0 is the minimum value,
900 | * and 255 is the maximum.
901 | */
902 | this.alpha = a;
903 |
904 | };
905 |
--------------------------------------------------------------------------------
/src/js/guacamole/OutputStream.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Glyptodon LLC
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 |
23 | var Guacamole = Guacamole || {};
24 |
25 | /**
26 | * Abstract stream which can receive data.
27 | *
28 | * @constructor
29 | * @param {Guacamole.Client} client The client owning this stream.
30 | * @param {Number} index The index of this stream.
31 | */
32 | Guacamole.OutputStream = function(client, index) {
33 |
34 | /**
35 | * Reference to this stream.
36 | * @private
37 | */
38 | var guac_stream = this;
39 |
40 | /**
41 | * The index of this stream.
42 | * @type Number
43 | */
44 | this.index = index;
45 |
46 | /**
47 | * Fired whenever an acknowledgement is received from the server, indicating
48 | * that a stream operation has completed, or an error has occurred.
49 | *
50 | * @event
51 | * @param {Guacamole.Status} status The status of the operation.
52 | */
53 | this.onack = null;
54 |
55 | /**
56 | * Writes the given base64-encoded data to this stream as a blob.
57 | *
58 | * @param {String} data The base64-encoded data to send.
59 | */
60 | this.sendBlob = function(data) {
61 | client.sendBlob(guac_stream.index, data);
62 | };
63 |
64 | /**
65 | * Closes this stream.
66 | */
67 | this.sendEnd = function() {
68 | client.endStream(guac_stream.index);
69 | };
70 |
71 | };
72 |
--------------------------------------------------------------------------------
/src/js/guacamole/Parser.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Glyptodon LLC
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 |
23 | var Guacamole = Guacamole || {};
24 |
25 | /**
26 | * Simple Guacamole protocol parser that invokes an oninstruction event when
27 | * full instructions are available from data received via receive().
28 | *
29 | * @constructor
30 | */
31 | Guacamole.Parser = function() {
32 |
33 | /**
34 | * Reference to this parser.
35 | * @private
36 | */
37 | var parser = this;
38 |
39 | /**
40 | * Current buffer of received data. This buffer grows until a full
41 | * element is available. After a full element is available, that element
42 | * is flushed into the element buffer.
43 | *
44 | * @private
45 | */
46 | var buffer = "";
47 |
48 | /**
49 | * Buffer of all received, complete elements. After an entire instruction
50 | * is read, this buffer is flushed, and a new instruction begins.
51 | *
52 | * @private
53 | */
54 | var element_buffer = [];
55 |
56 | // The location of the last element's terminator
57 | var element_end = -1;
58 |
59 | // Where to start the next length search or the next element
60 | var start_index = 0;
61 |
62 | /**
63 | * Appends the given instruction data packet to the internal buffer of
64 | * this Guacamole.Parser, executing all completed instructions at
65 | * the beginning of this buffer, if any.
66 | *
67 | * @param {String} packet The instruction data to receive.
68 | */
69 | this.receive = function(packet) {
70 |
71 | // Truncate buffer as necessary
72 | if (start_index > 4096 && element_end >= start_index) {
73 |
74 | buffer = buffer.substring(start_index);
75 |
76 | // Reset parse relative to truncation
77 | element_end -= start_index;
78 | start_index = 0;
79 |
80 | }
81 |
82 | // Append data to buffer
83 | buffer += packet;
84 |
85 | // While search is within currently received data
86 | while (element_end < buffer.length) {
87 |
88 | // If we are waiting for element data
89 | if (element_end >= start_index) {
90 |
91 | // We now have enough data for the element. Parse.
92 | var element = buffer.substring(start_index, element_end);
93 | var terminator = buffer.substring(element_end, element_end+1);
94 |
95 | // Add element to array
96 | element_buffer.push(element);
97 |
98 | // If last element, handle instruction
99 | if (terminator == ";") {
100 |
101 | // Get opcode
102 | var opcode = element_buffer.shift();
103 |
104 | // Call instruction handler.
105 | if (parser.oninstruction != null)
106 | parser.oninstruction(opcode, element_buffer);
107 |
108 | // Clear elements
109 | element_buffer.length = 0;
110 |
111 | }
112 | else if (terminator != ',')
113 | throw new Error("Illegal terminator.");
114 |
115 | // Start searching for length at character after
116 | // element terminator
117 | start_index = element_end + 1;
118 |
119 | }
120 |
121 | // Search for end of length
122 | var length_end = buffer.indexOf(".", start_index);
123 | if (length_end != -1) {
124 |
125 | // Parse length
126 | var length = parseInt(buffer.substring(element_end+1, length_end));
127 | if (isNaN(length))
128 | throw new Error("Non-numeric character in element length.");
129 |
130 | // Calculate start of element
131 | start_index = length_end + 1;
132 |
133 | // Calculate location of element terminator
134 | element_end = start_index + length;
135 |
136 | }
137 |
138 | // If no period yet, continue search when more data
139 | // is received
140 | else {
141 | start_index = buffer.length;
142 | break;
143 | }
144 |
145 | } // end parse loop
146 |
147 | };
148 |
149 | /**
150 | * Fired once for every complete Guacamole instruction received, in order.
151 | *
152 | * @event
153 | * @param {String} opcode The Guacamole instruction opcode.
154 | * @param {Array} parameters The parameters provided for the instruction,
155 | * if any.
156 | */
157 | this.oninstruction = null;
158 |
159 | };
160 |
--------------------------------------------------------------------------------
/src/js/guacamole/Status.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Glyptodon LLC
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 |
23 | var Guacamole = Guacamole || {};
24 |
25 | /**
26 | * A Guacamole status. Each Guacamole status consists of a status code, defined
27 | * by the protocol, and an optional human-readable message, usually only
28 | * included for debugging convenience.
29 | *
30 | * @param {Number} code The Guacamole status code, as defined by
31 | * Guacamole.Status.Code.
32 | * @param {String} [message] An optional human-readable message.
33 | */
34 | Guacamole.Status = function(code, message) {
35 |
36 | /**
37 | * Reference to this Guacamole.Status.
38 | * @private
39 | */
40 | var guac_status = this;
41 |
42 | /**
43 | * The Guacamole status code.
44 | * @see Guacamole.Status.Code
45 | * @type Number
46 | */
47 | this.code = code;
48 |
49 | /**
50 | * An arbitrary human-readable message associated with this status, if any.
51 | * The human-readable message is not required, and is generally provided
52 | * for debugging purposes only. For user feedback, it is better to translate
53 | * the Guacamole status code into a message.
54 | *
55 | * @type String
56 | */
57 | this.message = message;
58 |
59 | /**
60 | * Returns whether this status represents an error.
61 | * @returns {Boolean} true if this status represents an error, false
62 | * otherwise.
63 | */
64 | this.isError = function() {
65 | return guac_status.code < 0 || guac_status.code > 0x00FF;
66 | };
67 |
68 | };
69 |
70 | /**
71 | * Enumeration of all Guacamole status codes.
72 | */
73 | Guacamole.Status.Code = {
74 |
75 | /**
76 | * The operation succeeded.
77 | *
78 | * @type Number
79 | */
80 | "SUCCESS": 0x0000,
81 |
82 | /**
83 | * The requested operation is unsupported.
84 | *
85 | * @type Number
86 | */
87 | "UNSUPPORTED": 0x0100,
88 |
89 | /**
90 | * The operation could not be performed due to an internal failure.
91 | *
92 | * @type Number
93 | */
94 | "SERVER_ERROR": 0x0200,
95 |
96 | /**
97 | * The operation could not be performed as the server is busy.
98 | *
99 | * @type Number
100 | */
101 | "SERVER_BUSY": 0x0201,
102 |
103 | /**
104 | * The operation was unsuccessful due to an error or otherwise unexpected
105 | * condition of the upstream server.
106 | *
107 | * @type Number
108 | */
109 | "UPSTREAM_TIMEOUT": 0x0202,
110 |
111 | /**
112 | * The operation could not be performed because the upstream server is not
113 | * responding.
114 | *
115 | * @type Number
116 | */
117 | "UPSTREAM_ERROR": 0x0203,
118 |
119 | /**
120 | * The operation could not be performed as the requested resource does not
121 | * exist.
122 | *
123 | * @type Number
124 | */
125 | "RESOURCE_NOT_FOUND": 0x0204,
126 |
127 | /**
128 | * The operation could not be performed as the requested resource is
129 | * already in use.
130 | *
131 | * @type Number
132 | */
133 | "RESOURCE_CONFLICT": 0x0205,
134 |
135 | /**
136 | * The operation could not be performed because bad parameters were given.
137 | *
138 | * @type Number
139 | */
140 | "CLIENT_BAD_REQUEST": 0x0300,
141 |
142 | /**
143 | * Permission was denied to perform the operation, as the user is not yet
144 | * authorized (not yet logged in, for example).
145 | *
146 | * @type Number
147 | */
148 | "CLIENT_UNAUTHORIZED": 0x0301,
149 |
150 | /**
151 | * Permission was denied to perform the operation, and this permission will
152 | * not be granted even if the user is authorized.
153 | *
154 | * @type Number
155 | */
156 | "CLIENT_FORBIDDEN": 0x0303,
157 |
158 | /**
159 | * The client took too long to respond.
160 | *
161 | * @type Number
162 | */
163 | "CLIENT_TIMEOUT": 0x0308,
164 |
165 | /**
166 | * The client sent too much data.
167 | *
168 | * @type Number
169 | */
170 | "CLIENT_OVERRUN": 0x030D,
171 |
172 | /**
173 | * The client sent data of an unsupported or unexpected type.
174 | *
175 | * @type Number
176 | */
177 | "CLIENT_BAD_TYPE": 0x030F,
178 |
179 | /**
180 | * The operation failed because the current client is already using too
181 | * many resources.
182 | *
183 | * @type Number
184 | */
185 | "CLIENT_TOO_MANY": 0x031D
186 |
187 | };
188 |
--------------------------------------------------------------------------------
/src/js/guacamole/StringReader.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Glyptodon LLC
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 |
23 | var Guacamole = Guacamole || {};
24 |
25 | /**
26 | * A reader which automatically handles the given input stream, returning
27 | * strictly text data. Note that this object will overwrite any installed event
28 | * handlers on the given Guacamole.InputStream.
29 | *
30 | * @constructor
31 | * @param {Guacamole.InputStream} stream The stream that data will be read
32 | * from.
33 | */
34 | Guacamole.StringReader = function(stream) {
35 |
36 | /**
37 | * Reference to this Guacamole.InputStream.
38 | * @private
39 | */
40 | var guac_reader = this;
41 |
42 | /**
43 | * Wrapped Guacamole.ArrayBufferReader.
44 | * @private
45 | * @type Guacamole.ArrayBufferReader
46 | */
47 | var array_reader = new Guacamole.ArrayBufferReader(stream);
48 |
49 | /**
50 | * The number of bytes remaining for the current codepoint.
51 | *
52 | * @type Number
53 | */
54 | var bytes_remaining = 0;
55 |
56 | /**
57 | * The current codepoint value, as calculated from bytes read so far.
58 | * @type Number
59 | */
60 | var codepoint = 0;
61 |
62 | /**
63 | * Decodes the given UTF-8 data into a Unicode string. The data may end in
64 | * the middle of a multibyte character.
65 | *
66 | * @private
67 | * @param {ArrayBuffer} buffer Arbitrary UTF-8 data.
68 | * @return {String} A decoded Unicode string.
69 | */
70 | function __decode_utf8(buffer) {
71 |
72 | var text = "";
73 |
74 | var bytes = new Uint8Array(buffer);
75 | for (var i=0; i= buffer.length) {
78 | var new_buffer = new Uint8Array((length+bytes)*2);
79 | new_buffer.set(buffer);
80 | buffer = new_buffer;
81 | }
82 |
83 | length += bytes;
84 |
85 | }
86 |
87 | /**
88 | * Appends a single Unicode character to the current buffer, resizing the
89 | * buffer if necessary. The character will be encoded as UTF-8.
90 | *
91 | * @private
92 | * @param {Number} codepoint The codepoint of the Unicode character to
93 | * append.
94 | */
95 | function __append_utf8(codepoint) {
96 |
97 | var mask;
98 | var bytes;
99 |
100 | // 1 byte
101 | if (codepoint <= 0x7F) {
102 | mask = 0x00;
103 | bytes = 1;
104 | }
105 |
106 | // 2 byte
107 | else if (codepoint <= 0x7FF) {
108 | mask = 0xC0;
109 | bytes = 2;
110 | }
111 |
112 | // 3 byte
113 | else if (codepoint <= 0xFFFF) {
114 | mask = 0xE0;
115 | bytes = 3;
116 | }
117 |
118 | // 4 byte
119 | else if (codepoint <= 0x1FFFFF) {
120 | mask = 0xF0;
121 | bytes = 4;
122 | }
123 |
124 | // If invalid codepoint, append replacement character
125 | else {
126 | __append_utf8(0xFFFD);
127 | return;
128 | }
129 |
130 | // Offset buffer by size
131 | __expand(bytes);
132 | var offset = length - 1;
133 |
134 | // Add trailing bytes, if any
135 | for (var i=1; i>= 6;
138 | }
139 |
140 | // Set initial byte
141 | buffer[offset] = mask | codepoint;
142 |
143 | }
144 |
145 | /**
146 | * Encodes the given string as UTF-8, returning an ArrayBuffer containing
147 | * the resulting bytes.
148 | *
149 | * @private
150 | * @param {String} text The string to encode as UTF-8.
151 | * @return {Uint8Array} The encoded UTF-8 data.
152 | */
153 | function __encode_utf8(text) {
154 |
155 | // Fill buffer with UTF-8
156 | for (var i=0; i 0) {
163 | var out_buffer = buffer.subarray(0, length);
164 | length = 0;
165 | return out_buffer;
166 | }
167 |
168 | }
169 |
170 | /**
171 | * Sends the given text.
172 | *
173 | * @param {String} text The text to send.
174 | */
175 | this.sendText = function(text) {
176 | array_writer.sendData(__encode_utf8(text));
177 | };
178 |
179 | /**
180 | * Signals that no further text will be sent, effectively closing the
181 | * stream.
182 | */
183 | this.sendEnd = function() {
184 | array_writer.sendEnd();
185 | };
186 |
187 | /**
188 | * Fired for received data, if acknowledged by the server.
189 | * @event
190 | * @param {Guacamole.Status} status The status of the operation.
191 | */
192 | this.onack = null;
193 |
194 | };
--------------------------------------------------------------------------------
/src/js/guacamole/Tunnel.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Glyptodon LLC
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 |
23 | var Guacamole = Guacamole || {};
24 |
25 | /**
26 | * Core object providing abstract communication for Guacamole. This object
27 | * is a null implementation whose functions do nothing. Guacamole applications
28 | * should use {@link Guacamole.HTTPTunnel} instead, or implement their own tunnel based
29 | * on this one.
30 | *
31 | * @constructor
32 | * @see Guacamole.HTTPTunnel
33 | */
34 | Guacamole.Tunnel = function() {
35 |
36 | /**
37 | * Connect to the tunnel with the given optional data. This data is
38 | * typically used for authentication. The format of data accepted is
39 | * up to the tunnel implementation.
40 | *
41 | * @param {String} data The data to send to the tunnel when connecting.
42 | */
43 | this.connect = function(data) {};
44 |
45 | /**
46 | * Disconnect from the tunnel.
47 | */
48 | this.disconnect = function() {};
49 |
50 | /**
51 | * Send the given message through the tunnel to the service on the other
52 | * side. All messages are guaranteed to be received in the order sent.
53 | *
54 | * @param {...} elements The elements of the message to send to the
55 | * service on the other side of the tunnel.
56 | */
57 | this.sendMessage = function(elements) {};
58 |
59 | /**
60 | * The current state of this tunnel.
61 | *
62 | * @type Number
63 | */
64 | this.state = Guacamole.Tunnel.State.CONNECTING;
65 |
66 | /**
67 | * The maximum amount of time to wait for data to be received, in
68 | * milliseconds. If data is not received within this amount of time,
69 | * the tunnel is closed with an error. The default value is 15000.
70 | * Set to zero for no timeout.
71 | *
72 | * @type Number
73 | */
74 | this.receiveTimeout = 15000;
75 |
76 | /**
77 | * Fired whenever an error is encountered by the tunnel.
78 | *
79 | * @event
80 | * @param {Guacamole.Status} status A status object which describes the
81 | * error.
82 | */
83 | this.onerror = null;
84 |
85 | /**
86 | * Fired whenever the state of the tunnel changes.
87 | *
88 | * @event
89 | * @param {Number} state The new state of the client.
90 | */
91 | this.onstatechange = null;
92 |
93 | /**
94 | * Fired once for every complete Guacamole instruction received, in order.
95 | *
96 | * @event
97 | * @param {String} opcode The Guacamole instruction opcode.
98 | * @param {Array} parameters The parameters provided for the instruction,
99 | * if any.
100 | */
101 | this.oninstruction = null;
102 |
103 | };
104 |
105 | /**
106 | * All possible tunnel states.
107 | */
108 | Guacamole.Tunnel.State = {
109 |
110 | /**
111 | * A connection is in pending. It is not yet known whether connection was
112 | * successful.
113 | *
114 | * @type Number
115 | */
116 | "CONNECTING": 0,
117 |
118 | /**
119 | * Connection was successful, and data is being received.
120 | *
121 | * @type Number
122 | */
123 | "OPEN": 1,
124 |
125 | /**
126 | * The connection is closed. Connection may not have been successful, the
127 | * tunnel may have been explicitly closed by either side, or an error may
128 | * have occurred.
129 | *
130 | * @type Number
131 | */
132 | "CLOSED": 2
133 |
134 | };
135 |
136 | /**
137 | * Guacamole Tunnel implemented over HTTP via XMLHttpRequest.
138 | *
139 | * @constructor
140 | * @augments Guacamole.Tunnel
141 | * @param {String} tunnelURL The URL of the HTTP tunneling service.
142 | */
143 | Guacamole.HTTPTunnel = function(tunnelURL) {
144 |
145 | /**
146 | * Reference to this HTTP tunnel.
147 | * @private
148 | */
149 | var tunnel = this;
150 |
151 | var tunnel_uuid;
152 |
153 | var TUNNEL_CONNECT = tunnelURL + "?connect";
154 | var TUNNEL_READ = tunnelURL + "?read:";
155 | var TUNNEL_WRITE = tunnelURL + "?write:";
156 |
157 | var POLLING_ENABLED = 1;
158 | var POLLING_DISABLED = 0;
159 |
160 | // Default to polling - will be turned off automatically if not needed
161 | var pollingMode = POLLING_ENABLED;
162 |
163 | var sendingMessages = false;
164 | var outputMessageBuffer = "";
165 |
166 | /**
167 | * The current receive timeout ID, if any.
168 | * @private
169 | */
170 | var receive_timeout = null;
171 |
172 | /**
173 | * Closes this tunnel, signaling the given status and corresponding
174 | * message, which will be sent to the onerror handler if the status is
175 | * an error status.
176 | *
177 | * @private
178 | * @param {Guacamole.Status} status The status causing the connection to
179 | * close;
180 | */
181 | function close_tunnel(status) {
182 |
183 | // Ignore if already closed
184 | if (tunnel.state === Guacamole.Tunnel.State.CLOSED)
185 | return;
186 |
187 | // If connection closed abnormally, signal error.
188 | if (status.code !== Guacamole.Status.Code.SUCCESS && tunnel.onerror) {
189 |
190 | // Ignore RESOURCE_NOT_FOUND if we've already connected, as that
191 | // only signals end-of-stream for the HTTP tunnel.
192 | if (tunnel.state === Guacamole.Tunnel.State.CONNECTING
193 | || status.code !== Guacamole.Status.Code.RESOURCE_NOT_FOUND)
194 | tunnel.onerror(status);
195 |
196 | }
197 |
198 | // Mark as closed
199 | tunnel.state = Guacamole.Tunnel.State.CLOSED;
200 |
201 | // Reset output message buffer
202 | sendingMessages = false;
203 |
204 | if (tunnel.onstatechange)
205 | tunnel.onstatechange(tunnel.state);
206 |
207 | }
208 |
209 |
210 | this.sendMessage = function() {
211 |
212 | // Do not attempt to send messages if not connected
213 | if (tunnel.state !== Guacamole.Tunnel.State.OPEN)
214 | return;
215 |
216 | // Do not attempt to send empty messages
217 | if (arguments.length === 0)
218 | return;
219 |
220 | /**
221 | * Converts the given value to a length/string pair for use as an
222 | * element in a Guacamole instruction.
223 | *
224 | * @private
225 | * @param value The value to convert.
226 | * @return {String} The converted value.
227 | */
228 | function getElement(value) {
229 | var string = new String(value);
230 | return string.length + "." + string;
231 | }
232 |
233 | // Initialized message with first element
234 | var message = getElement(arguments[0]);
235 |
236 | // Append remaining elements
237 | for (var i=1; i 0) {
259 |
260 | sendingMessages = true;
261 |
262 | var message_xmlhttprequest = new XMLHttpRequest();
263 | message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel_uuid);
264 | message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
265 |
266 | // Once response received, send next queued event.
267 | message_xmlhttprequest.onreadystatechange = function() {
268 | if (message_xmlhttprequest.readyState === 4) {
269 |
270 | // If an error occurs during send, handle it
271 | if (message_xmlhttprequest.status !== 200)
272 | handleHTTPTunnelError(message_xmlhttprequest);
273 |
274 | // Otherwise, continue the send loop
275 | else
276 | sendPendingMessages();
277 |
278 | }
279 | };
280 |
281 | message_xmlhttprequest.send(outputMessageBuffer);
282 | outputMessageBuffer = ""; // Clear buffer
283 |
284 | }
285 | else
286 | sendingMessages = false;
287 |
288 | }
289 |
290 | function handleHTTPTunnelError(xmlhttprequest) {
291 |
292 | var code = parseInt(xmlhttprequest.getResponseHeader("Guacamole-Status-Code"));
293 | var message = xmlhttprequest.getResponseHeader("Guacamole-Error-Message");
294 |
295 | close_tunnel(new Guacamole.Status(code, message));
296 |
297 | }
298 |
299 | function handleResponse(xmlhttprequest) {
300 |
301 | var interval = null;
302 | var nextRequest = null;
303 |
304 | var dataUpdateEvents = 0;
305 |
306 | // The location of the last element's terminator
307 | var elementEnd = -1;
308 |
309 | // Where to start the next length search or the next element
310 | var startIndex = 0;
311 |
312 | // Parsed elements
313 | var elements = new Array();
314 |
315 | function parseResponse() {
316 |
317 | // Do not handle responses if not connected
318 | if (tunnel.state !== Guacamole.Tunnel.State.OPEN) {
319 |
320 | // Clean up interval if polling
321 | if (interval !== null)
322 | clearInterval(interval);
323 |
324 | return;
325 | }
326 |
327 | // Do not parse response yet if not ready
328 | if (xmlhttprequest.readyState < 2) return;
329 |
330 | // Attempt to read status
331 | var status;
332 | try { status = xmlhttprequest.status; }
333 |
334 | // If status could not be read, assume successful.
335 | catch (e) { status = 200; }
336 |
337 | // Start next request as soon as possible IF request was successful
338 | if (!nextRequest && status === 200)
339 | nextRequest = makeRequest();
340 |
341 | // Parse stream when data is received and when complete.
342 | if (xmlhttprequest.readyState === 3 ||
343 | xmlhttprequest.readyState === 4) {
344 |
345 | // Also poll every 30ms (some browsers don't repeatedly call onreadystatechange for new data)
346 | if (pollingMode === POLLING_ENABLED) {
347 | if (xmlhttprequest.readyState === 3 && !interval)
348 | interval = setInterval(parseResponse, 30);
349 | else if (xmlhttprequest.readyState === 4 && !interval)
350 | clearInterval(interval);
351 | }
352 |
353 | // If canceled, stop transfer
354 | if (xmlhttprequest.status === 0) {
355 | tunnel.disconnect();
356 | return;
357 | }
358 |
359 | // Halt on error during request
360 | else if (xmlhttprequest.status !== 200) {
361 | handleHTTPTunnelError(xmlhttprequest);
362 | return;
363 | }
364 |
365 | // Attempt to read in-progress data
366 | var current;
367 | try { current = xmlhttprequest.responseText; }
368 |
369 | // Do not attempt to parse if data could not be read
370 | catch (e) { return; }
371 |
372 | // While search is within currently received data
373 | while (elementEnd < current.length) {
374 |
375 | // If we are waiting for element data
376 | if (elementEnd >= startIndex) {
377 |
378 | // We now have enough data for the element. Parse.
379 | var element = current.substring(startIndex, elementEnd);
380 | var terminator = current.substring(elementEnd, elementEnd+1);
381 |
382 | // Add element to array
383 | elements.push(element);
384 |
385 | // If last element, handle instruction
386 | if (terminator === ";") {
387 |
388 | // Get opcode
389 | var opcode = elements.shift();
390 |
391 | // Call instruction handler.
392 | if (tunnel.oninstruction)
393 | tunnel.oninstruction(opcode, elements);
394 |
395 | // Clear elements
396 | elements.length = 0;
397 |
398 | }
399 |
400 | // Start searching for length at character after
401 | // element terminator
402 | startIndex = elementEnd + 1;
403 |
404 | }
405 |
406 | // Search for end of length
407 | var lengthEnd = current.indexOf(".", startIndex);
408 | if (lengthEnd !== -1) {
409 |
410 | // Parse length
411 | var length = parseInt(current.substring(elementEnd+1, lengthEnd));
412 |
413 | // If we're done parsing, handle the next response.
414 | if (length === 0) {
415 |
416 | // Clean up interval if polling
417 | if (!interval)
418 | clearInterval(interval);
419 |
420 | // Clean up object
421 | xmlhttprequest.onreadystatechange = null;
422 | xmlhttprequest.abort();
423 |
424 | // Start handling next request
425 | if (nextRequest)
426 | handleResponse(nextRequest);
427 |
428 | // Done parsing
429 | break;
430 |
431 | }
432 |
433 | // Calculate start of element
434 | startIndex = lengthEnd + 1;
435 |
436 | // Calculate location of element terminator
437 | elementEnd = startIndex + length;
438 |
439 | }
440 |
441 | // If no period yet, continue search when more data
442 | // is received
443 | else {
444 | startIndex = current.length;
445 | break;
446 | }
447 |
448 | } // end parse loop
449 |
450 | }
451 |
452 | }
453 |
454 | // If response polling enabled, attempt to detect if still
455 | // necessary (via wrapping parseResponse())
456 | if (pollingMode === POLLING_ENABLED) {
457 | xmlhttprequest.onreadystatechange = function() {
458 |
459 | // If we receive two or more readyState==3 events,
460 | // there is no need to poll.
461 | if (xmlhttprequest.readyState === 3) {
462 | dataUpdateEvents++;
463 | if (dataUpdateEvents >= 2) {
464 | pollingMode = POLLING_DISABLED;
465 | xmlhttprequest.onreadystatechange = parseResponse;
466 | }
467 | }
468 |
469 | parseResponse();
470 | };
471 | }
472 |
473 | // Otherwise, just parse
474 | else
475 | xmlhttprequest.onreadystatechange = parseResponse;
476 |
477 | parseResponse();
478 |
479 | }
480 |
481 | /**
482 | * Arbitrary integer, unique for each tunnel read request.
483 | * @private
484 | */
485 | var request_id = 0;
486 |
487 | function makeRequest() {
488 |
489 | // Make request, increment request ID
490 | var xmlhttprequest = new XMLHttpRequest();
491 | xmlhttprequest.open("GET", TUNNEL_READ + tunnel_uuid + ":" + (request_id++));
492 | xmlhttprequest.send(null);
493 |
494 | return xmlhttprequest;
495 |
496 | }
497 |
498 | this.connect = function(data) {
499 |
500 | // Start tunnel and connect
501 | var connect_xmlhttprequest = new XMLHttpRequest();
502 | connect_xmlhttprequest.onreadystatechange = function() {
503 |
504 | if (connect_xmlhttprequest.readyState !== 4)
505 | return;
506 |
507 | // If failure, throw error
508 | if (connect_xmlhttprequest.status !== 200) {
509 | handleHTTPTunnelError(connect_xmlhttprequest);
510 | return;
511 | }
512 |
513 | // Get UUID from response
514 | tunnel_uuid = connect_xmlhttprequest.responseText;
515 |
516 | tunnel.state = Guacamole.Tunnel.State.OPEN;
517 | if (tunnel.onstatechange)
518 | tunnel.onstatechange(tunnel.state);
519 |
520 | // Start reading data
521 | handleResponse(makeRequest());
522 |
523 | };
524 |
525 | connect_xmlhttprequest.open("POST", TUNNEL_CONNECT, true);
526 | connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
527 | connect_xmlhttprequest.send(data);
528 |
529 | };
530 |
531 | this.disconnect = function() {
532 | close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SUCCESS, "Manually closed."));
533 | };
534 |
535 | };
536 |
537 | Guacamole.HTTPTunnel.prototype = new Guacamole.Tunnel();
538 |
539 | /**
540 | * Guacamole Tunnel implemented over WebSocket via XMLHttpRequest.
541 | *
542 | * @constructor
543 | * @augments Guacamole.Tunnel
544 | * @param {String} tunnelURL The URL of the WebSocket tunneling service.
545 | */
546 | Guacamole.WebSocketTunnel = function(tunnelURL) {
547 |
548 | /**
549 | * Reference to this WebSocket tunnel.
550 | * @private
551 | */
552 | var tunnel = this;
553 |
554 | /**
555 | * The WebSocket used by this tunnel.
556 | * @private
557 | */
558 | var socket = null;
559 |
560 | /**
561 | * The current receive timeout ID, if any.
562 | * @private
563 | */
564 | var receive_timeout = null;
565 |
566 | /**
567 | * The WebSocket protocol corresponding to the protocol used for the current
568 | * location.
569 | * @private
570 | */
571 | var ws_protocol = {
572 | "http:": "ws:",
573 | "https:": "wss:"
574 | };
575 |
576 | // Transform current URL to WebSocket URL
577 |
578 | // If not already a websocket URL
579 | if ( tunnelURL.substring(0, 3) !== "ws:"
580 | && tunnelURL.substring(0, 4) !== "wss:") {
581 |
582 | var protocol = ws_protocol[window.location.protocol];
583 |
584 | // If absolute URL, convert to absolute WS URL
585 | if (tunnelURL.substring(0, 1) === "/")
586 | tunnelURL =
587 | protocol
588 | + "//" + window.location.host
589 | + tunnelURL;
590 |
591 | // Otherwise, construct absolute from relative URL
592 | else {
593 |
594 | // Get path from pathname
595 | var slash = window.location.pathname.lastIndexOf("/");
596 | var path = window.location.pathname.substring(0, slash + 1);
597 |
598 | // Construct absolute URL
599 | tunnelURL =
600 | protocol
601 | + "//" + window.location.host
602 | + path
603 | + tunnelURL;
604 |
605 | }
606 |
607 | }
608 |
609 | /**
610 | * Closes this tunnel, signaling the given status and corresponding
611 | * message, which will be sent to the onerror handler if the status is
612 | * an error status.
613 | *
614 | * @private
615 | * @param {Guacamole.Status} status The status causing the connection to
616 | * close;
617 | */
618 | function close_tunnel(status) {
619 |
620 | // Ignore if already closed
621 | if (tunnel.state === Guacamole.Tunnel.State.CLOSED)
622 | return;
623 |
624 | // If connection closed abnormally, signal error.
625 | if (status.code !== Guacamole.Status.Code.SUCCESS && tunnel.onerror)
626 | tunnel.onerror(status);
627 |
628 | // Mark as closed
629 | tunnel.state = Guacamole.Tunnel.State.CLOSED;
630 | if (tunnel.onstatechange)
631 | tunnel.onstatechange(tunnel.state);
632 |
633 | socket.close();
634 |
635 | }
636 |
637 | this.sendRawMessage = function(message) {
638 | socket.send(message);
639 | }
640 |
641 | this.sendMessage = function(elements) {
642 |
643 | // Do not attempt to send messages if not connected
644 | if (tunnel.state !== Guacamole.Tunnel.State.OPEN)
645 | return;
646 |
647 | // Do not attempt to send empty messages
648 | if (arguments.length === 0)
649 | return;
650 |
651 | /**
652 | * Converts the given value to a length/string pair for use as an
653 | * element in a Guacamole instruction.
654 | *
655 | * @private
656 | * @param value The value to convert.
657 | * @return {String} The converted value.
658 | */
659 | function getElement(value) {
660 | var string = new String(value);
661 | return string.length + "." + string;
662 | }
663 |
664 | // Initialized message with first element
665 | var message = getElement(arguments[0]);
666 |
667 | // Append remaining elements
668 | for (var i=1; i li > div > span.admin {
64 | font-weight: bold;
65 | color: red;
66 | }
67 | .moderator.list-group-item, .message-pane > li > div > span.moderator {
68 | font-weight: bold;
69 | color: green;
70 | }
71 | .dev.list-group-item, .message-pane > li > div > span.dev {
72 | font-weight: bold;
73 | color: blue;
74 | }
75 | .waiting-turn.list-group-item {
76 | background-color: #FFFFB3;
77 | }
78 | .has-turn.list-group-item {
79 | background-color: #CFF2FC;
80 | }
81 | .message-pane-wrapper {
82 | border-radius: 4px;
83 | clear: both;
84 | overflow: auto;
85 | position: absolute;
86 | top: 0;
87 | right: 0;
88 | bottom: 0;
89 | left: 0;
90 | height: auto;
91 | width: auto;
92 | /*margin: 0 200px 31px 0;*/
93 | margin-bottom: 44px;
94 | background-color: #eee;
95 | font-size: 13px;
96 | padding: 0 5px;
97 | }
98 |
99 | .message-pane {
100 | padding-top: 1px;
101 | }
102 |
103 | .message-pane li {
104 | border-bottom: 1px solid #ccc;
105 | box-shadow: 0 1px 0 0 white;
106 | }
107 |
108 | .message-pane small {
109 | display: none;
110 | font-size: 10px;
111 | position: absolute;
112 | background-color: #f7f7f7;
113 | text-align: center;
114 | line-height: 20px;
115 | margin: 4px 0;
116 | padding: 0 5px;
117 | right: 5px;
118 | }
119 |
120 | .message-pane li:hover {
121 | background-color: #f7f7f7;
122 | }
123 |
124 | .message-pane li:hover small {
125 | display: block;
126 | }
127 |
128 | .message-pane li>div {
129 | overflow: auto;
130 | padding: 2px 0 2px 0;
131 | line-height: 24px;
132 | white-space: -o-pre-wrap; /* Opera */
133 | word-wrap: break-word; /* Internet Explorer 5.5+ */
134 | display: inline-block;
135 | }
136 |
137 | .message-pane li>div.server-message {
138 | font-weight: bold;
139 | }
140 |
141 | .message-pane li>div p {
142 | margin: 0;
143 | }
144 |
145 | .message-pane .username {
146 | font-weight: bold;
147 | white-space: nowrap;
148 | display: block;
149 | float: left;
150 | overflow: hidden;
151 | text-align: right;
152 | color: black;
153 | }
154 |
155 | .message-pane .spacer {
156 | font-size: 14px;
157 | margin-right: 5px;
158 | }
159 | /* Guacamole Keyboard */
160 | .osk {
161 | width: 100%;
162 | position: relative;
163 | }
164 |
165 | .guac-keyboard {
166 | display: inline-block;
167 | width: 100%;
168 |
169 | margin: 0;
170 | padding: 0;
171 | cursor: default;
172 |
173 | text-align: left;
174 | vertical-align: middle;
175 | }
176 |
177 | .guac-keyboard,
178 | .guac-keyboard * {
179 | overflow: hidden;
180 | white-space: nowrap;
181 | }
182 |
183 | .guac-keyboard .guac-keyboard-key-container {
184 | display: inline-block;
185 | margin: 0.05em;
186 | position: relative;
187 | }
188 |
189 | .guac-keyboard .guac-keyboard-key {
190 |
191 | position: absolute;
192 | left: 0;
193 | right: 0;
194 | top: 0;
195 | bottom: 0;
196 |
197 | background: #444;
198 |
199 | border: 0.125em solid #666;
200 | -moz-border-radius: 0.25em;
201 | -webkit-border-radius: 0.25em;
202 | -khtml-border-radius: 0.25em;
203 | border-radius: 0.25em;
204 |
205 | color: white;
206 | font-size: 40%;
207 | font-weight: lighter;
208 | text-align: center;
209 | white-space: pre;
210 |
211 | text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25),
212 | 1px -1px 0 rgba(0, 0, 0, 0.25),
213 | -1px 1px 0 rgba(0, 0, 0, 0.25),
214 | -1px -1px 0 rgba(0, 0, 0, 0.25);
215 |
216 | }
217 |
218 | .guac-keyboard .guac-keyboard-key:hover {
219 | cursor: pointer;
220 | }
221 |
222 | .guac-keyboard .guac-keyboard-key.highlight {
223 | background: #666;
224 | border-color: #666;
225 | }
226 |
227 | /* Align some keys to the left */
228 | .guac-keyboard .guac-keyboard-key-caps,
229 | .guac-keyboard .guac-keyboard-key-enter,
230 | .guac-keyboard .guac-keyboard-key-tab,
231 | .guac-keyboard .guac-keyboard-key-lalt,
232 | .guac-keyboard .guac-keyboard-key-ralt,
233 | .guac-keyboard .guac-keyboard-key-lctrl,
234 | .guac-keyboard .guac-keyboard-key-rctrl,
235 | .guac-keyboard .guac-keyboard-key-lshift,
236 | .guac-keyboard .guac-keyboard-key-rshift {
237 | text-align: left;
238 | padding-left: 0.75em;
239 | }
240 |
241 | /* Active shift */
242 | .guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-rshift,
243 | .guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-lshift,
244 |
245 | /* Active ctrl */
246 | .guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-rctrl,
247 | .guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-lctrl,
248 |
249 | /* Active alt */
250 | .guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-ralt,
251 | .guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-lalt,
252 |
253 | /* Active caps */
254 | .guac-keyboard.guac-keyboard-modifier-caps .guac-keyboard-key-caps,
255 |
256 | /* Active super */
257 | .guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key-super,
258 | .guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key-menu {
259 | background: #882;
260 | border-color: #DD4;
261 | }
262 |
263 | .guac-keyboard .guac-keyboard-key.guac-keyboard-disabled {
264 | background: #CCC;
265 | border-color: #BBB;
266 | cursor: default;
267 | }
268 |
269 | .guac-keyboard .guac-keyboard-key.guac-keyboard-pressed {
270 | background: #822;
271 | border-color: #D44;
272 | }
273 |
274 | .guac-keyboard .guac-keyboard-group {
275 | line-height: 0;
276 | }
277 |
278 | .guac-keyboard .guac-keyboard-group.guac-keyboard-alpha,
279 | .guac-keyboard .guac-keyboard-group.guac-keyboard-movement {
280 | display: inline-block;
281 | text-align: center;
282 | vertical-align: top;
283 | }
284 |
285 | .guac-keyboard .guac-keyboard-group.guac-keyboard-main {
286 |
287 | /* IE10 */
288 | display: -ms-flexbox;
289 | -ms-flex-align: stretch;
290 | -ms-flex-direction: row;
291 |
292 | /* Ancient Mozilla */
293 | display: -moz-box;
294 | -moz-box-align: stretch;
295 | -moz-box-orient: horizontal;
296 |
297 | /* Ancient WebKit */
298 | display: -webkit-box;
299 | -webkit-box-align: stretch;
300 | -webkit-box-orient: horizontal;
301 |
302 | /* Old WebKit */
303 | display: -webkit-flex;
304 | -webkit-align-items: stretch;
305 | -webkit-flex-direction: row;
306 |
307 | /* W3C */
308 | display: flex;
309 | align-items: stretch;
310 | flex-direction: row;
311 |
312 | }
313 |
314 | .guac-keyboard .guac-keyboard-group.guac-keyboard-movement {
315 | -ms-flex: 1 1 auto;
316 | -moz-box-flex: 1;
317 | -webkit-box-flex: 1;
318 | -webkit-flex: 1 1 auto;
319 | flex: 1 1 auto;
320 | }
321 |
322 | .guac-keyboard .guac-keyboard-gap {
323 | display: inline-block;
324 | }
325 |
326 | /* Hide keycaps requiring modifiers which are NOT currently active. */
327 | .guac-keyboard:not(.guac-keyboard-modifier-caps)
328 | .guac-keyboard-cap.guac-keyboard-requires-caps,
329 |
330 | .guac-keyboard:not(.guac-keyboard-modifier-shift)
331 | .guac-keyboard-cap.guac-keyboard-requires-shift,
332 |
333 | /* Hide keycaps NOT requiring modifiers which ARE currently active, where that
334 | modifier is used to determine which cap is displayed for the current key. */
335 | .guac-keyboard.guac-keyboard-modifier-shift
336 | .guac-keyboard-key.guac-keyboard-uses-shift
337 | .guac-keyboard-cap:not(.guac-keyboard-requires-shift),
338 |
339 | .guac-keyboard.guac-keyboard-modifier-caps
340 | .guac-keyboard-key.guac-keyboard-uses-caps
341 | .guac-keyboard-cap:not(.guac-keyboard-requires-caps) {
342 |
343 | display: none;
344 |
345 | }
346 |
347 | /* Spinner */
348 | .sk-fading-circle {
349 | width: 100%;
350 | height: 100%;
351 | position: relative;
352 | }
353 |
354 | .sk-fading-circle .sk-circle {
355 | width: 100%;
356 | height: 100%;
357 | position: absolute;
358 | left: 0;
359 | top: 0;
360 | }
361 |
362 | .sk-fading-circle .sk-circle:before {
363 | content: '';
364 | display: block;
365 | margin: 0 auto;
366 | width: 15%;
367 | height: 15%;
368 | background-color: #333;
369 | border-radius: 100%;
370 | -webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
371 | animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
372 | }
373 | .sk-fading-circle .sk-circle2 {
374 | -webkit-transform: rotate(30deg);
375 | -ms-transform: rotate(30deg);
376 | transform: rotate(30deg);
377 | }
378 | .sk-fading-circle .sk-circle3 {
379 | -webkit-transform: rotate(60deg);
380 | -ms-transform: rotate(60deg);
381 | transform: rotate(60deg);
382 | }
383 | .sk-fading-circle .sk-circle4 {
384 | -webkit-transform: rotate(90deg);
385 | -ms-transform: rotate(90deg);
386 | transform: rotate(90deg);
387 | }
388 | .sk-fading-circle .sk-circle5 {
389 | -webkit-transform: rotate(120deg);
390 | -ms-transform: rotate(120deg);
391 | transform: rotate(120deg);
392 | }
393 | .sk-fading-circle .sk-circle6 {
394 | -webkit-transform: rotate(150deg);
395 | -ms-transform: rotate(150deg);
396 | transform: rotate(150deg);
397 | }
398 | .sk-fading-circle .sk-circle7 {
399 | -webkit-transform: rotate(180deg);
400 | -ms-transform: rotate(180deg);
401 | transform: rotate(180deg);
402 | }
403 | .sk-fading-circle .sk-circle8 {
404 | -webkit-transform: rotate(210deg);
405 | -ms-transform: rotate(210deg);
406 | transform: rotate(210deg);
407 | }
408 | .sk-fading-circle .sk-circle9 {
409 | -webkit-transform: rotate(240deg);
410 | -ms-transform: rotate(240deg);
411 | transform: rotate(240deg);
412 | }
413 | .sk-fading-circle .sk-circle10 {
414 | -webkit-transform: rotate(270deg);
415 | -ms-transform: rotate(270deg);
416 | transform: rotate(270deg);
417 | }
418 | .sk-fading-circle .sk-circle11 {
419 | -webkit-transform: rotate(300deg);
420 | -ms-transform: rotate(300deg);
421 | transform: rotate(300deg);
422 | }
423 | .sk-fading-circle .sk-circle12 {
424 | -webkit-transform: rotate(330deg);
425 | -ms-transform: rotate(330deg);
426 | transform: rotate(330deg);
427 | }
428 | .sk-fading-circle .sk-circle2:before {
429 | -webkit-animation-delay: -1.1s;
430 | animation-delay: -1.1s;
431 | }
432 | .sk-fading-circle .sk-circle3:before {
433 | -webkit-animation-delay: -1s;
434 | animation-delay: -1s;
435 | }
436 | .sk-fading-circle .sk-circle4:before {
437 | -webkit-animation-delay: -0.9s;
438 | animation-delay: -0.9s;
439 | }
440 | .sk-fading-circle .sk-circle5:before {
441 | -webkit-animation-delay: -0.8s;
442 | animation-delay: -0.8s;
443 | }
444 | .sk-fading-circle .sk-circle6:before {
445 | -webkit-animation-delay: -0.7s;
446 | animation-delay: -0.7s;
447 | }
448 | .sk-fading-circle .sk-circle7:before {
449 | -webkit-animation-delay: -0.6s;
450 | animation-delay: -0.6s;
451 | }
452 | .sk-fading-circle .sk-circle8:before {
453 | -webkit-animation-delay: -0.5s;
454 | animation-delay: -0.5s;
455 | }
456 | .sk-fading-circle .sk-circle9:before {
457 | -webkit-animation-delay: -0.4s;
458 | animation-delay: -0.4s;
459 | }
460 | .sk-fading-circle .sk-circle10:before {
461 | -webkit-animation-delay: -0.3s;
462 | animation-delay: -0.3s;
463 | }
464 | .sk-fading-circle .sk-circle11:before {
465 | -webkit-animation-delay: -0.2s;
466 | animation-delay: -0.2s;
467 | }
468 | .sk-fading-circle .sk-circle12:before {
469 | -webkit-animation-delay: -0.1s;
470 | animation-delay: -0.1s;
471 | }
472 |
473 | @-webkit-keyframes sk-circleFadeDelay {
474 | 0%, 39%, 100% { opacity: 0; }
475 | 40% { opacity: 1; }
476 | }
477 |
478 | @keyframes sk-circleFadeDelay {
479 | 0%, 39%, 100% { opacity: 0; }
480 | 40% { opacity: 1; }
481 | }
482 |
483 | #vm-list {
484 | display: flex;
485 | flex-wrap: wrap;
486 | }
487 |
--------------------------------------------------------------------------------
/src/res/notify.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/computernewb/collab-vm-classic-web-app/24ec00a270ef25f8792e3b4cd4a73d19c5e89629/src/res/notify.m4a
--------------------------------------------------------------------------------
/src/res/notify.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/computernewb/collab-vm-classic-web-app/24ec00a270ef25f8792e3b4cd4a73d19c5e89629/src/res/notify.mp3
--------------------------------------------------------------------------------
/src/res/notify.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/computernewb/collab-vm-classic-web-app/24ec00a270ef25f8792e3b4cd4a73d19c5e89629/src/res/notify.ogg
--------------------------------------------------------------------------------
/src/templates/navbar.html:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/src/templates/win-logo-ascii.html:
--------------------------------------------------------------------------------
1 |
66 |
67 |
--------------------------------------------------------------------------------