Initial commit
This commit is contained in:
8
kasmweb/utils/.eslintrc
Normal file
8
kasmweb/utils/.eslintrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": 0
|
||||
}
|
||||
}
|
||||
14
kasmweb/utils/README.md
Normal file
14
kasmweb/utils/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## WebSockets Proxy/Bridge
|
||||
|
||||
Websockify has been forked out into its own project. `launch.sh` wil
|
||||
automatically download it here if it is not already present and not
|
||||
installed as system-wide.
|
||||
|
||||
For more detailed description and usage information please refer to
|
||||
the [websockify README](https://github.com/novnc/websockify/blob/master/README.md).
|
||||
|
||||
The other versions of websockify (C, Node.js) and the associated test
|
||||
programs have been moved to
|
||||
[websockify](https://github.com/novnc/websockify). Websockify was
|
||||
formerly named wsproxy.
|
||||
|
||||
17
kasmweb/utils/b64-to-binary.pl
Executable file
17
kasmweb/utils/b64-to-binary.pl
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env perl
|
||||
use MIME::Base64;
|
||||
|
||||
for (<>) {
|
||||
unless (/^'([{}])(\d+)\1(.+?)',$/) {
|
||||
print;
|
||||
next;
|
||||
}
|
||||
|
||||
my ($dir, $amt, $b64) = ($1, $2, $3);
|
||||
|
||||
my $decoded = MIME::Base64::decode($b64) or die "Could not base64-decode line `$_`";
|
||||
|
||||
my $decoded_escaped = join "", map { "\\x$_" } unpack("(H2)*", $decoded);
|
||||
|
||||
print "'${dir}${amt}${dir}${decoded_escaped}',\n";
|
||||
}
|
||||
127
kasmweb/utils/genkeysymdef.js
Executable file
127
kasmweb/utils/genkeysymdef.js
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* genkeysymdef: X11 keysymdef.h to JavaScript converter
|
||||
* Copyright (C) 2018 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
let show_help = process.argv.length === 2;
|
||||
let filename;
|
||||
|
||||
for (let i = 2; i < process.argv.length; ++i) {
|
||||
switch (process.argv[i]) {
|
||||
case "--help":
|
||||
case "-h":
|
||||
show_help = true;
|
||||
break;
|
||||
case "--file":
|
||||
case "-f":
|
||||
default:
|
||||
filename = process.argv[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!filename) {
|
||||
show_help = true;
|
||||
console.log("Error: No filename specified\n");
|
||||
}
|
||||
|
||||
if (show_help) {
|
||||
console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
|
||||
console.log("Usage: node parse.js [options] filename:");
|
||||
console.log(" -h [ --help ] Produce this help message");
|
||||
console.log(" filename The keysymdef.h file to parse");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const buf = fs.readFileSync(filename);
|
||||
const str = buf.toString('utf8');
|
||||
|
||||
const re = /^#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m;
|
||||
|
||||
const arr = str.split('\n');
|
||||
|
||||
const codepoints = {};
|
||||
|
||||
for (let i = 0; i < arr.length; ++i) {
|
||||
const result = re.exec(arr[i]);
|
||||
if (result) {
|
||||
const keyname = result[1];
|
||||
const keysym = parseInt(result[2], 16);
|
||||
const remainder = result[3];
|
||||
|
||||
const unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
|
||||
if (unicodeRes) {
|
||||
const unicode = parseInt(unicodeRes[1], 16);
|
||||
// The first entry is the preferred one
|
||||
if (!codepoints[unicode]) {
|
||||
codepoints[unicode] = { keysym: keysym, name: keyname };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let out =
|
||||
"/*\n" +
|
||||
" * Mapping from Unicode codepoints to X11/RFB keysyms\n" +
|
||||
" *\n" +
|
||||
" * This file was automatically generated from keysymdef.h\n" +
|
||||
" * DO NOT EDIT!\n" +
|
||||
" */\n" +
|
||||
"\n" +
|
||||
"/* Functions at the bottom */\n" +
|
||||
"\n" +
|
||||
"const codepoints = {\n";
|
||||
|
||||
function toHex(num) {
|
||||
let s = num.toString(16);
|
||||
if (s.length < 4) {
|
||||
s = ("0000" + s).slice(-4);
|
||||
}
|
||||
return "0x" + s;
|
||||
}
|
||||
|
||||
for (let codepoint in codepoints) {
|
||||
codepoint = parseInt(codepoint);
|
||||
|
||||
// Latin-1?
|
||||
if ((codepoint >= 0x20) && (codepoint <= 0xff)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handled by the general Unicode mapping?
|
||||
if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out += " " + toHex(codepoint) + ": " +
|
||||
toHex(codepoints[codepoint].keysym) +
|
||||
", // XK_" + codepoints[codepoint].name + "\n";
|
||||
}
|
||||
|
||||
out +=
|
||||
"};\n" +
|
||||
"\n" +
|
||||
"export default {\n" +
|
||||
" lookup(u) {\n" +
|
||||
" // Latin-1 is one-to-one mapping\n" +
|
||||
" if ((u >= 0x20) && (u <= 0xff)) {\n" +
|
||||
" return u;\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" // Lookup table (fairly random)\n" +
|
||||
" const keysym = codepoints[u];\n" +
|
||||
" if (keysym !== undefined) {\n" +
|
||||
" return keysym;\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" // General mapping as final fallback\n" +
|
||||
" return 0x01000000 | u;\n" +
|
||||
" },\n" +
|
||||
"};";
|
||||
|
||||
console.log(out);
|
||||
40
kasmweb/utils/img2js.py
Executable file
40
kasmweb/utils/img2js.py
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Convert image to Javascript compatible base64 Data URI
|
||||
# Copyright (C) 2018 The noVNC Authors
|
||||
# Licensed under MPL 2.0 (see docs/LICENSE.MPL-2.0)
|
||||
#
|
||||
|
||||
import sys, base64
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
except:
|
||||
print "python PIL module required (python-imaging package)"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print "Usage: %s IMAGE JS_VARIABLE" % sys.argv[0]
|
||||
sys.exit(1)
|
||||
|
||||
fname = sys.argv[1]
|
||||
var = sys.argv[2]
|
||||
|
||||
ext = fname.lower().split('.')[-1]
|
||||
if ext == "png": mime = "image/png"
|
||||
elif ext in ["jpg", "jpeg"]: mime = "image/jpeg"
|
||||
elif ext == "gif": mime = "image/gif"
|
||||
else:
|
||||
print "Only PNG, JPEG and GIF images are supported"
|
||||
sys.exit(1)
|
||||
uri = "data:%s;base64," % mime
|
||||
|
||||
im = Image.open(fname)
|
||||
w, h = im.size
|
||||
|
||||
raw = open(fname).read()
|
||||
|
||||
print '%s = {"width": %s, "height": %s, "data": "%s%s"};' % (
|
||||
var, w, h, uri, base64.b64encode(raw))
|
||||
206
kasmweb/utils/json2graph.py
Executable file
206
kasmweb/utils/json2graph.py
Executable file
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Use matplotlib to generate performance charts
|
||||
Copyright (C) 2018 The noVNC Authors
|
||||
Licensed under MPL-2.0 (see docs/LICENSE.MPL-2.0)
|
||||
'''
|
||||
|
||||
# a bar plot with errorbars
|
||||
import sys, json
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.font_manager import FontProperties
|
||||
|
||||
def usage():
|
||||
print "%s json_file level1 level2 level3 [legend_height]\n\n" % sys.argv[0]
|
||||
print "Description:\n"
|
||||
print "level1, level2, and level3 are one each of the following:\n";
|
||||
print " select=ITEM - select only ITEM at this level";
|
||||
print " bar - each item on this level becomes a graph bar";
|
||||
print " group - items on this level become groups of bars";
|
||||
print "\n";
|
||||
print "json_file is a file containing json data in the following format:\n"
|
||||
print ' {';
|
||||
print ' "conf": {';
|
||||
print ' "order_l1": [';
|
||||
print ' "level1_label1",';
|
||||
print ' "level1_label2",';
|
||||
print ' ...';
|
||||
print ' ],';
|
||||
print ' "order_l2": [';
|
||||
print ' "level2_label1",';
|
||||
print ' "level2_label2",';
|
||||
print ' ...';
|
||||
print ' ],';
|
||||
print ' "order_l3": [';
|
||||
print ' "level3_label1",';
|
||||
print ' "level3_label2",';
|
||||
print ' ...';
|
||||
print ' ]';
|
||||
print ' },';
|
||||
print ' "stats": {';
|
||||
print ' "level1_label1": {';
|
||||
print ' "level2_label1": {';
|
||||
print ' "level3_label1": [val1, val2, val3],';
|
||||
print ' "level3_label2": [val1, val2, val3],';
|
||||
print ' ...';
|
||||
print ' },';
|
||||
print ' "level2_label2": {';
|
||||
print ' ...';
|
||||
print ' },';
|
||||
print ' },';
|
||||
print ' "level1_label2": {';
|
||||
print ' ...';
|
||||
print ' },';
|
||||
print ' ...';
|
||||
print ' },';
|
||||
print ' }';
|
||||
sys.exit(2)
|
||||
|
||||
def error(msg):
|
||||
print msg
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
#colors = ['#ff0000', '#0863e9', '#00f200', '#ffa100',
|
||||
# '#800000', '#805100', '#013075', '#007900']
|
||||
colors = ['#ff0000', '#00ff00', '#0000ff',
|
||||
'#dddd00', '#dd00dd', '#00dddd',
|
||||
'#dd6622', '#dd2266', '#66dd22',
|
||||
'#8844dd', '#44dd88', '#4488dd']
|
||||
|
||||
if len(sys.argv) < 5:
|
||||
usage()
|
||||
|
||||
filename = sys.argv[1]
|
||||
L1 = sys.argv[2]
|
||||
L2 = sys.argv[3]
|
||||
L3 = sys.argv[4]
|
||||
if len(sys.argv) > 5:
|
||||
legendHeight = float(sys.argv[5])
|
||||
else:
|
||||
legendHeight = 0.75
|
||||
|
||||
# Load the JSON data from the file
|
||||
data = json.loads(file(filename).read())
|
||||
conf = data['conf']
|
||||
stats = data['stats']
|
||||
|
||||
# Sanity check data hierarchy
|
||||
if len(conf['order_l1']) != len(stats.keys()):
|
||||
error("conf.order_l1 does not match stats level 1")
|
||||
for l1 in stats.keys():
|
||||
if len(conf['order_l2']) != len(stats[l1].keys()):
|
||||
error("conf.order_l2 does not match stats level 2 for %s" % l1)
|
||||
if conf['order_l1'].count(l1) < 1:
|
||||
error("%s not found in conf.order_l1" % l1)
|
||||
for l2 in stats[l1].keys():
|
||||
if len(conf['order_l3']) != len(stats[l1][l2].keys()):
|
||||
error("conf.order_l3 does not match stats level 3")
|
||||
if conf['order_l2'].count(l2) < 1:
|
||||
error("%s not found in conf.order_l2" % l2)
|
||||
for l3 in stats[l1][l2].keys():
|
||||
if conf['order_l3'].count(l3) < 1:
|
||||
error("%s not found in conf.order_l3" % l3)
|
||||
|
||||
#
|
||||
# Generate the data based on the level specifications
|
||||
#
|
||||
bar_labels = None
|
||||
group_labels = None
|
||||
bar_vals = []
|
||||
bar_sdvs = []
|
||||
if L3.startswith("select="):
|
||||
select_label = l3 = L3.split("=")[1]
|
||||
bar_labels = conf['order_l1']
|
||||
group_labels = conf['order_l2']
|
||||
bar_vals = [[0]*len(group_labels) for i in bar_labels]
|
||||
bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
|
||||
for b in range(len(bar_labels)):
|
||||
l1 = bar_labels[b]
|
||||
for g in range(len(group_labels)):
|
||||
l2 = group_labels[g]
|
||||
bar_vals[b][g] = np.mean(stats[l1][l2][l3])
|
||||
bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
|
||||
elif L2.startswith("select="):
|
||||
select_label = l2 = L2.split("=")[1]
|
||||
bar_labels = conf['order_l1']
|
||||
group_labels = conf['order_l3']
|
||||
bar_vals = [[0]*len(group_labels) for i in bar_labels]
|
||||
bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
|
||||
for b in range(len(bar_labels)):
|
||||
l1 = bar_labels[b]
|
||||
for g in range(len(group_labels)):
|
||||
l3 = group_labels[g]
|
||||
bar_vals[b][g] = np.mean(stats[l1][l2][l3])
|
||||
bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
|
||||
elif L1.startswith("select="):
|
||||
select_label = l1 = L1.split("=")[1]
|
||||
bar_labels = conf['order_l2']
|
||||
group_labels = conf['order_l3']
|
||||
bar_vals = [[0]*len(group_labels) for i in bar_labels]
|
||||
bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
|
||||
for b in range(len(bar_labels)):
|
||||
l2 = bar_labels[b]
|
||||
for g in range(len(group_labels)):
|
||||
l3 = group_labels[g]
|
||||
bar_vals[b][g] = np.mean(stats[l1][l2][l3])
|
||||
bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
|
||||
else:
|
||||
usage()
|
||||
|
||||
# If group is before bar then flip (zip) the data
|
||||
if [L1, L2, L3].index("group") < [L1, L2, L3].index("bar"):
|
||||
bar_labels, group_labels = group_labels, bar_labels
|
||||
bar_vals = zip(*bar_vals)
|
||||
bar_sdvs = zip(*bar_sdvs)
|
||||
|
||||
print "bar_vals:", bar_vals
|
||||
|
||||
#
|
||||
# Now render the bar graph
|
||||
#
|
||||
ind = np.arange(len(group_labels)) # the x locations for the groups
|
||||
width = 0.8 * (1.0/len(bar_labels)) # the width of the bars
|
||||
|
||||
fig = plt.figure(figsize=(10,6), dpi=80)
|
||||
plot = fig.add_subplot(1, 1, 1)
|
||||
|
||||
rects = []
|
||||
for i in range(len(bar_vals)):
|
||||
rects.append(plot.bar(ind+width*i, bar_vals[i], width, color=colors[i],
|
||||
yerr=bar_sdvs[i], align='center'))
|
||||
|
||||
# add some
|
||||
plot.set_ylabel('Milliseconds (less is better)')
|
||||
plot.set_title("Javascript array test: %s" % select_label)
|
||||
plot.set_xticks(ind+width)
|
||||
plot.set_xticklabels( group_labels )
|
||||
|
||||
fontP = FontProperties()
|
||||
fontP.set_size('small')
|
||||
plot.legend( [r[0] for r in rects], bar_labels, prop=fontP,
|
||||
loc = 'center right', bbox_to_anchor = (1.0, legendHeight))
|
||||
|
||||
def autolabel(rects):
|
||||
# attach some text labels
|
||||
for rect in rects:
|
||||
height = rect.get_height()
|
||||
if np.isnan(height):
|
||||
height = 0.0
|
||||
plot.text(rect.get_x()+rect.get_width()/2., height+20, '%d'%int(height),
|
||||
ha='center', va='bottom', size='7')
|
||||
|
||||
for rect in rects:
|
||||
autolabel(rect)
|
||||
|
||||
# Adjust axis sizes
|
||||
axis = list(plot.axis())
|
||||
axis[0] = -width # Make sure left side has enough for bar
|
||||
#axis[1] = axis[1] * 1.20 # Add 20% to the right to make sure it fits
|
||||
axis[2] = 0 # Make y-axis start at 0
|
||||
axis[3] = axis[3] * 1.10 # Add 10% to the top
|
||||
plot.axis(axis)
|
||||
|
||||
plt.show()
|
||||
169
kasmweb/utils/launch.sh
Executable file
169
kasmweb/utils/launch.sh
Executable file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (C) 2018 The noVNC Authors
|
||||
# Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
|
||||
usage() {
|
||||
if [ "$*" ]; then
|
||||
echo "$*"
|
||||
echo
|
||||
fi
|
||||
echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]"
|
||||
echo
|
||||
echo "Starts the WebSockets proxy and a mini-webserver and "
|
||||
echo "provides a cut-and-paste URL to go to."
|
||||
echo
|
||||
echo " --listen PORT Port for proxy/webserver to listen on"
|
||||
echo " Default: 6080"
|
||||
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
|
||||
echo " Default: localhost:5900"
|
||||
echo " --cert CERT Path to combined cert/key file"
|
||||
echo " Default: self.pem"
|
||||
echo " --web WEB Path to web files (e.g. vnc.html)"
|
||||
echo " Default: ./"
|
||||
echo " --ssl-only Disable non-https connections."
|
||||
echo " "
|
||||
echo " --record FILE Record traffic to FILE.session.js"
|
||||
echo " "
|
||||
exit 2
|
||||
}
|
||||
|
||||
NAME="$(basename $0)"
|
||||
REAL_NAME="$(readlink -f $0)"
|
||||
HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)"
|
||||
PORT="6080"
|
||||
VNC_DEST="localhost:5900"
|
||||
CERT=""
|
||||
WEB=""
|
||||
proxy_pid=""
|
||||
SSLONLY=""
|
||||
RECORD_ARG=""
|
||||
|
||||
die() {
|
||||
echo "$*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
trap - TERM QUIT INT EXIT
|
||||
trap "true" CHLD # Ignore cleanup messages
|
||||
echo
|
||||
if [ -n "${proxy_pid}" ]; then
|
||||
echo "Terminating WebSockets proxy (${proxy_pid})"
|
||||
kill ${proxy_pid}
|
||||
fi
|
||||
}
|
||||
|
||||
# Process Arguments
|
||||
|
||||
# Arguments that only apply to chrooter itself
|
||||
while [ "$*" ]; do
|
||||
param=$1; shift; OPTARG=$1
|
||||
case $param in
|
||||
--listen) PORT="${OPTARG}"; shift ;;
|
||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||
--cert) CERT="${OPTARG}"; shift ;;
|
||||
--web) WEB="${OPTARG}"; shift ;;
|
||||
--ssl-only) SSLONLY="--ssl-only" ;;
|
||||
--record) RECORD_ARG="--record ${OPTARG}"; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) usage "Unknown chrooter option: ${param}" ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Sanity checks
|
||||
if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then
|
||||
exec 7<&-
|
||||
exec 7>&-
|
||||
die "Port ${PORT} in use. Try --listen PORT"
|
||||
else
|
||||
exec 7<&-
|
||||
exec 7>&-
|
||||
fi
|
||||
|
||||
trap "cleanup" TERM QUIT INT EXIT
|
||||
|
||||
# Find vnc.html
|
||||
if [ -n "${WEB}" ]; then
|
||||
if [ ! -e "${WEB}/vnc.html" ]; then
|
||||
die "Could not find ${WEB}/vnc.html"
|
||||
fi
|
||||
elif [ -e "$(pwd)/vnc.html" ]; then
|
||||
WEB=$(pwd)
|
||||
elif [ -e "${HERE}/../vnc.html" ]; then
|
||||
WEB=${HERE}/../
|
||||
elif [ -e "${HERE}/vnc.html" ]; then
|
||||
WEB=${HERE}
|
||||
elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
|
||||
WEB=${HERE}/../share/novnc/
|
||||
else
|
||||
die "Could not find vnc.html"
|
||||
fi
|
||||
|
||||
# Find self.pem
|
||||
if [ -n "${CERT}" ]; then
|
||||
if [ ! -e "${CERT}" ]; then
|
||||
die "Could not find ${CERT}"
|
||||
fi
|
||||
elif [ -e "$(pwd)/self.pem" ]; then
|
||||
CERT="$(pwd)/self.pem"
|
||||
elif [ -e "${HERE}/../self.pem" ]; then
|
||||
CERT="${HERE}/../self.pem"
|
||||
elif [ -e "${HERE}/self.pem" ]; then
|
||||
CERT="${HERE}/self.pem"
|
||||
else
|
||||
echo "Warning: could not find self.pem"
|
||||
fi
|
||||
|
||||
# try to find websockify (prefer local, try global, then download local)
|
||||
if [[ -e ${HERE}/websockify ]]; then
|
||||
WEBSOCKIFY=${HERE}/websockify/run
|
||||
|
||||
if [[ ! -x $WEBSOCKIFY ]]; then
|
||||
echo "The path ${HERE}/websockify exists, but $WEBSOCKIFY either does not exist or is not executable."
|
||||
echo "If you intended to use an installed websockify package, please remove ${HERE}/websockify."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using local websockify at $WEBSOCKIFY"
|
||||
else
|
||||
WEBSOCKIFY=$(which websockify 2>/dev/null)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "No installed websockify, attempting to clone websockify..."
|
||||
WEBSOCKIFY=${HERE}/websockify/run
|
||||
git clone https://github.com/novnc/websockify ${HERE}/websockify
|
||||
|
||||
if [[ ! -e $WEBSOCKIFY ]]; then
|
||||
echo "Unable to locate ${HERE}/websockify/run after downloading"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using local websockify at $WEBSOCKIFY"
|
||||
else
|
||||
echo "Using installed websockify at $WEBSOCKIFY"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
||||
#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
${WEBSOCKIFY} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} ${RECORD_ARG} &
|
||||
proxy_pid="$!"
|
||||
sleep 1
|
||||
if ! ps -p ${proxy_pid} >/dev/null; then
|
||||
proxy_pid=
|
||||
echo "Failed to start WebSockets proxy"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "\n\nNavigate to this URL:\n"
|
||||
if [ "x$SSLONLY" == "x" ]; then
|
||||
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
||||
else
|
||||
echo -e " https://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
||||
fi
|
||||
|
||||
echo -e "Press Ctrl-C to exit\n\n"
|
||||
|
||||
wait ${proxy_pid}
|
||||
28
kasmweb/utils/u2x11
Executable file
28
kasmweb/utils/u2x11
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Convert "U+..." commented entries in /usr/include/X11/keysymdef.h
|
||||
# into JavaScript for use by noVNC. Note this is likely to produce
|
||||
# a few duplicate properties with clashing values, that will need
|
||||
# resolving manually.
|
||||
#
|
||||
# Colin Dean <colin@xvpsource.org>
|
||||
#
|
||||
|
||||
regex="^#define[ \t]+XK_[A-Za-z0-9_]+[ \t]+0x([0-9a-fA-F]+)[ \t]+\/\*[ \t]+U\+([0-9a-fA-F]+)[ \t]+[^*]+.[ \t]+\*\/[ \t]*$"
|
||||
echo "unicodeTable = {"
|
||||
while read line; do
|
||||
if echo "${line}" | egrep -qs "${regex}"; then
|
||||
|
||||
x11=$(echo "${line}" | sed -r "s/${regex}/\1/")
|
||||
vnc=$(echo "${line}" | sed -r "s/${regex}/\2/")
|
||||
|
||||
if echo "${vnc}" | egrep -qs "^00[2-9A-F][0-9A-F]$"; then
|
||||
: # skip ISO Latin-1 (U+0020 to U+00FF) as 1-to-1 mapping
|
||||
else
|
||||
# note 1-to-1 is possible (e.g. for Euro symbol, U+20AC)
|
||||
echo " 0x${vnc} : 0x${x11},"
|
||||
fi
|
||||
fi
|
||||
done < /usr/include/X11/keysymdef.h | uniq
|
||||
echo "};"
|
||||
|
||||
313
kasmweb/utils/use_require.js
Executable file
313
kasmweb/utils/use_require.js
Executable file
@@ -0,0 +1,313 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const path = require('path');
|
||||
const program = require('commander');
|
||||
const fs = require('fs');
|
||||
const fse = require('fs-extra');
|
||||
const babel = require('babel-core');
|
||||
|
||||
const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']);
|
||||
|
||||
program
|
||||
.option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`)
|
||||
.option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')
|
||||
.option('--with-app', 'process app files as well as core files')
|
||||
.option('--only-legacy', 'only output legacy files (no ES6 modules) for the app')
|
||||
.option('--clean', 'clear the lib folder before building')
|
||||
.parse(process.argv);
|
||||
|
||||
// the various important paths
|
||||
const paths = {
|
||||
main: path.resolve(__dirname, '..'),
|
||||
core: path.resolve(__dirname, '..', 'core'),
|
||||
app: path.resolve(__dirname, '..', 'app'),
|
||||
vendor: path.resolve(__dirname, '..', 'vendor'),
|
||||
out_dir_base: path.resolve(__dirname, '..', 'build'),
|
||||
lib_dir_base: path.resolve(__dirname, '..', 'lib'),
|
||||
};
|
||||
|
||||
const no_copy_files = new Set([
|
||||
// skip these -- they don't belong in the processed application
|
||||
path.join(paths.vendor, 'sinon.js'),
|
||||
path.join(paths.vendor, 'browser-es-module-loader'),
|
||||
path.join(paths.vendor, 'promise.js'),
|
||||
path.join(paths.app, 'images', 'icons', 'Makefile'),
|
||||
]);
|
||||
|
||||
const no_transform_files = new Set([
|
||||
// don't transform this -- we want it imported as-is to properly catch loading errors
|
||||
path.join(paths.app, 'error-handler.js'),
|
||||
]);
|
||||
|
||||
no_copy_files.forEach(file => no_transform_files.add(file));
|
||||
|
||||
// util.promisify requires Node.js 8.x, so we have our own
|
||||
function promisify(original) {
|
||||
return function promise_wrap() {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
return new Promise((resolve, reject) => {
|
||||
original.apply(this, args.concat((err, value) => {
|
||||
if (err) return reject(err);
|
||||
resolve(value);
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const readFile = promisify(fs.readFile);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
const readdir = promisify(fs.readdir);
|
||||
const lstat = promisify(fs.lstat);
|
||||
|
||||
const copy = promisify(fse.copy);
|
||||
const unlink = promisify(fse.unlink);
|
||||
const ensureDir = promisify(fse.ensureDir);
|
||||
const rmdir = promisify(fse.rmdir);
|
||||
|
||||
const babelTransformFile = promisify(babel.transformFile);
|
||||
|
||||
// walkDir *recursively* walks directories trees,
|
||||
// calling the callback for all normal files found.
|
||||
function walkDir(base_path, cb, filter) {
|
||||
return readdir(base_path)
|
||||
.then((files) => {
|
||||
const paths = files.map(filename => path.join(base_path, filename));
|
||||
return Promise.all(paths.map(filepath => lstat(filepath)
|
||||
.then((stats) => {
|
||||
if (filter !== undefined && !filter(filepath, stats)) return;
|
||||
|
||||
if (stats.isSymbolicLink()) return;
|
||||
if (stats.isFile()) return cb(filepath);
|
||||
if (stats.isDirectory()) return walkDir(filepath, cb, filter);
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
function transform_html(legacy_scripts, only_legacy) {
|
||||
// write out the modified vnc.html file that works with the bundle
|
||||
const src_html_path = path.resolve(__dirname, '..', 'vnc.html');
|
||||
const out_html_path = path.resolve(paths.out_dir_base, 'vnc.html');
|
||||
return readFile(src_html_path)
|
||||
.then((contents_raw) => {
|
||||
let contents = contents_raw.toString();
|
||||
|
||||
const start_marker = '<!-- begin scripts -->\n';
|
||||
const end_marker = '<!-- end scripts -->';
|
||||
const start_ind = contents.indexOf(start_marker) + start_marker.length;
|
||||
const end_ind = contents.indexOf(end_marker, start_ind);
|
||||
|
||||
let new_script = '';
|
||||
|
||||
if (only_legacy) {
|
||||
// Only legacy version, so include things directly
|
||||
for (let i = 0;i < legacy_scripts.length;i++) {
|
||||
new_script += ` <script src="${legacy_scripts[i]}"></script>\n`;
|
||||
}
|
||||
} else {
|
||||
// Otherwise detect if it's a modern browser and select
|
||||
// variant accordingly
|
||||
new_script += `\
|
||||
<script type="module">\n\
|
||||
window._noVNC_has_module_support = true;\n\
|
||||
</script>\n\
|
||||
<script>\n\
|
||||
window.addEventListener("load", function() {\n\
|
||||
if (window._noVNC_has_module_support) return;\n\
|
||||
let legacy_scripts = ${JSON.stringify(legacy_scripts)};\n\
|
||||
for (let i = 0;i < legacy_scripts.length;i++) {\n\
|
||||
let script = document.createElement("script");\n\
|
||||
script.src = legacy_scripts[i];\n\
|
||||
script.async = false;\n\
|
||||
document.head.appendChild(script);\n\
|
||||
}\n\
|
||||
});\n\
|
||||
</script>\n`;
|
||||
|
||||
// Original, ES6 modules
|
||||
new_script += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
|
||||
}
|
||||
|
||||
contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind);
|
||||
|
||||
return contents;
|
||||
})
|
||||
.then((contents) => {
|
||||
console.log(`Writing ${out_html_path}`);
|
||||
return writeFile(out_html_path, contents);
|
||||
});
|
||||
}
|
||||
|
||||
function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) {
|
||||
if (!import_format) {
|
||||
throw new Error("you must specify an import format to generate compiled noVNC libraries");
|
||||
} else if (!SUPPORTED_FORMATS.has(import_format)) {
|
||||
throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`);
|
||||
}
|
||||
|
||||
// NB: we need to make a copy of babel_opts, since babel sets some defaults on it
|
||||
const babel_opts = () => ({
|
||||
plugins: [`transform-es2015-modules-${import_format}`],
|
||||
presets: ['es2015'],
|
||||
ast: false,
|
||||
sourceMaps: source_maps,
|
||||
});
|
||||
|
||||
// No point in duplicate files without the app, so force only converted files
|
||||
if (!with_app_dir) {
|
||||
only_legacy = true;
|
||||
}
|
||||
|
||||
let in_path;
|
||||
let out_path_base;
|
||||
if (with_app_dir) {
|
||||
out_path_base = paths.out_dir_base;
|
||||
in_path = paths.main;
|
||||
} else {
|
||||
out_path_base = paths.lib_dir_base;
|
||||
}
|
||||
const legacy_path_base = only_legacy ? out_path_base : path.join(out_path_base, 'legacy');
|
||||
|
||||
fse.ensureDirSync(out_path_base);
|
||||
|
||||
const helpers = require('./use_require_helpers');
|
||||
const helper = helpers[import_format];
|
||||
|
||||
const outFiles = [];
|
||||
|
||||
const handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve()
|
||||
.then(() => {
|
||||
if (no_copy_files.has(filename)) return;
|
||||
|
||||
const out_path = path.join(out_path_base, path.relative(in_path_base, filename));
|
||||
const legacy_path = path.join(legacy_path_base, path.relative(in_path_base, filename));
|
||||
|
||||
if (path.extname(filename) !== '.js') {
|
||||
if (!js_only) {
|
||||
console.log(`Writing ${out_path}`);
|
||||
return copy(filename, out_path);
|
||||
}
|
||||
return; // skip non-javascript files
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (only_legacy && !no_transform_files.has(filename)) {
|
||||
return;
|
||||
}
|
||||
return ensureDir(path.dirname(out_path))
|
||||
.then(() => {
|
||||
console.log(`Writing ${out_path}`);
|
||||
return copy(filename, out_path);
|
||||
});
|
||||
})
|
||||
.then(() => ensureDir(path.dirname(legacy_path)))
|
||||
.then(() => {
|
||||
if (no_transform_files.has(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opts = babel_opts();
|
||||
if (helper && helpers.optionsOverride) {
|
||||
helper.optionsOverride(opts);
|
||||
}
|
||||
// Adjust for the fact that we move the core files relative
|
||||
// to the vendor directory
|
||||
if (vendor_rewrite) {
|
||||
opts.plugins.push(["import-redirect",
|
||||
{"root": legacy_path_base,
|
||||
"redirect": { "vendor/(.+)": "./vendor/$1"}}]);
|
||||
}
|
||||
|
||||
return babelTransformFile(filename, opts)
|
||||
.then((res) => {
|
||||
console.log(`Writing ${legacy_path}`);
|
||||
const {map} = res;
|
||||
let {code} = res;
|
||||
if (source_maps === true) {
|
||||
// append URL for external source map
|
||||
code += `\n//# sourceMappingURL=${path.basename(legacy_path)}.map\n`;
|
||||
}
|
||||
outFiles.push(`${legacy_path}`);
|
||||
return writeFile(legacy_path, code)
|
||||
.then(() => {
|
||||
if (source_maps === true || source_maps === 'both') {
|
||||
console.log(` and ${legacy_path}.map`);
|
||||
outFiles.push(`${legacy_path}.map`);
|
||||
return writeFile(`${legacy_path}.map`, JSON.stringify(map));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (with_app_dir && helper && helper.noCopyOverride) {
|
||||
helper.noCopyOverride(paths, no_copy_files);
|
||||
}
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
const handler = handleDir.bind(null, true, false, in_path || paths.main);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
return walkDir(paths.vendor, handler, filter);
|
||||
})
|
||||
.then(() => {
|
||||
const handler = handleDir.bind(null, true, !in_path, in_path || paths.core);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
return walkDir(paths.core, handler, filter);
|
||||
})
|
||||
.then(() => {
|
||||
if (!with_app_dir) return;
|
||||
const handler = handleDir.bind(null, false, false, in_path);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
return walkDir(paths.app, handler, filter);
|
||||
})
|
||||
.then(() => {
|
||||
if (!with_app_dir) return;
|
||||
|
||||
if (!helper || !helper.appWriter) {
|
||||
throw new Error(`Unable to generate app for the ${import_format} format!`);
|
||||
}
|
||||
|
||||
const out_app_path = path.join(legacy_path_base, 'app.js');
|
||||
console.log(`Writing ${out_app_path}`);
|
||||
return helper.appWriter(out_path_base, legacy_path_base, out_app_path)
|
||||
.then((extra_scripts) => {
|
||||
const rel_app_path = path.relative(out_path_base, out_app_path);
|
||||
const legacy_scripts = extra_scripts.concat([rel_app_path]);
|
||||
transform_html(legacy_scripts, only_legacy);
|
||||
})
|
||||
.then(() => {
|
||||
if (!helper.removeModules) return;
|
||||
console.log(`Cleaning up temporary files...`);
|
||||
return Promise.all(outFiles.map((filepath) => {
|
||||
unlink(filepath)
|
||||
.then(() => {
|
||||
// Try to clean up any empty directories if this
|
||||
// was the last file in there
|
||||
const rmdir_r = dir =>
|
||||
rmdir(dir)
|
||||
.then(() => rmdir_r(path.dirname(dir)))
|
||||
.catch(() => {
|
||||
// Assume the error was ENOTEMPTY and ignore it
|
||||
});
|
||||
return rmdir_r(path.dirname(filepath));
|
||||
});
|
||||
}));
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`Failure converting modules: ${err}`);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
if (program.clean) {
|
||||
console.log(`Removing ${paths.lib_dir_base}`);
|
||||
fse.removeSync(paths.lib_dir_base);
|
||||
|
||||
console.log(`Removing ${paths.out_dir_base}`);
|
||||
fse.removeSync(paths.out_dir_base);
|
||||
}
|
||||
|
||||
make_lib_files(program.as, program.withSourceMaps, program.withApp, program.onlyLegacy);
|
||||
76
kasmweb/utils/use_require_helpers.js
Normal file
76
kasmweb/utils/use_require_helpers.js
Normal file
@@ -0,0 +1,76 @@
|
||||
// writes helpers require for vnc.html (they should output app.js)
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// util.promisify requires Node.js 8.x, so we have our own
|
||||
function promisify(original) {
|
||||
return function promise_wrap() {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
return new Promise((resolve, reject) => {
|
||||
original.apply(this, args.concat((err, value) => {
|
||||
if (err) return reject(err);
|
||||
resolve(value);
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
module.exports = {
|
||||
'amd': {
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
// setup for requirejs
|
||||
const ui_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'app', 'ui'));
|
||||
return writeFile(out_path, `requirejs(["${ui_path}"], (ui) => {});`)
|
||||
.then(() => {
|
||||
console.log(`Please place RequireJS in ${path.join(script_base_path, 'require.js')}`);
|
||||
const require_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'require.js'));
|
||||
return [ require_path ];
|
||||
});
|
||||
},
|
||||
noCopyOverride: () => {},
|
||||
},
|
||||
'commonjs': {
|
||||
optionsOverride: (opts) => {
|
||||
// CommonJS supports properly shifting the default export to work as normal
|
||||
opts.plugins.unshift("add-module-exports");
|
||||
},
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
const browserify = require('browserify');
|
||||
const b = browserify(path.join(script_base_path, 'app/ui.js'), {});
|
||||
return promisify(b.bundle).call(b)
|
||||
.then(buf => writeFile(out_path, buf))
|
||||
.then(() => []);
|
||||
},
|
||||
noCopyOverride: () => {},
|
||||
removeModules: true,
|
||||
},
|
||||
'systemjs': {
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
const ui_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'app', 'ui.js'));
|
||||
return writeFile(out_path, `SystemJS.import("${ui_path}");`)
|
||||
.then(() => {
|
||||
console.log(`Please place SystemJS in ${path.join(script_base_path, 'system-production.js')}`);
|
||||
// FIXME: Should probably be in the legacy directory
|
||||
const promise_path = path.relative(base_out_path,
|
||||
path.join(base_out_path, 'vendor', 'promise.js'));
|
||||
const systemjs_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'system-production.js'));
|
||||
return [ promise_path, systemjs_path ];
|
||||
});
|
||||
},
|
||||
noCopyOverride: (paths, no_copy_files) => {
|
||||
no_copy_files.delete(path.join(paths.vendor, 'promise.js'));
|
||||
},
|
||||
},
|
||||
'umd': {
|
||||
optionsOverride: (opts) => {
|
||||
// umd supports properly shifting the default export to work as normal
|
||||
opts.plugins.unshift("add-module-exports");
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user