mirror of
https://github.com/retspen/webvirtcloud
synced 2024-12-24 15:15:22 +00:00
update novnc 1.1.0 ->1.2.0
This commit is contained in:
parent
bb935b3713
commit
43f1461e29
53 changed files with 109904 additions and 56529 deletions
|
@ -17,7 +17,11 @@
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|
||||||
<!-- Icons (see Makefile for what the sizes are for) -->
|
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
|
||||||
|
Remove this if you use the .htaccess -->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
|
||||||
|
<!-- Icons (see app/images/icons/Makefile for what the sizes are for) -->
|
||||||
<link rel="icon" sizes="16x16" type="image/png" href="{% static "js/novnc/app/images/icons/novnc-16x16.png" %}">
|
<link rel="icon" sizes="16x16" type="image/png" href="{% static "js/novnc/app/images/icons/novnc-16x16.png" %}">
|
||||||
<link rel="icon" sizes="24x24" type="image/png" href="{% static "js/novnc/app/images/icons/novnc-24x24.png" %}">
|
<link rel="icon" sizes="24x24" type="image/png" href="{% static "js/novnc/app/images/icons/novnc-24x24.png" %}">
|
||||||
<link rel="icon" sizes="32x32" type="image/png" href="{% static "js/novnc/app/images/icons/novnc-32x32.png" %}">
|
<link rel="icon" sizes="32x32" type="image/png" href="{% static "js/novnc/app/images/icons/novnc-32x32.png" %}">
|
||||||
|
@ -54,12 +58,8 @@
|
||||||
<!-- Stylesheets -->
|
<!-- Stylesheets -->
|
||||||
<link rel="stylesheet" href="{% static "js/novnc/app/styles/base.css" %}" />
|
<link rel="stylesheet" href="{% static "js/novnc/app/styles/base.css" %}" />
|
||||||
|
|
||||||
<!--
|
|
||||||
<script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- this is included as a normal file in order to catch script-loading errors as well -->
|
<!-- this is included as a normal file in order to catch script-loading errors as well -->
|
||||||
<script type="text/javascript" src="{% static "js/novnc/app/error-handler.js" %}"></script>
|
<script src="{% static "js/novnc/app/error-handler.js" %}"></script>
|
||||||
|
|
||||||
<!-- begin scripts -->
|
<!-- begin scripts -->
|
||||||
<!-- promise polyfills promises for IE11 -->
|
<!-- promise polyfills promises for IE11 -->
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
|
|
||||||
<div class="noVNC_scroll">
|
<div class="noVNC_scroll">
|
||||||
|
|
||||||
<h1 class="noVNC_logo" translate="no"><span>no</span><br />VNC</h1>
|
<h1 class="noVNC_logo" translate="no"><span>no</span><br>VNC</h1>
|
||||||
|
|
||||||
<!-- Drag/Pan the viewport -->
|
<!-- Drag/Pan the viewport -->
|
||||||
<input type="image" alt="viewport drag" src="{% static "js/novnc/app/images/drag.svg" %}"
|
<input type="image" alt="viewport drag" src="{% static "js/novnc/app/images/drag.svg" %}"
|
||||||
|
@ -99,37 +99,27 @@
|
||||||
|
|
||||||
<!--noVNC Touch Device only buttons-->
|
<!--noVNC Touch Device only buttons-->
|
||||||
<div id="noVNC_mobile_buttons">
|
<div id="noVNC_mobile_buttons">
|
||||||
<input type="image" alt="No mousebutton" src="{% static "js/novnc/app/images/mouse_none.svg" %}"
|
|
||||||
id="noVNC_mouse_button0" class="noVNC_button" title="Active Mouse Button" />
|
|
||||||
<input type="image" alt="Left mousebutton" src="{% static "js/novnc/app/images/mouse_left.svg" %}"
|
|
||||||
id="noVNC_mouse_button1" class="noVNC_button" title="Active Mouse Button" />
|
|
||||||
<input type="image" alt="Middle mousebutton" src="{% static "js/novnc/app/images/mouse_middle.svg" %}"
|
|
||||||
id="noVNC_mouse_button2" class="noVNC_button" title="Active Mouse Button" />
|
|
||||||
<input type="image" alt="Right mousebutton" src="{% static "js/novnc/app/images/mouse_right.svg" %}"
|
|
||||||
id="noVNC_mouse_button4" class="noVNC_button" title="Active Mouse Button" />
|
|
||||||
<input type="image" alt="Keyboard" src="{% static "js/novnc/app/images/keyboard.svg" %}"
|
<input type="image" alt="Keyboard" src="{% static "js/novnc/app/images/keyboard.svg" %}"
|
||||||
id="noVNC_keyboard_button" class="noVNC_button" value="Keyboard" title="Show Keyboard" />
|
id="noVNC_keyboard_button" class="noVNC_button" title="Show Keyboard">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Extra manual keys -->
|
<!-- Extra manual keys -->
|
||||||
<div id="noVNC_extra_keys">
|
<input type="image" alt="Extra keys" src="{% static "js/novnc/app/images/toggleextrakeys.svg" %}"
|
||||||
<input type="image" alt="Extra keys" src="{% static "js/novnc/app/images/toggleextrakeys.svg" %}"
|
id="noVNC_toggle_extra_keys_button" class="noVNC_button" title="Show Extra Keys" />
|
||||||
id="noVNC_toggle_extra_keys_button" class="noVNC_button" title="Show Extra Keys" />
|
<div class="noVNC_vcenter">
|
||||||
<div class="noVNC_vcenter">
|
<div id="noVNC_modifiers" class="noVNC_panel">
|
||||||
<div id="noVNC_modifiers" class="noVNC_panel">
|
<input type="image" alt="Ctrl" src="{% static "js/novnc/app/images/ctrl.svg" %}"
|
||||||
<input type="image" alt="Ctrl" src="{% static "js/novnc/app/images/ctrl.svg" %}"
|
id="noVNC_toggle_ctrl_button" class="noVNC_button" title="Toggle Ctrl" />
|
||||||
id="noVNC_toggle_ctrl_button" class="noVNC_button" title="Toggle Ctrl" />
|
<input type="image" alt="Alt" src="{% static "js/novnc/app/images/alt.svg" %}"
|
||||||
<input type="image" alt="Alt" src="{% static "js/novnc/app/images/alt.svg" %}"
|
id="noVNC_toggle_alt_button" class="noVNC_button" title="Toggle Alt" />
|
||||||
id="noVNC_toggle_alt_button" class="noVNC_button" title="Toggle Alt" />
|
<input type="image" alt="Windows" src="{% static "js/novnc/app/images/windows.svg" %}"
|
||||||
<input type="image" alt="Windows" src="{% static "js/novnc/app/images/windows.svg" %}"
|
id="noVNC_toggle_windows_button" class="noVNC_button" title="Toggle Windows">
|
||||||
id="noVNC_toggle_windows_button" class="noVNC_button" title="Toggle Windows">
|
<input type="image" alt="Tab" src="{% static "js/novnc/app/images/tab.svg" %}"
|
||||||
<input type="image" alt="Tab" src="{% static "js/novnc/app/images/tab.svg" %}"
|
id="noVNC_send_tab_button" class="noVNC_button" title="Send Tab" />
|
||||||
id="noVNC_send_tab_button" class="noVNC_button" title="Send Tab" />
|
<input type="image" alt="Esc" src="{% static "js/novnc/app/images/esc.svg" %}"
|
||||||
<input type="image" alt="Esc" src="{% static "js/novnc/app/images/esc.svg" %}"
|
id="noVNC_send_esc_button" class="noVNC_button" title="Send Escape" />
|
||||||
id="noVNC_send_esc_button" class="noVNC_button" title="Send Escape" />
|
<input type="image" alt="Ctrl+Alt+Del" src="{% static "js/novnc/app/images/ctrlaltdel.svg" %}"
|
||||||
<input type="image" alt="Ctrl+Alt+Del" src="{% static "js/novnc/app/images/ctrlaltdel.svg" %}"
|
id="noVNC_send_ctrl_alt_del_button" class="noVNC_button" title="Send Ctrl-Alt-Del" />
|
||||||
id="noVNC_send_ctrl_alt_del_button" class="noVNC_button" title="Send Ctrl-Alt-Del" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -199,68 +189,66 @@
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="noVNC_expander">Advanced</div>
|
<div class="noVNC_expander">Advanced</div>
|
||||||
<div>
|
<div><ul>
|
||||||
<ul>
|
<li>
|
||||||
<li>
|
<label for="noVNC_setting_quality">Quality:</label>
|
||||||
<label for="noVNC_setting_repeaterID">Repeater ID:</label>
|
<input id="noVNC_setting_quality" type="range" min="0" max="9" value="6">
|
||||||
<input id="noVNC_setting_repeaterID" type="input" value="" />
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<label for="noVNC_setting_compression">Compression level:</label>
|
||||||
<div class="noVNC_expander">WebSocket</div>
|
<input id="noVNC_setting_compression" type="range" min="0" max="9" value="2">
|
||||||
<div>
|
</li>
|
||||||
<ul>
|
<li><hr></li>
|
||||||
<li>
|
<li>
|
||||||
<label><input id="noVNC_setting_encrypt"
|
<label for="noVNC_setting_repeaterID">Repeater ID:</label>
|
||||||
type="checkbox" />Encrypt</label>
|
<input id="noVNC_setting_repeaterID" type="text" value="">
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="noVNC_setting_host">Host:</label>
|
<div class="noVNC_expander">WebSocket</div>
|
||||||
<input id="noVNC_setting_host" value="{{ ws_host }}" />
|
<div><ul>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<label><input id="noVNC_setting_encrypt" type="checkbox"> Encrypt</label>
|
||||||
<label for="noVNC_setting_port">Port:</label>
|
</li>
|
||||||
<input id="noVNC_setting_port" value="{{ ws_port }}"
|
<li>
|
||||||
type="number" />
|
<label for="noVNC_setting_host">Host:</label>
|
||||||
</li>
|
<input id="noVNC_setting_host">
|
||||||
<li>
|
</li>
|
||||||
<label for="noVNC_setting_path">Path:</label>
|
<li>
|
||||||
<!-- <input id="noVNC_setting_path" type="input" value="websockify"/> -->
|
<label for="noVNC_setting_port">Port:</label>
|
||||||
<input id="noVNC_setting_path" type="input" value="{{ ws_path }}" />
|
<input id="noVNC_setting_port" type="number">
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li>
|
||||||
</div>
|
<label for="noVNC_setting_path">Path:</label>
|
||||||
</li>
|
<input id="noVNC_setting_path" type="text" value="websockify">
|
||||||
<li>
|
</li>
|
||||||
<hr>
|
</ul></div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li><hr></li>
|
||||||
<label><input id="noVNC_setting_reconnect" type="checkbox" />Automatic
|
<li>
|
||||||
Reconnect</label>
|
<label><input id="noVNC_setting_reconnect" type="checkbox"> Automatic Reconnect</label>
|
||||||
<input id="noVNC_setting_autoconnect" type="checkbox" value="true" hidden />
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<label for="noVNC_setting_reconnect_delay">Reconnect Delay (ms):</label>
|
||||||
<label for="noVNC_setting_reconnect_delay">Reconnect Delay (ms):</label>
|
<input id="noVNC_setting_reconnect_delay" type="number">
|
||||||
<input id="noVNC_setting_reconnect_delay" type="number" />
|
</li>
|
||||||
</li>
|
<li><hr></li>
|
||||||
<li>
|
<li>
|
||||||
<hr>
|
<label><input id="noVNC_setting_show_dot" type="checkbox"> Show Dot when No Cursor</label>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li><hr></li>
|
||||||
<label><input id="noVNC_setting_show_dot" type="checkbox">Show Dot when No
|
<!-- Logging selection dropdown -->
|
||||||
Cursor</label>
|
<li>
|
||||||
</li>
|
<label>Logging:
|
||||||
<li>
|
<select id="noVNC_setting_logging" name="vncLogging">
|
||||||
<hr>
|
</select>
|
||||||
</li>
|
</label>
|
||||||
<!-- Logging selection dropdown -->
|
</li>
|
||||||
<li>
|
</ul></div>
|
||||||
<label>Logging:
|
</li>
|
||||||
<select id="noVNC_setting_logging" name="vncLogging">
|
<li class="noVNC_version_separator"><hr></li>
|
||||||
</select>
|
<li class="noVNC_version_wrapper">
|
||||||
</label>
|
<span>Version:</span>
|
||||||
</li>
|
<span class="noVNC_version"></span>
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -292,10 +280,14 @@
|
||||||
|
|
||||||
<!-- Password Dialog -->
|
<!-- Password Dialog -->
|
||||||
<div class="noVNC_center noVNC_connect_layer">
|
<div class="noVNC_center noVNC_connect_layer">
|
||||||
<div id="noVNC_password_dlg" class="noVNC_panel">
|
<div id="noVNC_credentials_dlg" class="noVNC_panel">
|
||||||
<form aria-label="noVNC password form">
|
<form aria-label="noVNC password form">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li id="noVNC_username_block">
|
||||||
|
<label>Username:</label>
|
||||||
|
<input id="noVNC_username_input">
|
||||||
|
</li>
|
||||||
|
<li id="noVNC_password_block">
|
||||||
<label>Password:</label>
|
<label>Password:</label>
|
||||||
{% if perms.instances.passwordless_console %}
|
{% if perms.instances.passwordless_console %}
|
||||||
<input id="noVNC_password_input" type="password" value='{{ console_passwd }}' />
|
<input id="noVNC_password_input" type="password" value='{{ console_passwd }}' />
|
||||||
|
@ -304,7 +296,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input id="noVNC_password_button" type="submit" value="Send Password" class="noVNC_submit" />
|
<input id="noVNC_credentials_button" type="submit" value="Send Credentials" class="noVNC_submit" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
|
@ -326,8 +318,8 @@
|
||||||
html attributes which attempt to disable text suggestions on the
|
html attributes which attempt to disable text suggestions on the
|
||||||
on-screen keyboard. Let's hope Chrome implements the ime-mode
|
on-screen keyboard. Let's hope Chrome implements the ime-mode
|
||||||
style for example -->
|
style for example -->
|
||||||
<textarea id="noVNC_keyboardinput" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false"
|
<textarea id="noVNC_keyboardinput" autocapitalize="off" autocomplete="off" spellcheck="false"
|
||||||
mozactionhint="Enter" tabindex="-1"></textarea>
|
tabindex="-1"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<audio id="noVNC_bell">
|
<audio id="noVNC_bell">
|
||||||
|
|
|
@ -205,7 +205,6 @@
|
||||||
rfb.addEventListener("disconnect", disconnectedFromServer);
|
rfb.addEventListener("disconnect", disconnectedFromServer);
|
||||||
rfb.addEventListener("credentialsrequired", credentialsAreRequired);
|
rfb.addEventListener("credentialsrequired", credentialsAreRequired);
|
||||||
rfb.addEventListener("desktopname", updateDesktopName);
|
rfb.addEventListener("desktopname", updateDesktopName);
|
||||||
rfb.addEventListener("capabilities", function () { updatePowerButtons(); });
|
|
||||||
|
|
||||||
// Set parameters that can be changed on an active connection
|
// Set parameters that can be changed on an active connection
|
||||||
rfb.scaleViewport = {{ scale }};
|
rfb.scaleViewport = {{ scale }};
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
/*
|
||||||
|
* noVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
|
*
|
||||||
|
* See README.md for usage and integration instructions.
|
||||||
|
*/
|
||||||
|
|
||||||
// NB: this should *not* be included as a module until we have
|
// NB: this should *not* be included as a module until we have
|
||||||
// native support in the browsers, so that our error handler
|
// native support in the browsers, so that our error handler
|
||||||
// can catch script-loading errors.
|
// can catch script-loading errors.
|
||||||
|
|
|
@ -15,18 +15,18 @@
|
||||||
inkscape:export-xdpi="90"
|
inkscape:export-xdpi="90"
|
||||||
sodipodi:docname="windows.svg"
|
sodipodi:docname="windows.svg"
|
||||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
inkscape:version="0.92.4 (unknown)"
|
||||||
x="0px"
|
x="0px"
|
||||||
y="0px"
|
y="0px"
|
||||||
viewBox="-293 384 25 23"
|
viewBox="-293 384 25 25"
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
width="25"
|
width="25"
|
||||||
height="23"><metadata
|
height="25"><metadata
|
||||||
id="metadata21"><rdf:RDF><cc:Work
|
id="metadata21"><rdf:RDF><cc:Work
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
id="defs19" /><sodipodi:namedview
|
id="defs19" /><sodipodi:namedview
|
||||||
pagecolor="#ffffff"
|
pagecolor="#959595"
|
||||||
bordercolor="#666666"
|
bordercolor="#666666"
|
||||||
borderopacity="1"
|
borderopacity="1"
|
||||||
objecttolerance="10"
|
objecttolerance="10"
|
||||||
|
@ -35,51 +35,31 @@
|
||||||
inkscape:pageopacity="0"
|
inkscape:pageopacity="0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:window-width="1920"
|
inkscape:window-width="1920"
|
||||||
inkscape:window-height="1017"
|
inkscape:window-height="1136"
|
||||||
id="namedview17"
|
id="namedview17"
|
||||||
showgrid="false"
|
showgrid="true"
|
||||||
inkscape:pagecheckerboard="true"
|
inkscape:pagecheckerboard="false"
|
||||||
inkscape:zoom="9.44"
|
inkscape:zoom="32"
|
||||||
inkscape:cx="-0.84745763"
|
inkscape:cx="3.926913"
|
||||||
inkscape:cy="12.5"
|
inkscape:cy="13.255959"
|
||||||
inkscape:window-x="2552"
|
inkscape:window-x="1920"
|
||||||
inkscape:window-y="122"
|
inkscape:window-y="27"
|
||||||
inkscape:window-maximized="1"
|
inkscape:window-maximized="1"
|
||||||
inkscape:current-layer="svg2" />
|
inkscape:current-layer="svg2"><inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid818" /></sodipodi:namedview>
|
||||||
<style
|
<style
|
||||||
type="text/css"
|
type="text/css"
|
||||||
id="style2">
|
id="style2">
|
||||||
.st0{fill:#FFFFFF;}
|
.st0{fill:#FFFFFF;}
|
||||||
</style>
|
</style>
|
||||||
<g
|
|
||||||
id="g14"
|
<path
|
||||||
transform="matrix(1.2624869,0,0,1.3601695,73.614445,-144.84322)">
|
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
|
||||||
<g
|
d="M 21 4 L 11 5.1757812 L 11 12 L 21 12 L 21 4 z M 10 5.2949219 L 4 6 L 4 12 L 10 12 L 10 5.2949219 z "
|
||||||
id="g12">
|
transform="translate(-293,384)"
|
||||||
<path
|
id="path853" /><path
|
||||||
class="st0"
|
id="path858"
|
||||||
d="m -277.4,396 c -0.7,0 -1.3,0 -2,0 -0.4,0 -0.5,-0.1 -0.5,-0.5 0,-1 0,-2 0,-3 0,-0.3 0.2,-0.5 0.5,-0.5 1.3,-0.1 2.6,-0.3 3.9,-0.4 0.4,0 0.7,0.1 0.7,0.6 0,1.1 0,2.2 0,3.3 0,0.4 -0.2,0.6 -0.6,0.6 -0.7,-0.1 -1.4,-0.1 -2,-0.1 z"
|
d="m -272,405 -10,-1.17578 V 397 h 10 z M -283,403.70508 -289,403 v -6 h 6 z"
|
||||||
id="path4"
|
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0" /></svg>
|
||||||
style="fill:#ffffff" />
|
|
||||||
<path
|
|
||||||
class="st0"
|
|
||||||
d="m -274.9,399.3 c 0,0.6 0,1.1 0,1.7 0,0.4 -0.1,0.6 -0.6,0.6 -1.4,-0.1 -2.8,-0.3 -4.1,-0.4 -0.3,0 -0.4,-0.3 -0.4,-0.5 0,-1 0,-2 0,-3 0,-0.4 0.2,-0.5 0.6,-0.5 1.3,0 2.6,0 3.9,0 0.5,0 0.6,0.2 0.6,0.6 0,0.4 0,0.9 0,1.5 z"
|
|
||||||
id="path6"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#ffffff" />
|
|
||||||
<path
|
|
||||||
class="st0"
|
|
||||||
d="m -283.5,396 c -0.6,0 -1.3,0 -1.9,0 -0.4,0 -0.6,-0.1 -0.6,-0.6 0,-0.8 0,-1.5 0,-2.3 0,-0.4 0.2,-0.6 0.6,-0.7 1.3,-0.1 2.7,-0.3 4,-0.4 0.4,0 0.5,0.1 0.5,0.5 0,1 0,1.9 0,2.9 0,0.4 -0.2,0.5 -0.5,0.5 -0.8,0.1 -1.5,0.1 -2.1,0.1 z"
|
|
||||||
id="path8"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#ffffff" />
|
|
||||||
<path
|
|
||||||
class="st0"
|
|
||||||
d="m -283.5,397 c 0.6,0 1.3,0 1.9,0 0.4,0 0.6,0.1 0.6,0.5 0,1 0,1.9 0,2.9 0,0.4 -0.2,0.5 -0.5,0.5 -1.3,-0.1 -2.7,-0.3 -4,-0.4 -0.4,0 -0.6,-0.2 -0.6,-0.7 0,-0.7 0,-1.5 0,-2.2 0,-0.5 0.2,-0.7 0.7,-0.7 0.6,0.1 1.2,0.1 1.9,0.1 z"
|
|
||||||
id="path10"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#ffffff" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.4 KiB |
1
static/js/novnc/app/locale/README
Executable file
1
static/js/novnc/app/locale/README
Executable file
|
@ -0,0 +1 @@
|
||||||
|
DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES.
|
73
static/js/novnc/app/locale/ja.json
Executable file
73
static/js/novnc/app/locale/ja.json
Executable file
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"Connecting...": "接続しています...",
|
||||||
|
"Disconnecting...": "切断しています...",
|
||||||
|
"Reconnecting...": "再接続しています...",
|
||||||
|
"Internal error": "内部エラー",
|
||||||
|
"Must set host": "ホストを設定する必要があります",
|
||||||
|
"Connected (encrypted) to ": "接続しました (暗号化済み): ",
|
||||||
|
"Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
|
||||||
|
"Something went wrong, connection is closed": "何かが問題で、接続が閉じられました",
|
||||||
|
"Failed to connect to server": "サーバーへの接続に失敗しました",
|
||||||
|
"Disconnected": "切断しました",
|
||||||
|
"New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
|
||||||
|
"New connection has been rejected": "新規接続は拒否されました",
|
||||||
|
"Password is required": "パスワードが必要です",
|
||||||
|
"noVNC encountered an error:": "noVNC でエラーが発生しました:",
|
||||||
|
"Hide/Show the control bar": "コントロールバーを隠す/表示する",
|
||||||
|
"Move/Drag Viewport": "ビューポートを移動/ドラッグ",
|
||||||
|
"viewport drag": "ビューポートをドラッグ",
|
||||||
|
"Active Mouse Button": "アクティブなマウスボタン",
|
||||||
|
"No mousebutton": "マウスボタンなし",
|
||||||
|
"Left mousebutton": "左マウスボタン",
|
||||||
|
"Middle mousebutton": "中マウスボタン",
|
||||||
|
"Right mousebutton": "右マウスボタン",
|
||||||
|
"Keyboard": "キーボード",
|
||||||
|
"Show Keyboard": "キーボードを表示",
|
||||||
|
"Extra keys": "追加キー",
|
||||||
|
"Show Extra Keys": "追加キーを表示",
|
||||||
|
"Ctrl": "Ctrl",
|
||||||
|
"Toggle Ctrl": "Ctrl キーを切り替え",
|
||||||
|
"Alt": "Alt",
|
||||||
|
"Toggle Alt": "Alt キーを切り替え",
|
||||||
|
"Toggle Windows": "Windows キーを切り替え",
|
||||||
|
"Windows": "Windows",
|
||||||
|
"Send Tab": "Tab キーを送信",
|
||||||
|
"Tab": "Tab",
|
||||||
|
"Esc": "Esc",
|
||||||
|
"Send Escape": "Escape キーを送信",
|
||||||
|
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
|
||||||
|
"Send Ctrl-Alt-Del": "Ctrl-Alt-Del を送信",
|
||||||
|
"Shutdown/Reboot": "シャットダウン/再起動",
|
||||||
|
"Shutdown/Reboot...": "シャットダウン/再起動...",
|
||||||
|
"Power": "電源",
|
||||||
|
"Shutdown": "シャットダウン",
|
||||||
|
"Reboot": "再起動",
|
||||||
|
"Reset": "リセット",
|
||||||
|
"Clipboard": "クリップボード",
|
||||||
|
"Clear": "クリア",
|
||||||
|
"Fullscreen": "全画面表示",
|
||||||
|
"Settings": "設定",
|
||||||
|
"Shared Mode": "共有モード",
|
||||||
|
"View Only": "表示のみ",
|
||||||
|
"Clip to Window": "ウィンドウにクリップ",
|
||||||
|
"Scaling Mode:": "スケーリングモード:",
|
||||||
|
"None": "なし",
|
||||||
|
"Local Scaling": "ローカルスケーリング",
|
||||||
|
"Remote Resizing": "リモートでリサイズ",
|
||||||
|
"Advanced": "高度",
|
||||||
|
"Repeater ID:": "リピーター ID:",
|
||||||
|
"WebSocket": "WebSocket",
|
||||||
|
"Encrypt": "暗号化",
|
||||||
|
"Host:": "ホスト:",
|
||||||
|
"Port:": "ポート:",
|
||||||
|
"Path:": "パス:",
|
||||||
|
"Automatic Reconnect": "自動再接続",
|
||||||
|
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
|
||||||
|
"Show Dot when No Cursor": "カーソルがないときにドットを表示",
|
||||||
|
"Logging:": "ロギング:",
|
||||||
|
"Disconnect": "切断",
|
||||||
|
"Connect": "接続",
|
||||||
|
"Password:": "パスワード:",
|
||||||
|
"Send Password": "パスワードを送信",
|
||||||
|
"Cancel": "キャンセル"
|
||||||
|
}
|
|
@ -11,16 +11,11 @@
|
||||||
"Disconnected": "Frånkopplad",
|
"Disconnected": "Frånkopplad",
|
||||||
"New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ",
|
"New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ",
|
||||||
"New connection has been rejected": "Ny anslutning har blivit nekad",
|
"New connection has been rejected": "Ny anslutning har blivit nekad",
|
||||||
"Password is required": "Lösenord krävs",
|
"Credentials are required": "Användaruppgifter krävs",
|
||||||
"noVNC encountered an error:": "noVNC stötte på ett problem:",
|
"noVNC encountered an error:": "noVNC stötte på ett problem:",
|
||||||
"Hide/Show the control bar": "Göm/Visa kontrollbaren",
|
"Hide/Show the control bar": "Göm/Visa kontrollbaren",
|
||||||
|
"Drag": "Dra",
|
||||||
"Move/Drag Viewport": "Flytta/Dra Vyn",
|
"Move/Drag Viewport": "Flytta/Dra Vyn",
|
||||||
"viewport drag": "dra vy",
|
|
||||||
"Active Mouse Button": "Aktiv musknapp",
|
|
||||||
"No mousebutton": "Ingen musknapp",
|
|
||||||
"Left mousebutton": "Vänster musknapp",
|
|
||||||
"Middle mousebutton": "Mitten-musknapp",
|
|
||||||
"Right mousebutton": "Höger musknapp",
|
|
||||||
"Keyboard": "Tangentbord",
|
"Keyboard": "Tangentbord",
|
||||||
"Show Keyboard": "Visa Tangentbord",
|
"Show Keyboard": "Visa Tangentbord",
|
||||||
"Extra keys": "Extraknappar",
|
"Extra keys": "Extraknappar",
|
||||||
|
@ -55,6 +50,8 @@
|
||||||
"Local Scaling": "Lokal Skalning",
|
"Local Scaling": "Lokal Skalning",
|
||||||
"Remote Resizing": "Ändra Storlek",
|
"Remote Resizing": "Ändra Storlek",
|
||||||
"Advanced": "Avancerat",
|
"Advanced": "Avancerat",
|
||||||
|
"Quality:": "Kvalitet:",
|
||||||
|
"Compression level:": "Kompressionsnivå:",
|
||||||
"Repeater ID:": "Repeater-ID:",
|
"Repeater ID:": "Repeater-ID:",
|
||||||
"WebSocket": "WebSocket",
|
"WebSocket": "WebSocket",
|
||||||
"Encrypt": "Kryptera",
|
"Encrypt": "Kryptera",
|
||||||
|
@ -65,9 +62,11 @@
|
||||||
"Reconnect Delay (ms):": "Fördröjning (ms):",
|
"Reconnect Delay (ms):": "Fördröjning (ms):",
|
||||||
"Show Dot when No Cursor": "Visa prick när ingen muspekare finns",
|
"Show Dot when No Cursor": "Visa prick när ingen muspekare finns",
|
||||||
"Logging:": "Loggning:",
|
"Logging:": "Loggning:",
|
||||||
|
"Version:": "Version:",
|
||||||
"Disconnect": "Koppla från",
|
"Disconnect": "Koppla från",
|
||||||
"Connect": "Anslut",
|
"Connect": "Anslut",
|
||||||
|
"Username:": "Användarnamn:",
|
||||||
"Password:": "Lösenord:",
|
"Password:": "Lösenord:",
|
||||||
"Send Password": "Skicka lösenord",
|
"Send Credentials": "Skicka Användaruppgifter",
|
||||||
"Cancel": "Avbryt"
|
"Cancel": "Avbryt"
|
||||||
}
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
{
|
{
|
||||||
"Connecting...": "链接中...",
|
"Connecting...": "连接中...",
|
||||||
"Disconnecting...": "正在中断连接...",
|
"Disconnecting...": "正在断开连接...",
|
||||||
"Reconnecting...": "重新链接中...",
|
"Reconnecting...": "重新连接中...",
|
||||||
"Internal error": "内部错误",
|
"Internal error": "内部错误",
|
||||||
"Must set host": "请提供主机名",
|
"Must set host": "请提供主机名",
|
||||||
"Connected (encrypted) to ": "已加密链接到",
|
"Connected (encrypted) to ": "已连接到(加密)",
|
||||||
"Connected (unencrypted) to ": "未加密链接到",
|
"Connected (unencrypted) to ": "已连接到(未加密)",
|
||||||
"Something went wrong, connection is closed": "发生错误,链接已关闭",
|
"Something went wrong, connection is closed": "发生错误,连接已关闭",
|
||||||
"Failed to connect to server": "无法链接到服务器",
|
"Failed to connect to server": "无法连接到服务器",
|
||||||
"Disconnected": "链接已中断",
|
"Disconnected": "已断开连接",
|
||||||
"New connection has been rejected with reason: ": "链接被拒绝,原因:",
|
"New connection has been rejected with reason: ": "连接被拒绝,原因:",
|
||||||
"New connection has been rejected": "链接被拒绝",
|
"New connection has been rejected": "连接被拒绝",
|
||||||
"Password is required": "请提供密码",
|
"Password is required": "请提供密码",
|
||||||
"noVNC encountered an error:": "noVNC 遇到一个错误:",
|
"noVNC encountered an error:": "noVNC 遇到一个错误:",
|
||||||
"Hide/Show the control bar": "显示/隐藏控制列",
|
"Hide/Show the control bar": "显示/隐藏控制栏",
|
||||||
"Move/Drag Viewport": "拖放显示范围",
|
"Move/Drag Viewport": "拖放显示范围",
|
||||||
"viewport drag": "显示范围拖放",
|
"viewport drag": "显示范围拖放",
|
||||||
"Active Mouse Button": "启动鼠标按鍵",
|
"Active Mouse Button": "启动鼠标按鍵",
|
||||||
|
@ -43,10 +43,10 @@
|
||||||
"Reset": "重置",
|
"Reset": "重置",
|
||||||
"Clipboard": "剪贴板",
|
"Clipboard": "剪贴板",
|
||||||
"Clear": "清除",
|
"Clear": "清除",
|
||||||
"Fullscreen": "全屏幕",
|
"Fullscreen": "全屏",
|
||||||
"Settings": "设置",
|
"Settings": "设置",
|
||||||
"Shared Mode": "分享模式",
|
"Shared Mode": "分享模式",
|
||||||
"View Only": "仅检视",
|
"View Only": "仅查看",
|
||||||
"Clip to Window": "限制/裁切窗口大小",
|
"Clip to Window": "限制/裁切窗口大小",
|
||||||
"Scaling Mode:": "缩放模式:",
|
"Scaling Mode:": "缩放模式:",
|
||||||
"None": "无",
|
"None": "无",
|
||||||
|
@ -59,11 +59,11 @@
|
||||||
"Host:": "主机:",
|
"Host:": "主机:",
|
||||||
"Port:": "端口:",
|
"Port:": "端口:",
|
||||||
"Path:": "路径:",
|
"Path:": "路径:",
|
||||||
"Automatic Reconnect": "自动重新链接",
|
"Automatic Reconnect": "自动重新连接",
|
||||||
"Reconnect Delay (ms):": "重新链接间隔 (ms):",
|
"Reconnect Delay (ms):": "重新连接间隔 (ms):",
|
||||||
"Logging:": "日志级别:",
|
"Logging:": "日志级别:",
|
||||||
"Disconnect": "终端链接",
|
"Disconnect": "中断连接",
|
||||||
"Connect": "链接",
|
"Connect": "连接",
|
||||||
"Password:": "密码:",
|
"Password:": "密码:",
|
||||||
"Cancel": "取消"
|
"Cancel": "取消"
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC base CSS
|
* noVNC base CSS
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
|
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
|
||||||
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
|
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
|
||||||
*/
|
*/
|
||||||
|
@ -22,13 +22,12 @@
|
||||||
body {
|
body {
|
||||||
margin:0;
|
margin:0;
|
||||||
padding:0;
|
padding:0;
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
font-family: Helvetica;
|
||||||
/*Background image with light grey curve.*/
|
/*Background image with light grey curve.*/
|
||||||
background-color:#494949;
|
background-color:#494949;
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
background-position:right bottom;
|
background-position:right bottom;
|
||||||
height:100%;
|
height:100%;
|
||||||
display: flex;
|
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,8 +83,20 @@ html {
|
||||||
* ----------------------------------------
|
* ----------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
input[type=input], input[type=password], input[type=number],
|
input:not([type]),
|
||||||
input:not([type]), textarea {
|
input[type=date],
|
||||||
|
input[type=datetime-local],
|
||||||
|
input[type=email],
|
||||||
|
input[type=month],
|
||||||
|
input[type=number],
|
||||||
|
input[type=password],
|
||||||
|
input[type=search],
|
||||||
|
input[type=tel],
|
||||||
|
input[type=text],
|
||||||
|
input[type=time],
|
||||||
|
input[type=url],
|
||||||
|
input[type=week],
|
||||||
|
textarea {
|
||||||
/* Disable default rendering */
|
/* Disable default rendering */
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
|
@ -99,7 +110,11 @@ input:not([type]), textarea {
|
||||||
background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
|
background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=button], input[type=submit], select {
|
input[type=button],
|
||||||
|
input[type=color],
|
||||||
|
input[type=reset],
|
||||||
|
input[type=submit],
|
||||||
|
select {
|
||||||
/* Disable default rendering */
|
/* Disable default rendering */
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
|
@ -117,7 +132,10 @@ input[type=button], input[type=submit], select {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=button], input[type=submit] {
|
input[type=button],
|
||||||
|
input[type=color],
|
||||||
|
input[type=reset],
|
||||||
|
input[type=submit] {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
}
|
}
|
||||||
|
@ -127,35 +145,72 @@ option {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=input]:focus, input[type=password]:focus,
|
input:not([type]):focus,
|
||||||
input:not([type]):focus, input[type=button]:focus,
|
input[type=button]:focus,
|
||||||
|
input[type=color]:focus,
|
||||||
|
input[type=date]:focus,
|
||||||
|
input[type=datetime-local]:focus,
|
||||||
|
input[type=email]:focus,
|
||||||
|
input[type=month]:focus,
|
||||||
|
input[type=number]:focus,
|
||||||
|
input[type=password]:focus,
|
||||||
|
input[type=reset]:focus,
|
||||||
|
input[type=search]:focus,
|
||||||
input[type=submit]:focus,
|
input[type=submit]:focus,
|
||||||
textarea:focus, select:focus {
|
input[type=tel]:focus,
|
||||||
|
input[type=text]:focus,
|
||||||
|
input[type=time]:focus,
|
||||||
|
input[type=url]:focus,
|
||||||
|
input[type=week]:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
box-shadow: 0px 0px 3px rgba(74, 144, 217, 0.5);
|
box-shadow: 0px 0px 3px rgba(74, 144, 217, 0.5);
|
||||||
border-color: rgb(74, 144, 217);
|
border-color: rgb(74, 144, 217);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=button]::-moz-focus-inner,
|
input[type=button]::-moz-focus-inner,
|
||||||
|
input[type=color]::-moz-focus-inner,
|
||||||
|
input[type=reset]::-moz-focus-inner,
|
||||||
input[type=submit]::-moz-focus-inner {
|
input[type=submit]::-moz-focus-inner {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=input]:disabled, input[type=password]:disabled,
|
input:not([type]):disabled,
|
||||||
input:not([type]):disabled, input[type=button]:disabled,
|
input[type=button]:disabled,
|
||||||
input[type=submit]:disabled, input[type=number]:disabled,
|
input[type=color]:disabled,
|
||||||
textarea:disabled, select:disabled {
|
input[type=date]:disabled,
|
||||||
|
input[type=datetime-local]:disabled,
|
||||||
|
input[type=email]:disabled,
|
||||||
|
input[type=month]:disabled,
|
||||||
|
input[type=number]:disabled,
|
||||||
|
input[type=password]:disabled,
|
||||||
|
input[type=reset]:disabled,
|
||||||
|
input[type=search]:disabled,
|
||||||
|
input[type=submit]:disabled,
|
||||||
|
input[type=tel]:disabled,
|
||||||
|
input[type=text]:disabled,
|
||||||
|
input[type=time]:disabled,
|
||||||
|
input[type=url]:disabled,
|
||||||
|
input[type=week]:disabled,
|
||||||
|
select:disabled,
|
||||||
|
textarea:disabled {
|
||||||
color: rgb(128, 128, 128);
|
color: rgb(128, 128, 128);
|
||||||
background: rgb(240, 240, 240);
|
background: rgb(240, 240, 240);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=button]:active, input[type=submit]:active,
|
input[type=button]:active,
|
||||||
|
input[type=color]:active,
|
||||||
|
input[type=reset]:active,
|
||||||
|
input[type=submit]:active,
|
||||||
select:active {
|
select:active {
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root:not(.noVNC_touch) input[type=button]:hover:not(:disabled),
|
:root:not(.noVNC_touch) input[type=button]:hover:not(:disabled),
|
||||||
|
:root:not(.noVNC_touch) input[type=color]:hover:not(:disabled),
|
||||||
|
:root:not(.noVNC_touch) input[type=reset]:hover:not(:disabled),
|
||||||
:root:not(.noVNC_touch) input[type=submit]:hover:not(:disabled),
|
:root:not(.noVNC_touch) input[type=submit]:hover:not(:disabled),
|
||||||
:root:not(.noVNC_touch) select:hover:not(:disabled) {
|
:root:not(.noVNC_touch) select:hover:not(:disabled) {
|
||||||
background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
|
background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
|
||||||
|
@ -580,7 +635,7 @@ select:active {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Extra manual keys */
|
/* Extra manual keys */
|
||||||
:root:not(.noVNC_connected) #noVNC_extra_keys {
|
:root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,6 +687,16 @@ select:active {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Version */
|
||||||
|
|
||||||
|
.noVNC_version_wrapper {
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noVNC_version {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Connection Controls */
|
/* Connection Controls */
|
||||||
:root:not(.noVNC_connected) #noVNC_disconnect_button {
|
:root:not(.noVNC_connected) #noVNC_disconnect_button {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -781,19 +846,23 @@ select:active {
|
||||||
* ----------------------------------------
|
* ----------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#noVNC_password_dlg {
|
#noVNC_credentials_dlg {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
transform: translateY(-50px);
|
transform: translateY(-50px);
|
||||||
}
|
}
|
||||||
#noVNC_password_dlg.noVNC_open {
|
#noVNC_credentials_dlg.noVNC_open {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
#noVNC_password_dlg ul {
|
#noVNC_credentials_dlg ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
.noVNC_hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------
|
/* ----------------------------------------
|
||||||
* Main Area
|
* Main Area
|
||||||
|
|
|
@ -60,3 +60,8 @@ html {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#noVNC_container {
|
||||||
|
flex: 1; /* fill remaining space */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import * as Log from '../core/util/logging.js';
|
import * as Log from '../core/util/logging.js';
|
||||||
import _, { l10n } from './localization.js';
|
import _, { l10n } from './localization.js';
|
||||||
import { isTouchDevice, isSafari, isIOS, isAndroid, dragThreshold }
|
import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold }
|
||||||
from '../core/util/browser.js';
|
from '../core/util/browser.js';
|
||||||
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
||||||
import KeyTable from "../core/input/keysym.js";
|
import KeyTable from "../core/input/keysym.js";
|
||||||
|
@ -17,6 +17,8 @@ import Keyboard from "../core/input/keyboard.js";
|
||||||
import RFB from "../core/rfb.js";
|
import RFB from "../core/rfb.js";
|
||||||
import * as WebUtil from "./webutil.js";
|
import * as WebUtil from "./webutil.js";
|
||||||
|
|
||||||
|
const PAGE_TITLE = "noVNC";
|
||||||
|
|
||||||
const UI = {
|
const UI = {
|
||||||
|
|
||||||
connected: false,
|
connected: false,
|
||||||
|
@ -35,9 +37,9 @@ const UI = {
|
||||||
lastKeyboardinput: null,
|
lastKeyboardinput: null,
|
||||||
defaultKeyboardinputLen: 100,
|
defaultKeyboardinputLen: 100,
|
||||||
|
|
||||||
inhibit_reconnect: true,
|
inhibitReconnect: true,
|
||||||
reconnect_callback: null,
|
reconnectCallback: null,
|
||||||
reconnect_password: null,
|
reconnectPassword: null,
|
||||||
|
|
||||||
prime() {
|
prime() {
|
||||||
return WebUtil.initSettings().then(() => {
|
return WebUtil.initSettings().then(() => {
|
||||||
|
@ -59,6 +61,17 @@ const UI = {
|
||||||
// Translate the DOM
|
// Translate the DOM
|
||||||
l10n.translateDOM();
|
l10n.translateDOM();
|
||||||
|
|
||||||
|
WebUtil.fetchJSON('/static/js/novnc/package.json')
|
||||||
|
.then((packageInfo) => {
|
||||||
|
Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
Log.Error("Couldn't fetch package.json: " + err);
|
||||||
|
Array.from(document.getElementsByClassName('noVNC_version_wrapper'))
|
||||||
|
.concat(Array.from(document.getElementsByClassName('noVNC_version_separator')))
|
||||||
|
.forEach(el => el.style.display = 'none');
|
||||||
|
});
|
||||||
|
|
||||||
// Adapt the interface for touch screen devices
|
// Adapt the interface for touch screen devices
|
||||||
if (isTouchDevice) {
|
if (isTouchDevice) {
|
||||||
document.documentElement.classList.add("noVNC_touch");
|
document.documentElement.classList.add("noVNC_touch");
|
||||||
|
@ -148,6 +161,8 @@ const UI = {
|
||||||
UI.initSetting('encrypt', (window.location.protocol === "https:"));
|
UI.initSetting('encrypt', (window.location.protocol === "https:"));
|
||||||
UI.initSetting('view_clip', false);
|
UI.initSetting('view_clip', false);
|
||||||
UI.initSetting('resize', 'off');
|
UI.initSetting('resize', 'off');
|
||||||
|
UI.initSetting('quality', 6);
|
||||||
|
UI.initSetting('compression', 2);
|
||||||
UI.initSetting('shared', true);
|
UI.initSetting('shared', true);
|
||||||
UI.initSetting('view_only', false);
|
UI.initSetting('view_only', false);
|
||||||
UI.initSetting('show_dot', false);
|
UI.initSetting('show_dot', false);
|
||||||
|
@ -219,14 +234,6 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
addTouchSpecificHandlers() {
|
addTouchSpecificHandlers() {
|
||||||
document.getElementById("noVNC_mouse_button0")
|
|
||||||
.addEventListener('click', () => UI.setMouseButton(1));
|
|
||||||
document.getElementById("noVNC_mouse_button1")
|
|
||||||
.addEventListener('click', () => UI.setMouseButton(2));
|
|
||||||
document.getElementById("noVNC_mouse_button2")
|
|
||||||
.addEventListener('click', () => UI.setMouseButton(4));
|
|
||||||
document.getElementById("noVNC_mouse_button4")
|
|
||||||
.addEventListener('click', () => UI.setMouseButton(0));
|
|
||||||
document.getElementById("noVNC_keyboard_button")
|
document.getElementById("noVNC_keyboard_button")
|
||||||
.addEventListener('click', UI.toggleVirtualKeyboard);
|
.addEventListener('click', UI.toggleVirtualKeyboard);
|
||||||
|
|
||||||
|
@ -282,33 +289,6 @@ const UI = {
|
||||||
.addEventListener('click', UI.sendEsc);
|
.addEventListener('click', UI.sendEsc);
|
||||||
document.getElementById("noVNC_send_ctrl_alt_del_button")
|
document.getElementById("noVNC_send_ctrl_alt_del_button")
|
||||||
.addEventListener('click', UI.sendCtrlAltDel);
|
.addEventListener('click', UI.sendCtrlAltDel);
|
||||||
|
|
||||||
document.getElementById('ctrlaltdel')
|
|
||||||
.addEventListener('click', UI.sendCtrlAltDel);
|
|
||||||
document.getElementById('ctrlaltf1')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(0));
|
|
||||||
document.getElementById('ctrlaltf2')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(1));
|
|
||||||
document.getElementById('ctrlaltf3')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(2));
|
|
||||||
document.getElementById('ctrlaltf4')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(3));
|
|
||||||
document.getElementById('ctrlaltf5')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(4));
|
|
||||||
document.getElementById('ctrlaltf6')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(5));
|
|
||||||
document.getElementById('ctrlaltf7')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(6));
|
|
||||||
document.getElementById('ctrlaltf8')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(7));
|
|
||||||
document.getElementById('ctrlaltf9')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(8));
|
|
||||||
document.getElementById('ctrlaltf10')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(9));
|
|
||||||
document.getElementById('ctrlaltf11')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(10));
|
|
||||||
document.getElementById('ctrlaltf12')
|
|
||||||
.addEventListener('click', () => UI.sendCtrlAltFN(11));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addMachineHandlers() {
|
addMachineHandlers() {
|
||||||
|
@ -330,8 +310,8 @@ const UI = {
|
||||||
document.getElementById("noVNC_cancel_reconnect_button")
|
document.getElementById("noVNC_cancel_reconnect_button")
|
||||||
.addEventListener('click', UI.cancelReconnect);
|
.addEventListener('click', UI.cancelReconnect);
|
||||||
|
|
||||||
document.getElementById("noVNC_password_button")
|
document.getElementById("noVNC_credentials_button")
|
||||||
.addEventListener('click', UI.setPassword);
|
.addEventListener('click', UI.setCredentials);
|
||||||
},
|
},
|
||||||
|
|
||||||
addClipboardHandlers() {
|
addClipboardHandlers() {
|
||||||
|
@ -361,6 +341,10 @@ const UI = {
|
||||||
UI.addSettingChangeHandler('resize');
|
UI.addSettingChangeHandler('resize');
|
||||||
UI.addSettingChangeHandler('resize', UI.applyResizeMode);
|
UI.addSettingChangeHandler('resize', UI.applyResizeMode);
|
||||||
UI.addSettingChangeHandler('resize', UI.updateViewClip);
|
UI.addSettingChangeHandler('resize', UI.updateViewClip);
|
||||||
|
UI.addSettingChangeHandler('quality');
|
||||||
|
UI.addSettingChangeHandler('quality', UI.updateQuality);
|
||||||
|
UI.addSettingChangeHandler('compression');
|
||||||
|
UI.addSettingChangeHandler('compression', UI.updateCompression);
|
||||||
UI.addSettingChangeHandler('view_clip');
|
UI.addSettingChangeHandler('view_clip');
|
||||||
UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
|
UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
|
||||||
UI.addSettingChangeHandler('shared');
|
UI.addSettingChangeHandler('shared');
|
||||||
|
@ -381,8 +365,6 @@ const UI = {
|
||||||
addFullscreenHandlers() {
|
addFullscreenHandlers() {
|
||||||
document.getElementById("noVNC_fullscreen_button")
|
document.getElementById("noVNC_fullscreen_button")
|
||||||
.addEventListener('click', UI.toggleFullscreen);
|
.addEventListener('click', UI.toggleFullscreen);
|
||||||
document.getElementById("fullscreen_button")
|
|
||||||
.addEventListener('click', UI.toggleFullscreen);
|
|
||||||
|
|
||||||
window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
|
window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
|
||||||
window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
|
window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
|
||||||
|
@ -404,25 +386,25 @@ const UI = {
|
||||||
document.documentElement.classList.remove("noVNC_disconnecting");
|
document.documentElement.classList.remove("noVNC_disconnecting");
|
||||||
document.documentElement.classList.remove("noVNC_reconnecting");
|
document.documentElement.classList.remove("noVNC_reconnecting");
|
||||||
|
|
||||||
const transition_elem = document.getElementById("noVNC_transition_text");
|
const transitionElem = document.getElementById("noVNC_transition_text");
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'init':
|
case 'init':
|
||||||
break;
|
break;
|
||||||
case 'connecting':
|
case 'connecting':
|
||||||
transition_elem.textContent = _("Connecting...");
|
transitionElem.textContent = _("Connecting...");
|
||||||
document.documentElement.classList.add("noVNC_connecting");
|
document.documentElement.classList.add("noVNC_connecting");
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
document.documentElement.classList.add("noVNC_connected");
|
document.documentElement.classList.add("noVNC_connected");
|
||||||
break;
|
break;
|
||||||
case 'disconnecting':
|
case 'disconnecting':
|
||||||
transition_elem.textContent = _("Disconnecting...");
|
transitionElem.textContent = _("Disconnecting...");
|
||||||
document.documentElement.classList.add("noVNC_disconnecting");
|
document.documentElement.classList.add("noVNC_disconnecting");
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
break;
|
break;
|
||||||
case 'reconnecting':
|
case 'reconnecting':
|
||||||
transition_elem.textContent = _("Reconnecting...");
|
transitionElem.textContent = _("Reconnecting...");
|
||||||
document.documentElement.classList.add("noVNC_reconnecting");
|
document.documentElement.classList.add("noVNC_reconnecting");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -440,7 +422,6 @@ const UI = {
|
||||||
UI.disableSetting('port');
|
UI.disableSetting('port');
|
||||||
UI.disableSetting('path');
|
UI.disableSetting('path');
|
||||||
UI.disableSetting('repeaterID');
|
UI.disableSetting('repeaterID');
|
||||||
UI.setMouseButton(1);
|
|
||||||
|
|
||||||
// Hide the controlbar after 2 seconds
|
// Hide the controlbar after 2 seconds
|
||||||
UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
|
UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
|
||||||
|
@ -455,38 +436,35 @@ const UI = {
|
||||||
UI.keepControlbar();
|
UI.keepControlbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
// State change closes the password dialog
|
// State change closes dialogs as they may not be relevant
|
||||||
document.getElementById('noVNC_password_dlg')
|
// anymore
|
||||||
|
UI.closeAllPanels();
|
||||||
|
document.getElementById('noVNC_credentials_dlg')
|
||||||
.classList.remove('noVNC_open');
|
.classList.remove('noVNC_open');
|
||||||
},
|
},
|
||||||
|
|
||||||
showStatus(text, status_type, time) {
|
showStatus(text, statusType, time) {
|
||||||
const statusElem = document.getElementById('noVNC_status');
|
const statusElem = document.getElementById('noVNC_status');
|
||||||
|
|
||||||
clearTimeout(UI.statusTimeout);
|
if (typeof statusType === 'undefined') {
|
||||||
|
statusType = 'normal';
|
||||||
if (typeof status_type === 'undefined') {
|
|
||||||
status_type = 'normal';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't overwrite more severe visible statuses and never
|
// Don't overwrite more severe visible statuses and never
|
||||||
// errors. Only shows the first error.
|
// errors. Only shows the first error.
|
||||||
let visible_status_type = 'none';
|
|
||||||
if (statusElem.classList.contains("noVNC_open")) {
|
if (statusElem.classList.contains("noVNC_open")) {
|
||||||
if (statusElem.classList.contains("noVNC_status_error")) {
|
if (statusElem.classList.contains("noVNC_status_error")) {
|
||||||
visible_status_type = 'error';
|
return;
|
||||||
} else if (statusElem.classList.contains("noVNC_status_warn")) {
|
}
|
||||||
visible_status_type = 'warn';
|
if (statusElem.classList.contains("noVNC_status_warn") &&
|
||||||
} else {
|
statusType === 'normal') {
|
||||||
visible_status_type = 'normal';
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (visible_status_type === 'error' ||
|
|
||||||
(visible_status_type === 'warn' && status_type === 'normal')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (status_type) {
|
clearTimeout(UI.statusTimeout);
|
||||||
|
|
||||||
|
switch (statusType) {
|
||||||
case 'error':
|
case 'error':
|
||||||
statusElem.classList.remove("noVNC_status_warn");
|
statusElem.classList.remove("noVNC_status_warn");
|
||||||
statusElem.classList.remove("noVNC_status_normal");
|
statusElem.classList.remove("noVNC_status_normal");
|
||||||
|
@ -516,7 +494,7 @@ const UI = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error messages do not timeout
|
// Error messages do not timeout
|
||||||
if (status_type !== 'error') {
|
if (statusType !== 'error') {
|
||||||
UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
|
UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -536,6 +514,13 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
idleControlbar() {
|
idleControlbar() {
|
||||||
|
// Don't fade if a child of the control bar has focus
|
||||||
|
if (document.getElementById('noVNC_control_bar')
|
||||||
|
.contains(document.activeElement) && document.hasFocus()) {
|
||||||
|
UI.activateControlbar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('noVNC_control_bar_anchor')
|
document.getElementById('noVNC_control_bar_anchor')
|
||||||
.classList.add("noVNC_idle");
|
.classList.add("noVNC_idle");
|
||||||
},
|
},
|
||||||
|
@ -553,6 +538,7 @@ const UI = {
|
||||||
UI.closeAllPanels();
|
UI.closeAllPanels();
|
||||||
document.getElementById('noVNC_control_bar')
|
document.getElementById('noVNC_control_bar')
|
||||||
.classList.remove("noVNC_open");
|
.classList.remove("noVNC_open");
|
||||||
|
UI.rfb.focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleControlbar() {
|
toggleControlbar() {
|
||||||
|
@ -850,6 +836,8 @@ const UI = {
|
||||||
UI.updateSetting('encrypt');
|
UI.updateSetting('encrypt');
|
||||||
UI.updateSetting('view_clip');
|
UI.updateSetting('view_clip');
|
||||||
UI.updateSetting('resize');
|
UI.updateSetting('resize');
|
||||||
|
UI.updateSetting('quality');
|
||||||
|
UI.updateSetting('compression');
|
||||||
UI.updateSetting('shared');
|
UI.updateSetting('shared');
|
||||||
UI.updateSetting('view_only');
|
UI.updateSetting('view_only');
|
||||||
UI.updateSetting('path');
|
UI.updateSetting('path');
|
||||||
|
@ -1006,7 +994,7 @@ const UI = {
|
||||||
|
|
||||||
if (typeof password === 'undefined') {
|
if (typeof password === 'undefined') {
|
||||||
password = WebUtil.getConfigVar('password');
|
password = WebUtil.getConfigVar('password');
|
||||||
UI.reconnect_password = password;
|
UI.reconnectPassword = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password === null) {
|
if (password === null) {
|
||||||
|
@ -1021,7 +1009,6 @@ const UI = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.closeAllPanels();
|
|
||||||
UI.closeConnectPanel();
|
UI.closeConnectPanel();
|
||||||
|
|
||||||
UI.updateVisualState('connecting');
|
UI.updateVisualState('connecting');
|
||||||
|
@ -1038,7 +1025,6 @@ const UI = {
|
||||||
|
|
||||||
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
|
UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
|
||||||
{ shared: UI.getSetting('shared'),
|
{ shared: UI.getSetting('shared'),
|
||||||
showDotCursor: UI.getSetting('show_dot'),
|
|
||||||
repeaterID: UI.getSetting('repeaterID'),
|
repeaterID: UI.getSetting('repeaterID'),
|
||||||
credentials: { password: password } });
|
credentials: { password: password } });
|
||||||
UI.rfb.addEventListener("connect", UI.connectFinished);
|
UI.rfb.addEventListener("connect", UI.connectFinished);
|
||||||
|
@ -1052,18 +1038,20 @@ const UI = {
|
||||||
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
UI.rfb.clipViewport = UI.getSetting('view_clip');
|
||||||
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
|
||||||
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
|
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
|
||||||
|
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
|
||||||
|
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
||||||
|
UI.rfb.showDotCursor = UI.getSetting('show_dot');
|
||||||
|
|
||||||
UI.updateViewOnly(); // requires UI.rfb
|
UI.updateViewOnly(); // requires UI.rfb
|
||||||
},
|
},
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
UI.closeAllPanels();
|
|
||||||
UI.rfb.disconnect();
|
UI.rfb.disconnect();
|
||||||
|
|
||||||
UI.connected = false;
|
UI.connected = false;
|
||||||
|
|
||||||
// Disable automatic reconnecting
|
// Disable automatic reconnecting
|
||||||
UI.inhibit_reconnect = true;
|
UI.inhibitReconnect = true;
|
||||||
|
|
||||||
UI.updateVisualState('disconnecting');
|
UI.updateVisualState('disconnecting');
|
||||||
|
|
||||||
|
@ -1071,20 +1059,20 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
reconnect() {
|
reconnect() {
|
||||||
UI.reconnect_callback = null;
|
UI.reconnectCallback = null;
|
||||||
|
|
||||||
// if reconnect has been disabled in the meantime, do nothing.
|
// if reconnect has been disabled in the meantime, do nothing.
|
||||||
if (UI.inhibit_reconnect) {
|
if (UI.inhibitReconnect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.connect(null, UI.reconnect_password);
|
UI.connect(null, UI.reconnectPassword);
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelReconnect() {
|
cancelReconnect() {
|
||||||
if (UI.reconnect_callback !== null) {
|
if (UI.reconnectCallback !== null) {
|
||||||
clearTimeout(UI.reconnect_callback);
|
clearTimeout(UI.reconnectCallback);
|
||||||
UI.reconnect_callback = null;
|
UI.reconnectCallback = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.updateVisualState('disconnected');
|
UI.updateVisualState('disconnected');
|
||||||
|
@ -1095,7 +1083,7 @@ const UI = {
|
||||||
|
|
||||||
connectFinished(e) {
|
connectFinished(e) {
|
||||||
UI.connected = true;
|
UI.connected = true;
|
||||||
UI.inhibit_reconnect = false;
|
UI.inhibitReconnect = false;
|
||||||
|
|
||||||
let msg;
|
let msg;
|
||||||
if (UI.getSetting('encrypt')) {
|
if (UI.getSetting('encrypt')) {
|
||||||
|
@ -1129,17 +1117,19 @@ const UI = {
|
||||||
} else {
|
} else {
|
||||||
UI.showStatus(_("Failed to connect to server"), 'error');
|
UI.showStatus(_("Failed to connect to server"), 'error');
|
||||||
}
|
}
|
||||||
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
|
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {
|
||||||
UI.updateVisualState('reconnecting');
|
UI.updateVisualState('reconnecting');
|
||||||
|
|
||||||
const delay = parseInt(UI.getSetting('reconnect_delay'));
|
const delay = parseInt(UI.getSetting('reconnect_delay'));
|
||||||
UI.reconnect_callback = setTimeout(UI.reconnect, delay);
|
UI.reconnectCallback = setTimeout(UI.reconnect, delay);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
UI.updateVisualState('disconnected');
|
UI.updateVisualState('disconnected');
|
||||||
UI.showStatus(_("Disconnected"), 'normal');
|
UI.showStatus(_("Disconnected"), 'normal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.title = PAGE_TITLE;
|
||||||
|
|
||||||
UI.openControlbar();
|
UI.openControlbar();
|
||||||
UI.openConnectPanel();
|
UI.openConnectPanel();
|
||||||
},
|
},
|
||||||
|
@ -1166,27 +1156,46 @@ const UI = {
|
||||||
|
|
||||||
credentials(e) {
|
credentials(e) {
|
||||||
// FIXME: handle more types
|
// FIXME: handle more types
|
||||||
document.getElementById('noVNC_password_dlg')
|
|
||||||
|
document.getElementById("noVNC_username_block").classList.remove("noVNC_hidden");
|
||||||
|
document.getElementById("noVNC_password_block").classList.remove("noVNC_hidden");
|
||||||
|
|
||||||
|
let inputFocus = "none";
|
||||||
|
if (e.detail.types.indexOf("username") === -1) {
|
||||||
|
document.getElementById("noVNC_username_block").classList.add("noVNC_hidden");
|
||||||
|
} else {
|
||||||
|
inputFocus = inputFocus === "none" ? "noVNC_username_input" : inputFocus;
|
||||||
|
}
|
||||||
|
if (e.detail.types.indexOf("password") === -1) {
|
||||||
|
document.getElementById("noVNC_password_block").classList.add("noVNC_hidden");
|
||||||
|
} else {
|
||||||
|
inputFocus = inputFocus === "none" ? "noVNC_password_input" : inputFocus;
|
||||||
|
}
|
||||||
|
document.getElementById('noVNC_credentials_dlg')
|
||||||
.classList.add('noVNC_open');
|
.classList.add('noVNC_open');
|
||||||
|
|
||||||
setTimeout(() => document
|
setTimeout(() => document
|
||||||
.getElementById('noVNC_password_input').focus(), 100);
|
.getElementById(inputFocus).focus(), 100);
|
||||||
|
|
||||||
Log.Warn("Server asked for a password");
|
Log.Warn("Server asked for credentials");
|
||||||
UI.showStatus(_("Password is required"), "warning");
|
UI.showStatus(_("Credentials are required"), "warning");
|
||||||
},
|
},
|
||||||
|
|
||||||
setPassword(e) {
|
setCredentials(e) {
|
||||||
// Prevent actually submitting the form
|
// Prevent actually submitting the form
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const inputElem = document.getElementById('noVNC_password_input');
|
let inputElemUsername = document.getElementById('noVNC_username_input');
|
||||||
const password = inputElem.value;
|
const username = inputElemUsername.value;
|
||||||
|
|
||||||
|
let inputElemPassword = document.getElementById('noVNC_password_input');
|
||||||
|
const password = inputElemPassword.value;
|
||||||
// Clear the input after reading the password
|
// Clear the input after reading the password
|
||||||
inputElem.value = "";
|
inputElemPassword.value = "";
|
||||||
UI.rfb.sendCredentials({ password: password });
|
|
||||||
UI.reconnect_password = password;
|
UI.rfb.sendCredentials({ username: username, password: password });
|
||||||
document.getElementById('noVNC_password_dlg')
|
UI.reconnectPassword = password;
|
||||||
|
document.getElementById('noVNC_credentials_dlg')
|
||||||
.classList.remove('noVNC_open');
|
.classList.remove('noVNC_open');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1269,8 +1278,9 @@ const UI = {
|
||||||
// Can't be clipping if viewport is scaled to fit
|
// Can't be clipping if viewport is scaled to fit
|
||||||
UI.forceSetting('view_clip', false);
|
UI.forceSetting('view_clip', false);
|
||||||
UI.rfb.clipViewport = false;
|
UI.rfb.clipViewport = false;
|
||||||
} else if (isIOS() || isAndroid()) {
|
} else if (!hasScrollbarGutter) {
|
||||||
// iOS and Android usually have shit scrollbars
|
// Some platforms have scrollbars that are difficult
|
||||||
|
// to use in our case, so we always use our own panning
|
||||||
UI.forceSetting('view_clip', true);
|
UI.forceSetting('view_clip', true);
|
||||||
UI.rfb.clipViewport = true;
|
UI.rfb.clipViewport = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1313,30 +1323,40 @@ const UI = {
|
||||||
viewDragButton.classList.remove("noVNC_selected");
|
viewDragButton.classList.remove("noVNC_selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Different behaviour for touch vs non-touch
|
if (UI.rfb.clipViewport) {
|
||||||
// The button is disabled instead of hidden on touch devices
|
|
||||||
if (isTouchDevice) {
|
|
||||||
viewDragButton.classList.remove("noVNC_hidden");
|
viewDragButton.classList.remove("noVNC_hidden");
|
||||||
|
|
||||||
if (UI.rfb.clipViewport) {
|
|
||||||
viewDragButton.disabled = false;
|
|
||||||
} else {
|
|
||||||
viewDragButton.disabled = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
viewDragButton.disabled = false;
|
viewDragButton.classList.add("noVNC_hidden");
|
||||||
|
|
||||||
if (UI.rfb.clipViewport) {
|
|
||||||
viewDragButton.classList.remove("noVNC_hidden");
|
|
||||||
} else {
|
|
||||||
viewDragButton.classList.add("noVNC_hidden");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/* ------^-------
|
/* ------^-------
|
||||||
* /VIEWDRAG
|
* /VIEWDRAG
|
||||||
* ==============
|
* ==============
|
||||||
|
* QUALITY
|
||||||
|
* ------v------*/
|
||||||
|
|
||||||
|
updateQuality() {
|
||||||
|
if (!UI.rfb) return;
|
||||||
|
|
||||||
|
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ------^-------
|
||||||
|
* /QUALITY
|
||||||
|
* ==============
|
||||||
|
* COMPRESSION
|
||||||
|
* ------v------*/
|
||||||
|
|
||||||
|
updateCompression() {
|
||||||
|
if (!UI.rfb) return;
|
||||||
|
|
||||||
|
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ------^-------
|
||||||
|
* /COMPRESSION
|
||||||
|
* ==============
|
||||||
* KEYBOARD
|
* KEYBOARD
|
||||||
* ------v------*/
|
* ------v------*/
|
||||||
|
|
||||||
|
@ -1531,20 +1551,20 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
sendEsc() {
|
sendEsc() {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
|
UI.sendKey(KeyTable.XK_Escape, "Escape");
|
||||||
},
|
},
|
||||||
|
|
||||||
sendTab() {
|
sendTab() {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Tab);
|
UI.sendKey(KeyTable.XK_Tab, "Tab");
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleCtrl() {
|
toggleCtrl() {
|
||||||
const btn = document.getElementById('noVNC_toggle_ctrl_button');
|
const btn = document.getElementById('noVNC_toggle_ctrl_button');
|
||||||
if (btn.classList.contains("noVNC_selected")) {
|
if (btn.classList.contains("noVNC_selected")) {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
|
UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
|
||||||
btn.classList.remove("noVNC_selected");
|
btn.classList.remove("noVNC_selected");
|
||||||
} else {
|
} else {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
|
UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
btn.classList.add("noVNC_selected");
|
btn.classList.add("noVNC_selected");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1552,10 +1572,10 @@ const UI = {
|
||||||
toggleWindows() {
|
toggleWindows() {
|
||||||
const btn = document.getElementById('noVNC_toggle_windows_button');
|
const btn = document.getElementById('noVNC_toggle_windows_button');
|
||||||
if (btn.classList.contains("noVNC_selected")) {
|
if (btn.classList.contains("noVNC_selected")) {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
|
UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
|
||||||
btn.classList.remove("noVNC_selected");
|
btn.classList.remove("noVNC_selected");
|
||||||
} else {
|
} else {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
|
UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
|
||||||
btn.classList.add("noVNC_selected");
|
btn.classList.add("noVNC_selected");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1563,20 +1583,39 @@ const UI = {
|
||||||
toggleAlt() {
|
toggleAlt() {
|
||||||
const btn = document.getElementById('noVNC_toggle_alt_button');
|
const btn = document.getElementById('noVNC_toggle_alt_button');
|
||||||
if (btn.classList.contains("noVNC_selected")) {
|
if (btn.classList.contains("noVNC_selected")) {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
|
UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
|
||||||
btn.classList.remove("noVNC_selected");
|
btn.classList.remove("noVNC_selected");
|
||||||
} else {
|
} else {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
|
UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
|
||||||
btn.classList.add("noVNC_selected");
|
btn.classList.add("noVNC_selected");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
sendCtrlAltDel() {
|
sendCtrlAltDel() {
|
||||||
UI.rfb.sendCtrlAltDel();
|
UI.rfb.sendCtrlAltDel();
|
||||||
|
// See below
|
||||||
|
UI.rfb.focus();
|
||||||
|
UI.idleControlbar();
|
||||||
},
|
},
|
||||||
|
|
||||||
sendCtrlAltFN: function(f) {
|
sendKey(keysym, code, down) {
|
||||||
UI.rfb.sendCtrlAltFN(f);
|
UI.rfb.sendKey(keysym, code, down);
|
||||||
|
|
||||||
|
// Move focus to the screen in order to be able to use the
|
||||||
|
// keyboard right after these extra keys.
|
||||||
|
// The exception is when a virtual keyboard is used, because
|
||||||
|
// if we focus the screen the virtual keyboard would be closed.
|
||||||
|
// In this case we focus our special virtual keyboard input
|
||||||
|
// element instead.
|
||||||
|
if (document.getElementById('noVNC_keyboard_button')
|
||||||
|
.classList.contains("noVNC_selected")) {
|
||||||
|
document.getElementById('noVNC_keyboardinput').focus();
|
||||||
|
} else {
|
||||||
|
UI.rfb.focus();
|
||||||
|
}
|
||||||
|
// fade out the controlbar to highlight that
|
||||||
|
// the focus has been moved to the screen
|
||||||
|
UI.idleControlbar();
|
||||||
},
|
},
|
||||||
|
|
||||||
/* ------^-------
|
/* ------^-------
|
||||||
|
@ -1585,24 +1624,6 @@ const UI = {
|
||||||
* MISC
|
* MISC
|
||||||
* ------v------*/
|
* ------v------*/
|
||||||
|
|
||||||
setMouseButton(num) {
|
|
||||||
const view_only = UI.rfb.viewOnly;
|
|
||||||
if (UI.rfb && !view_only) {
|
|
||||||
UI.rfb.touchButton = num;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blist = [0, 1, 2, 4];
|
|
||||||
for (let b = 0; b < blist.length; b++) {
|
|
||||||
const button = document.getElementById('noVNC_mouse_button' +
|
|
||||||
blist[b]);
|
|
||||||
if (blist[b] === num && !view_only) {
|
|
||||||
button.classList.remove("noVNC_hidden");
|
|
||||||
} else {
|
|
||||||
button.classList.add("noVNC_hidden");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateViewOnly() {
|
updateViewOnly() {
|
||||||
if (!UI.rfb) return;
|
if (!UI.rfb) return;
|
||||||
UI.rfb.viewOnly = UI.getSetting('view_only');
|
UI.rfb.viewOnly = UI.getSetting('view_only');
|
||||||
|
@ -1613,14 +1634,14 @@ const UI = {
|
||||||
.classList.add('noVNC_hidden');
|
.classList.add('noVNC_hidden');
|
||||||
document.getElementById('noVNC_toggle_extra_keys_button')
|
document.getElementById('noVNC_toggle_extra_keys_button')
|
||||||
.classList.add('noVNC_hidden');
|
.classList.add('noVNC_hidden');
|
||||||
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
|
document.getElementById('noVNC_clipboard_button')
|
||||||
.classList.add('noVNC_hidden');
|
.classList.add('noVNC_hidden');
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('noVNC_keyboard_button')
|
document.getElementById('noVNC_keyboard_button')
|
||||||
.classList.remove('noVNC_hidden');
|
.classList.remove('noVNC_hidden');
|
||||||
document.getElementById('noVNC_toggle_extra_keys_button')
|
document.getElementById('noVNC_toggle_extra_keys_button')
|
||||||
.classList.remove('noVNC_hidden');
|
.classList.remove('noVNC_hidden');
|
||||||
document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
|
document.getElementById('noVNC_clipboard_button')
|
||||||
.classList.remove('noVNC_hidden');
|
.classList.remove('noVNC_hidden');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1631,13 +1652,13 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
updateLogging() {
|
updateLogging() {
|
||||||
WebUtil.init_logging(UI.getSetting('logging'));
|
WebUtil.initLogging(UI.getSetting('logging'));
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDesktopName(e) {
|
updateDesktopName(e) {
|
||||||
UI.desktopName = e.detail.name;
|
UI.desktopName = e.detail.name;
|
||||||
// Display the desktop name in the document title
|
// Display the desktop name in the document title
|
||||||
document.title = e.detail.name + " - noVNC";
|
document.title = e.detail.name + " - " + PAGE_TITLE;
|
||||||
},
|
},
|
||||||
|
|
||||||
bell(e) {
|
bell(e) {
|
||||||
|
@ -1673,7 +1694,7 @@ const UI = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up translations
|
// Set up translations
|
||||||
const LINGUAS = ["cs", "de", "el", "es", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
const LINGUAS = ["cs", "de", "el", "es", "ja", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
||||||
l10n.setup(LINGUAS);
|
l10n.setup(LINGUAS);
|
||||||
if (l10n.language === "en" || l10n.dictionary !== undefined) {
|
if (l10n.language === "en" || l10n.dictionary !== undefined) {
|
||||||
UI.prime();
|
UI.prime();
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { init_logging as main_init_logging } from '../core/util/logging.js';
|
import { initLogging as mainInitLogging } from '../core/util/logging.js';
|
||||||
|
|
||||||
// init log level reading the logging HTTP param
|
// init log level reading the logging HTTP param
|
||||||
export function init_logging(level) {
|
export function initLogging(level) {
|
||||||
"use strict";
|
"use strict";
|
||||||
if (typeof level !== "undefined") {
|
if (typeof level !== "undefined") {
|
||||||
main_init_logging(level);
|
mainInitLogging(level);
|
||||||
} else {
|
} else {
|
||||||
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
||||||
main_init_logging(param || undefined);
|
mainInitLogging(param || undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ export function injectParamIfMissing(path, param, value) {
|
||||||
const elem = document.createElement('a');
|
const elem = document.createElement('a');
|
||||||
elem.href = path;
|
elem.href = path;
|
||||||
|
|
||||||
const param_eq = encodeURIComponent(param) + "=";
|
const paramEq = encodeURIComponent(param) + "=";
|
||||||
let query;
|
let query;
|
||||||
if (elem.search) {
|
if (elem.search) {
|
||||||
query = elem.search.slice(1).split('&');
|
query = elem.search.slice(1).split('&');
|
||||||
|
@ -192,8 +192,8 @@ export function injectParamIfMissing(path, param, value) {
|
||||||
query = [];
|
query = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!query.some(v => v.startsWith(param_eq))) {
|
if (!query.some(v => v.startsWith(paramEq))) {
|
||||||
query.push(param_eq + encodeURIComponent(value));
|
query.push(paramEq + encodeURIComponent(value));
|
||||||
elem.search = "?" + query.join("&");
|
elem.search = "?" + query.join("&");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,12 +57,12 @@ export default {
|
||||||
/* eslint-enable comma-spacing */
|
/* eslint-enable comma-spacing */
|
||||||
|
|
||||||
decode(data, offset = 0) {
|
decode(data, offset = 0) {
|
||||||
let data_length = data.indexOf('=') - offset;
|
let dataLength = data.indexOf('=') - offset;
|
||||||
if (data_length < 0) { data_length = data.length - offset; }
|
if (dataLength < 0) { dataLength = data.length - offset; }
|
||||||
|
|
||||||
/* Every four characters is 3 resulting numbers */
|
/* Every four characters is 3 resulting numbers */
|
||||||
const result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
|
const resultLength = (dataLength >> 2) * 3 + Math.floor((dataLength % 4) / 1.5);
|
||||||
const result = new Array(result_length);
|
const result = new Array(resultLength);
|
||||||
|
|
||||||
// Convert one by one.
|
// Convert one by one.
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2012 Joel Martin
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
|
||||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2012 Joel Martin
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
|
||||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -19,10 +17,10 @@ export default class HextileDecoder {
|
||||||
|
|
||||||
decodeRect(x, y, width, height, sock, display, depth) {
|
decodeRect(x, y, width, height, sock, display, depth) {
|
||||||
if (this._tiles === 0) {
|
if (this._tiles === 0) {
|
||||||
this._tiles_x = Math.ceil(width / 16);
|
this._tilesX = Math.ceil(width / 16);
|
||||||
this._tiles_y = Math.ceil(height / 16);
|
this._tilesY = Math.ceil(height / 16);
|
||||||
this._total_tiles = this._tiles_x * this._tiles_y;
|
this._totalTiles = this._tilesX * this._tilesY;
|
||||||
this._tiles = this._total_tiles;
|
this._tiles = this._totalTiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (this._tiles > 0) {
|
while (this._tiles > 0) {
|
||||||
|
@ -41,11 +39,11 @@ export default class HextileDecoder {
|
||||||
subencoding + ")");
|
subencoding + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
const curr_tile = this._total_tiles - this._tiles;
|
const currTile = this._totalTiles - this._tiles;
|
||||||
const tile_x = curr_tile % this._tiles_x;
|
const tileX = currTile % this._tilesX;
|
||||||
const tile_y = Math.floor(curr_tile / this._tiles_x);
|
const tileY = Math.floor(currTile / this._tilesX);
|
||||||
const tx = x + tile_x * 16;
|
const tx = x + tileX * 16;
|
||||||
const ty = y + tile_y * 16;
|
const ty = y + tileY * 16;
|
||||||
const tw = Math.min(16, (x + width) - tx);
|
const tw = Math.min(16, (x + width) - tx);
|
||||||
const th = Math.min(16, (y + height) - ty);
|
const th = Math.min(16, (y + height) - ty);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2012 Joel Martin
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
|
||||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -26,15 +24,15 @@ export default class RawDecoder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cur_y = y + (height - this._lines);
|
const curY = y + (height - this._lines);
|
||||||
const curr_height = Math.min(this._lines,
|
const currHeight = Math.min(this._lines,
|
||||||
Math.floor(sock.rQlen / bytesPerLine));
|
Math.floor(sock.rQlen / bytesPerLine));
|
||||||
let data = sock.rQ;
|
let data = sock.rQ;
|
||||||
let index = sock.rQi;
|
let index = sock.rQi;
|
||||||
|
|
||||||
// Convert data if needed
|
// Convert data if needed
|
||||||
if (depth == 8) {
|
if (depth == 8) {
|
||||||
const pixels = width * curr_height;
|
const pixels = width * currHeight;
|
||||||
const newdata = new Uint8Array(pixels * 4);
|
const newdata = new Uint8Array(pixels * 4);
|
||||||
for (let i = 0; i < pixels; i++) {
|
for (let i = 0; i < pixels; i++) {
|
||||||
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
|
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
|
||||||
|
@ -46,9 +44,9 @@ export default class RawDecoder {
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.blitImage(x, cur_y, width, curr_height, data, index);
|
display.blitImage(x, curY, width, currHeight, data, index);
|
||||||
sock.rQskipBytes(curr_height * bytesPerLine);
|
sock.rQskipBytes(currHeight * bytesPerLine);
|
||||||
this._lines -= curr_height;
|
this._lines -= currHeight;
|
||||||
if (this._lines > 0) {
|
if (this._lines > 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2012 Joel Martin
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
|
||||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2012 Joel Martin
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
||||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
|
||||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -94,7 +92,7 @@ export default class TightDecoder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.imageRect(x, y, "image/jpeg", data);
|
display.imageRect(x, y, width, height, "image/jpeg", data);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -162,10 +160,9 @@ export default class TightDecoder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
|
this._zlibs[streamId].setInput(data);
|
||||||
if (data.length != uncompressedSize) {
|
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||||
throw new Error("Incomplete zlib block");
|
this._zlibs[streamId].setInput(null);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display.blitRgbImage(x, y, width, height, data, 0, false);
|
display.blitRgbImage(x, y, width, height, data, 0, false);
|
||||||
|
@ -210,10 +207,9 @@ export default class TightDecoder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
|
this._zlibs[streamId].setInput(data);
|
||||||
if (data.length != uncompressedSize) {
|
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||||
throw new Error("Incomplete zlib block");
|
this._zlibs[streamId].setInput(null);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert indexed (palette based) image data to RGB
|
// Convert indexed (palette based) image data to RGB
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2012 Joel Martin
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
|
||||||
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -18,7 +16,7 @@ export default class TightPNGDecoder extends TightDecoder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.imageRect(x, y, "image/png", data);
|
display.imageRect(x, y, width, height, "image/png", data);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
85
static/js/novnc/core/deflator.js
Executable file
85
static/js/novnc/core/deflator.js
Executable file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* noVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 The noVNC Authors
|
||||||
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
|
*
|
||||||
|
* See README.md for usage and integration instructions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
|
||||||
|
import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
|
||||||
|
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||||
|
|
||||||
|
export default class Deflator {
|
||||||
|
constructor() {
|
||||||
|
this.strm = new ZStream();
|
||||||
|
this.chunkSize = 1024 * 10 * 10;
|
||||||
|
this.outputBuffer = new Uint8Array(this.chunkSize);
|
||||||
|
this.windowBits = 5;
|
||||||
|
|
||||||
|
deflateInit(this.strm, this.windowBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
deflate(inData) {
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
this.strm.input = inData;
|
||||||
|
this.strm.avail_in = this.strm.input.length;
|
||||||
|
this.strm.next_in = 0;
|
||||||
|
this.strm.output = this.outputBuffer;
|
||||||
|
this.strm.avail_out = this.chunkSize;
|
||||||
|
this.strm.next_out = 0;
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
let lastRet = deflate(this.strm, Z_FULL_FLUSH);
|
||||||
|
let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||||
|
|
||||||
|
if (lastRet < 0) {
|
||||||
|
throw new Error("zlib deflate failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.strm.avail_in > 0) {
|
||||||
|
// Read chunks until done
|
||||||
|
|
||||||
|
let chunks = [outData];
|
||||||
|
let totalLen = outData.length;
|
||||||
|
do {
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
this.strm.output = new Uint8Array(this.chunkSize);
|
||||||
|
this.strm.next_out = 0;
|
||||||
|
this.strm.avail_out = this.chunkSize;
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
lastRet = deflate(this.strm, Z_FULL_FLUSH);
|
||||||
|
|
||||||
|
if (lastRet < 0) {
|
||||||
|
throw new Error("zlib deflate failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||||
|
totalLen += chunk.length;
|
||||||
|
chunks.push(chunk);
|
||||||
|
} while (this.strm.avail_in > 0);
|
||||||
|
|
||||||
|
// Combine chunks into a single data
|
||||||
|
|
||||||
|
let newData = new Uint8Array(totalLen);
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < chunks.length; i++) {
|
||||||
|
newData.set(chunks[i], offset);
|
||||||
|
offset += chunks[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
outData = newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
this.strm.input = null;
|
||||||
|
this.strm.avail_in = 0;
|
||||||
|
this.strm.next_in = 0;
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
return outData;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -9,24 +9,24 @@
|
||||||
import * as Log from './util/logging.js';
|
import * as Log from './util/logging.js';
|
||||||
import Base64 from "./base64.js";
|
import Base64 from "./base64.js";
|
||||||
import { supportsImageMetadata } from './util/browser.js';
|
import { supportsImageMetadata } from './util/browser.js';
|
||||||
|
import { toSigned32bit } from './util/int.js';
|
||||||
|
|
||||||
export default class Display {
|
export default class Display {
|
||||||
constructor(target) {
|
constructor(target) {
|
||||||
this._drawCtx = null;
|
this._drawCtx = null;
|
||||||
this._c_forceCanvas = false;
|
|
||||||
|
|
||||||
this._renderQ = []; // queue drawing actions for in-oder rendering
|
this._renderQ = []; // queue drawing actions for in-oder rendering
|
||||||
this._flushing = false;
|
this._flushing = false;
|
||||||
|
|
||||||
// the full frame buffer (logical canvas) size
|
// the full frame buffer (logical canvas) size
|
||||||
this._fb_width = 0;
|
this._fbWidth = 0;
|
||||||
this._fb_height = 0;
|
this._fbHeight = 0;
|
||||||
|
|
||||||
this._prevDrawStyle = "";
|
this._prevDrawStyle = "";
|
||||||
this._tile = null;
|
this._tile = null;
|
||||||
this._tile16x16 = null;
|
this._tile16x16 = null;
|
||||||
this._tile_x = 0;
|
this._tileX = 0;
|
||||||
this._tile_y = 0;
|
this._tileY = 0;
|
||||||
|
|
||||||
Log.Debug(">> Display.constructor");
|
Log.Debug(">> Display.constructor");
|
||||||
|
|
||||||
|
@ -60,8 +60,6 @@ export default class Display {
|
||||||
|
|
||||||
Log.Debug("User Agent: " + navigator.userAgent);
|
Log.Debug("User Agent: " + navigator.userAgent);
|
||||||
|
|
||||||
this.clear();
|
|
||||||
|
|
||||||
// Check canvas features
|
// Check canvas features
|
||||||
if (!('createImageData' in this._drawCtx)) {
|
if (!('createImageData' in this._drawCtx)) {
|
||||||
throw new Error("Canvas does not support createImageData");
|
throw new Error("Canvas does not support createImageData");
|
||||||
|
@ -74,7 +72,6 @@ export default class Display {
|
||||||
|
|
||||||
this._scale = 1.0;
|
this._scale = 1.0;
|
||||||
this._clipViewport = false;
|
this._clipViewport = false;
|
||||||
this.logo = null;
|
|
||||||
|
|
||||||
// ===== EVENT HANDLERS =====
|
// ===== EVENT HANDLERS =====
|
||||||
|
|
||||||
|
@ -98,11 +95,11 @@ export default class Display {
|
||||||
}
|
}
|
||||||
|
|
||||||
get width() {
|
get width() {
|
||||||
return this._fb_width;
|
return this._fbWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
get height() {
|
get height() {
|
||||||
return this._fb_height;
|
return this._fbHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== PUBLIC METHODS =====
|
// ===== PUBLIC METHODS =====
|
||||||
|
@ -125,15 +122,15 @@ export default class Display {
|
||||||
if (deltaX < 0 && vp.x + deltaX < 0) {
|
if (deltaX < 0 && vp.x + deltaX < 0) {
|
||||||
deltaX = -vp.x;
|
deltaX = -vp.x;
|
||||||
}
|
}
|
||||||
if (vx2 + deltaX >= this._fb_width) {
|
if (vx2 + deltaX >= this._fbWidth) {
|
||||||
deltaX -= vx2 + deltaX - this._fb_width + 1;
|
deltaX -= vx2 + deltaX - this._fbWidth + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vp.y + deltaY < 0) {
|
if (vp.y + deltaY < 0) {
|
||||||
deltaY = -vp.y;
|
deltaY = -vp.y;
|
||||||
}
|
}
|
||||||
if (vy2 + deltaY >= this._fb_height) {
|
if (vy2 + deltaY >= this._fbHeight) {
|
||||||
deltaY -= (vy2 + deltaY - this._fb_height + 1);
|
deltaY -= (vy2 + deltaY - this._fbHeight + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deltaX === 0 && deltaY === 0) {
|
if (deltaX === 0 && deltaY === 0) {
|
||||||
|
@ -156,18 +153,18 @@ export default class Display {
|
||||||
typeof(height) === "undefined") {
|
typeof(height) === "undefined") {
|
||||||
|
|
||||||
Log.Debug("Setting viewport to full display region");
|
Log.Debug("Setting viewport to full display region");
|
||||||
width = this._fb_width;
|
width = this._fbWidth;
|
||||||
height = this._fb_height;
|
height = this._fbHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
width = Math.floor(width);
|
width = Math.floor(width);
|
||||||
height = Math.floor(height);
|
height = Math.floor(height);
|
||||||
|
|
||||||
if (width > this._fb_width) {
|
if (width > this._fbWidth) {
|
||||||
width = this._fb_width;
|
width = this._fbWidth;
|
||||||
}
|
}
|
||||||
if (height > this._fb_height) {
|
if (height > this._fbHeight) {
|
||||||
height = this._fb_height;
|
height = this._fbHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vp = this._viewportLoc;
|
const vp = this._viewportLoc;
|
||||||
|
@ -194,21 +191,21 @@ export default class Display {
|
||||||
if (this._scale === 0) {
|
if (this._scale === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return x / this._scale + this._viewportLoc.x;
|
return toSigned32bit(x / this._scale + this._viewportLoc.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
absY(y) {
|
absY(y) {
|
||||||
if (this._scale === 0) {
|
if (this._scale === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return y / this._scale + this._viewportLoc.y;
|
return toSigned32bit(y / this._scale + this._viewportLoc.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
resize(width, height) {
|
resize(width, height) {
|
||||||
this._prevDrawStyle = "";
|
this._prevDrawStyle = "";
|
||||||
|
|
||||||
this._fb_width = width;
|
this._fbWidth = width;
|
||||||
this._fb_height = height;
|
this._fbHeight = height;
|
||||||
|
|
||||||
const canvas = this._backbuffer;
|
const canvas = this._backbuffer;
|
||||||
if (canvas.width !== width || canvas.height !== height) {
|
if (canvas.width !== width || canvas.height !== height) {
|
||||||
|
@ -256,9 +253,9 @@ export default class Display {
|
||||||
|
|
||||||
// Update the visible canvas with the contents of the
|
// Update the visible canvas with the contents of the
|
||||||
// rendering canvas
|
// rendering canvas
|
||||||
flip(from_queue) {
|
flip(fromQueue) {
|
||||||
if (this._renderQ.length !== 0 && !from_queue) {
|
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||||
this._renderQ_push({
|
this._renderQPush({
|
||||||
'type': 'flip'
|
'type': 'flip'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -302,17 +299,6 @@ export default class Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
|
||||||
if (this._logo) {
|
|
||||||
this.resize(this._logo.width, this._logo.height);
|
|
||||||
this.imageRect(0, 0, this._logo.type, this._logo.data);
|
|
||||||
} else {
|
|
||||||
this.resize(240, 20);
|
|
||||||
this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height);
|
|
||||||
}
|
|
||||||
this.flip();
|
|
||||||
}
|
|
||||||
|
|
||||||
pending() {
|
pending() {
|
||||||
return this._renderQ.length > 0;
|
return this._renderQ.length > 0;
|
||||||
}
|
}
|
||||||
|
@ -325,9 +311,9 @@ export default class Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fillRect(x, y, width, height, color, from_queue) {
|
fillRect(x, y, width, height, color, fromQueue) {
|
||||||
if (this._renderQ.length !== 0 && !from_queue) {
|
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||||
this._renderQ_push({
|
this._renderQPush({
|
||||||
'type': 'fill',
|
'type': 'fill',
|
||||||
'x': x,
|
'x': x,
|
||||||
'y': y,
|
'y': y,
|
||||||
|
@ -342,14 +328,14 @@ export default class Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) {
|
copyImage(oldX, oldY, newX, newY, w, h, fromQueue) {
|
||||||
if (this._renderQ.length !== 0 && !from_queue) {
|
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||||
this._renderQ_push({
|
this._renderQPush({
|
||||||
'type': 'copy',
|
'type': 'copy',
|
||||||
'old_x': old_x,
|
'oldX': oldX,
|
||||||
'old_y': old_y,
|
'oldY': oldY,
|
||||||
'x': new_x,
|
'x': newX,
|
||||||
'y': new_y,
|
'y': newY,
|
||||||
'width': w,
|
'width': w,
|
||||||
'height': h,
|
'height': h,
|
||||||
});
|
});
|
||||||
|
@ -367,27 +353,35 @@ export default class Display {
|
||||||
this._drawCtx.imageSmoothingEnabled = false;
|
this._drawCtx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
this._drawCtx.drawImage(this._backbuffer,
|
this._drawCtx.drawImage(this._backbuffer,
|
||||||
old_x, old_y, w, h,
|
oldX, oldY, w, h,
|
||||||
new_x, new_y, w, h);
|
newX, newY, w, h);
|
||||||
this._damage(new_x, new_y, w, h);
|
this._damage(newX, newY, w, h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imageRect(x, y, mime, arr) {
|
imageRect(x, y, width, height, mime, arr) {
|
||||||
|
/* The internal logic cannot handle empty images, so bail early */
|
||||||
|
if ((width === 0) || (height === 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
|
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
|
||||||
this._renderQ_push({
|
|
||||||
|
this._renderQPush({
|
||||||
'type': 'img',
|
'type': 'img',
|
||||||
'img': img,
|
'img': img,
|
||||||
'x': x,
|
'x': x,
|
||||||
'y': y
|
'y': y,
|
||||||
|
'width': width,
|
||||||
|
'height': height
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// start updating a tile
|
// start updating a tile
|
||||||
startTile(x, y, width, height, color) {
|
startTile(x, y, width, height, color) {
|
||||||
this._tile_x = x;
|
this._tileX = x;
|
||||||
this._tile_y = y;
|
this._tileY = y;
|
||||||
if (width === 16 && height === 16) {
|
if (width === 16 && height === 16) {
|
||||||
this._tile = this._tile16x16;
|
this._tile = this._tile16x16;
|
||||||
} else {
|
} else {
|
||||||
|
@ -430,21 +424,21 @@ export default class Display {
|
||||||
|
|
||||||
// draw the current tile to the screen
|
// draw the current tile to the screen
|
||||||
finishTile() {
|
finishTile() {
|
||||||
this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
|
this._drawCtx.putImageData(this._tile, this._tileX, this._tileY);
|
||||||
this._damage(this._tile_x, this._tile_y,
|
this._damage(this._tileX, this._tileY,
|
||||||
this._tile.width, this._tile.height);
|
this._tile.width, this._tile.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
blitImage(x, y, width, height, arr, offset, from_queue) {
|
blitImage(x, y, width, height, arr, offset, fromQueue) {
|
||||||
if (this._renderQ.length !== 0 && !from_queue) {
|
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||||
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
||||||
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
||||||
// this probably isn't getting called *nearly* as much
|
// this probably isn't getting called *nearly* as much
|
||||||
const new_arr = new Uint8Array(width * height * 4);
|
const newArr = new Uint8Array(width * height * 4);
|
||||||
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
|
||||||
this._renderQ_push({
|
this._renderQPush({
|
||||||
'type': 'blit',
|
'type': 'blit',
|
||||||
'data': new_arr,
|
'data': newArr,
|
||||||
'x': x,
|
'x': x,
|
||||||
'y': y,
|
'y': y,
|
||||||
'width': width,
|
'width': width,
|
||||||
|
@ -455,16 +449,16 @@ export default class Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blitRgbImage(x, y, width, height, arr, offset, from_queue) {
|
blitRgbImage(x, y, width, height, arr, offset, fromQueue) {
|
||||||
if (this._renderQ.length !== 0 && !from_queue) {
|
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||||
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
||||||
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
||||||
// this probably isn't getting called *nearly* as much
|
// this probably isn't getting called *nearly* as much
|
||||||
const new_arr = new Uint8Array(width * height * 3);
|
const newArr = new Uint8Array(width * height * 3);
|
||||||
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
|
||||||
this._renderQ_push({
|
this._renderQPush({
|
||||||
'type': 'blitRgb',
|
'type': 'blitRgb',
|
||||||
'data': new_arr,
|
'data': newArr,
|
||||||
'x': x,
|
'x': x,
|
||||||
'y': y,
|
'y': y,
|
||||||
'width': width,
|
'width': width,
|
||||||
|
@ -475,16 +469,16 @@ export default class Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blitRgbxImage(x, y, width, height, arr, offset, from_queue) {
|
blitRgbxImage(x, y, width, height, arr, offset, fromQueue) {
|
||||||
if (this._renderQ.length !== 0 && !from_queue) {
|
if (this._renderQ.length !== 0 && !fromQueue) {
|
||||||
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
||||||
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
||||||
// this probably isn't getting called *nearly* as much
|
// this probably isn't getting called *nearly* as much
|
||||||
const new_arr = new Uint8Array(width * height * 4);
|
const newArr = new Uint8Array(width * height * 4);
|
||||||
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
|
||||||
this._renderQ_push({
|
this._renderQPush({
|
||||||
'type': 'blitRgbx',
|
'type': 'blitRgbx',
|
||||||
'data': new_arr,
|
'data': newArr,
|
||||||
'x': x,
|
'x': x,
|
||||||
'y': y,
|
'y': y,
|
||||||
'width': width,
|
'width': width,
|
||||||
|
@ -589,23 +583,23 @@ export default class Display {
|
||||||
this._damage(x, y, img.width, img.height);
|
this._damage(x, y, img.width, img.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderQ_push(action) {
|
_renderQPush(action) {
|
||||||
this._renderQ.push(action);
|
this._renderQ.push(action);
|
||||||
if (this._renderQ.length === 1) {
|
if (this._renderQ.length === 1) {
|
||||||
// If this can be rendered immediately it will be, otherwise
|
// If this can be rendered immediately it will be, otherwise
|
||||||
// the scanner will wait for the relevant event
|
// the scanner will wait for the relevant event
|
||||||
this._scan_renderQ();
|
this._scanRenderQ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_resume_renderQ() {
|
_resumeRenderQ() {
|
||||||
// "this" is the object that is ready, not the
|
// "this" is the object that is ready, not the
|
||||||
// display object
|
// display object
|
||||||
this.removeEventListener('load', this._noVNC_display._resume_renderQ);
|
this.removeEventListener('load', this._noVNCDisplay._resumeRenderQ);
|
||||||
this._noVNC_display._scan_renderQ();
|
this._noVNCDisplay._scanRenderQ();
|
||||||
}
|
}
|
||||||
|
|
||||||
_scan_renderQ() {
|
_scanRenderQ() {
|
||||||
let ready = true;
|
let ready = true;
|
||||||
while (ready && this._renderQ.length > 0) {
|
while (ready && this._renderQ.length > 0) {
|
||||||
const a = this._renderQ[0];
|
const a = this._renderQ[0];
|
||||||
|
@ -614,7 +608,7 @@ export default class Display {
|
||||||
this.flip(true);
|
this.flip(true);
|
||||||
break;
|
break;
|
||||||
case 'copy':
|
case 'copy':
|
||||||
this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
|
this.copyImage(a.oldX, a.oldY, a.x, a.y, a.width, a.height, true);
|
||||||
break;
|
break;
|
||||||
case 'fill':
|
case 'fill':
|
||||||
this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
|
this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
|
||||||
|
@ -629,11 +623,18 @@ export default class Display {
|
||||||
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||||
break;
|
break;
|
||||||
case 'img':
|
case 'img':
|
||||||
if (a.img.complete) {
|
/* IE tends to set "complete" prematurely, so check dimensions */
|
||||||
|
if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) {
|
||||||
|
if (a.img.width !== a.width || a.img.height !== a.height) {
|
||||||
|
Log.Error("Decoded image has incorrect dimensions. Got " +
|
||||||
|
a.img.width + "x" + a.img.height + ". Expected " +
|
||||||
|
a.width + "x" + a.height + ".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.drawImage(a.img, a.x, a.y);
|
this.drawImage(a.img, a.x, a.y);
|
||||||
} else {
|
} else {
|
||||||
a.img._noVNC_display = this;
|
a.img._noVNCDisplay = this;
|
||||||
a.img.addEventListener('load', this._resume_renderQ);
|
a.img.addEventListener('load', this._resumeRenderQ);
|
||||||
// We need to wait for this image to 'load'
|
// We need to wait for this image to 'load'
|
||||||
// to keep things in-order
|
// to keep things in-order
|
||||||
ready = false;
|
ready = false;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -20,12 +20,15 @@ export const encodings = {
|
||||||
pseudoEncodingLastRect: -224,
|
pseudoEncodingLastRect: -224,
|
||||||
pseudoEncodingCursor: -239,
|
pseudoEncodingCursor: -239,
|
||||||
pseudoEncodingQEMUExtendedKeyEvent: -258,
|
pseudoEncodingQEMUExtendedKeyEvent: -258,
|
||||||
|
pseudoEncodingDesktopName: -307,
|
||||||
pseudoEncodingExtendedDesktopSize: -308,
|
pseudoEncodingExtendedDesktopSize: -308,
|
||||||
pseudoEncodingXvp: -309,
|
pseudoEncodingXvp: -309,
|
||||||
pseudoEncodingFence: -312,
|
pseudoEncodingFence: -312,
|
||||||
pseudoEncodingContinuousUpdates: -313,
|
pseudoEncodingContinuousUpdates: -313,
|
||||||
pseudoEncodingCompressLevel9: -247,
|
pseudoEncodingCompressLevel9: -247,
|
||||||
pseudoEncodingCompressLevel0: -256,
|
pseudoEncodingCompressLevel0: -256,
|
||||||
|
pseudoEncodingVMwareCursor: 0x574d5664,
|
||||||
|
pseudoEncodingExtendedClipboard: 0xc0a1e5ce
|
||||||
};
|
};
|
||||||
|
|
||||||
export function encodingName(num) {
|
export function encodingName(num) {
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
/*
|
||||||
|
* noVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 The noVNC Authors
|
||||||
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
|
*
|
||||||
|
* See README.md for usage and integration instructions.
|
||||||
|
*/
|
||||||
|
|
||||||
import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
|
import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
|
||||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||||
|
|
||||||
|
@ -11,12 +19,22 @@ export default class Inflate {
|
||||||
inflateInit(this.strm, this.windowBits);
|
inflateInit(this.strm, this.windowBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
inflate(data, flush, expected) {
|
setInput(data) {
|
||||||
this.strm.input = data;
|
if (!data) {
|
||||||
this.strm.avail_in = this.strm.input.length;
|
//FIXME: flush remaining data.
|
||||||
this.strm.next_in = 0;
|
/* eslint-disable camelcase */
|
||||||
this.strm.next_out = 0;
|
this.strm.input = null;
|
||||||
|
this.strm.avail_in = 0;
|
||||||
|
this.strm.next_in = 0;
|
||||||
|
} else {
|
||||||
|
this.strm.input = data;
|
||||||
|
this.strm.avail_in = this.strm.input.length;
|
||||||
|
this.strm.next_in = 0;
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inflate(expected) {
|
||||||
// resize our output buffer if it's too small
|
// resize our output buffer if it's too small
|
||||||
// (we could just use multiple chunks, but that would cause an extra
|
// (we could just use multiple chunks, but that would cause an extra
|
||||||
// allocation each time to flatten the chunks)
|
// allocation each time to flatten the chunks)
|
||||||
|
@ -25,9 +43,19 @@ export default class Inflate {
|
||||||
this.strm.output = new Uint8Array(this.chunkSize);
|
this.strm.output = new Uint8Array(this.chunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.strm.avail_out = this.chunkSize;
|
/* eslint-disable camelcase */
|
||||||
|
this.strm.next_out = 0;
|
||||||
|
this.strm.avail_out = expected;
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
inflate(this.strm, flush);
|
let ret = inflate(this.strm, 0); // Flush argument not used.
|
||||||
|
if (ret < 0) {
|
||||||
|
throw new Error("zlib inflate failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.strm.next_out != expected) {
|
||||||
|
throw new Error("Incomplete zlib block");
|
||||||
|
}
|
||||||
|
|
||||||
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,12 +43,10 @@ addStandard("CapsLock", KeyTable.XK_Caps_Lock);
|
||||||
addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R);
|
addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R);
|
||||||
// - Fn
|
// - Fn
|
||||||
// - FnLock
|
// - FnLock
|
||||||
addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
|
||||||
addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||||
addStandard("NumLock", KeyTable.XK_Num_Lock);
|
addStandard("NumLock", KeyTable.XK_Num_Lock);
|
||||||
addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
|
addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
|
||||||
addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
|
addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
|
||||||
addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
|
||||||
// - Symbol
|
// - Symbol
|
||||||
// - SymbolLock
|
// - SymbolLock
|
||||||
|
|
||||||
|
@ -72,6 +70,9 @@ addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
|
||||||
// 2.5. Editing Keys
|
// 2.5. Editing Keys
|
||||||
|
|
||||||
addStandard("Backspace", KeyTable.XK_BackSpace);
|
addStandard("Backspace", KeyTable.XK_BackSpace);
|
||||||
|
// Browsers send "Clear" for the numpad 5 without NumLock because
|
||||||
|
// Windows uses VK_Clear for that key. But Unix expects KP_Begin for
|
||||||
|
// that scenario.
|
||||||
addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin);
|
addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin);
|
||||||
addStandard("Copy", KeyTable.XF86XK_Copy);
|
addStandard("Copy", KeyTable.XF86XK_Copy);
|
||||||
// - CrSel
|
// - CrSel
|
||||||
|
@ -194,7 +195,8 @@ addStandard("F35", KeyTable.XK_F35);
|
||||||
addStandard("Close", KeyTable.XF86XK_Close);
|
addStandard("Close", KeyTable.XF86XK_Close);
|
||||||
addStandard("MailForward", KeyTable.XF86XK_MailForward);
|
addStandard("MailForward", KeyTable.XF86XK_MailForward);
|
||||||
addStandard("MailReply", KeyTable.XF86XK_Reply);
|
addStandard("MailReply", KeyTable.XF86XK_Reply);
|
||||||
addStandard("MainSend", KeyTable.XF86XK_Send);
|
addStandard("MailSend", KeyTable.XF86XK_Send);
|
||||||
|
// - MediaClose
|
||||||
addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
|
addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
|
||||||
addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
|
addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
|
||||||
addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
|
addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
|
||||||
|
@ -218,11 +220,9 @@ addStandard("SpellCheck", KeyTable.XF86XK_Spell);
|
||||||
|
|
||||||
// - AudioBalanceLeft
|
// - AudioBalanceLeft
|
||||||
// - AudioBalanceRight
|
// - AudioBalanceRight
|
||||||
// - AudioBassDown
|
|
||||||
// - AudioBassBoostDown
|
// - AudioBassBoostDown
|
||||||
// - AudioBassBoostToggle
|
// - AudioBassBoostToggle
|
||||||
// - AudioBassBoostUp
|
// - AudioBassBoostUp
|
||||||
// - AudioBassUp
|
|
||||||
// - AudioFaderFront
|
// - AudioFaderFront
|
||||||
// - AudioFaderRear
|
// - AudioFaderRear
|
||||||
// - AudioSurroundModeNext
|
// - AudioSurroundModeNext
|
||||||
|
@ -243,12 +243,12 @@ addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
|
||||||
|
|
||||||
// 2.14. Application Keys
|
// 2.14. Application Keys
|
||||||
|
|
||||||
addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator);
|
addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer);
|
||||||
|
addStandard("LaunchApplication2", KeyTable.XF86XK_Calculator);
|
||||||
addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
|
addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
|
||||||
addStandard("LaunchMail", KeyTable.XF86XK_Mail);
|
addStandard("LaunchMail", KeyTable.XF86XK_Mail);
|
||||||
addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
|
addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
|
||||||
addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
|
addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
|
||||||
addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer);
|
|
||||||
addStandard("LaunchPhone", KeyTable.XF86XK_Phone);
|
addStandard("LaunchPhone", KeyTable.XF86XK_Phone);
|
||||||
addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver);
|
addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver);
|
||||||
addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel);
|
addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel);
|
||||||
|
|
567
static/js/novnc/core/input/gesturehandler.js
Executable file
567
static/js/novnc/core/input/gesturehandler.js
Executable file
|
@ -0,0 +1,567 @@
|
||||||
|
/*
|
||||||
|
* noVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 The noVNC Authors
|
||||||
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
|
*
|
||||||
|
* See README.md for usage and integration instructions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const GH_NOGESTURE = 0;
|
||||||
|
const GH_ONETAP = 1;
|
||||||
|
const GH_TWOTAP = 2;
|
||||||
|
const GH_THREETAP = 4;
|
||||||
|
const GH_DRAG = 8;
|
||||||
|
const GH_LONGPRESS = 16;
|
||||||
|
const GH_TWODRAG = 32;
|
||||||
|
const GH_PINCH = 64;
|
||||||
|
|
||||||
|
const GH_INITSTATE = 127;
|
||||||
|
|
||||||
|
const GH_MOVE_THRESHOLD = 50;
|
||||||
|
const GH_ANGLE_THRESHOLD = 90; // Degrees
|
||||||
|
|
||||||
|
// Timeout when waiting for gestures (ms)
|
||||||
|
const GH_MULTITOUCH_TIMEOUT = 250;
|
||||||
|
|
||||||
|
// Maximum time between press and release for a tap (ms)
|
||||||
|
const GH_TAP_TIMEOUT = 1000;
|
||||||
|
|
||||||
|
// Timeout when waiting for longpress (ms)
|
||||||
|
const GH_LONGPRESS_TIMEOUT = 1000;
|
||||||
|
|
||||||
|
// Timeout when waiting to decide between PINCH and TWODRAG (ms)
|
||||||
|
const GH_TWOTOUCH_TIMEOUT = 50;
|
||||||
|
|
||||||
|
export default class GestureHandler {
|
||||||
|
constructor() {
|
||||||
|
this._target = null;
|
||||||
|
|
||||||
|
this._state = GH_INITSTATE;
|
||||||
|
|
||||||
|
this._tracked = [];
|
||||||
|
this._ignored = [];
|
||||||
|
|
||||||
|
this._waitingRelease = false;
|
||||||
|
this._releaseStart = 0.0;
|
||||||
|
|
||||||
|
this._longpressTimeoutId = null;
|
||||||
|
this._twoTouchTimeoutId = null;
|
||||||
|
|
||||||
|
this._boundEventHandler = this._eventHandler.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
attach(target) {
|
||||||
|
this.detach();
|
||||||
|
|
||||||
|
this._target = target;
|
||||||
|
this._target.addEventListener('touchstart',
|
||||||
|
this._boundEventHandler);
|
||||||
|
this._target.addEventListener('touchmove',
|
||||||
|
this._boundEventHandler);
|
||||||
|
this._target.addEventListener('touchend',
|
||||||
|
this._boundEventHandler);
|
||||||
|
this._target.addEventListener('touchcancel',
|
||||||
|
this._boundEventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
detach() {
|
||||||
|
if (!this._target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._stopLongpressTimeout();
|
||||||
|
this._stopTwoTouchTimeout();
|
||||||
|
|
||||||
|
this._target.removeEventListener('touchstart',
|
||||||
|
this._boundEventHandler);
|
||||||
|
this._target.removeEventListener('touchmove',
|
||||||
|
this._boundEventHandler);
|
||||||
|
this._target.removeEventListener('touchend',
|
||||||
|
this._boundEventHandler);
|
||||||
|
this._target.removeEventListener('touchcancel',
|
||||||
|
this._boundEventHandler);
|
||||||
|
this._target = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventHandler(e) {
|
||||||
|
let fn;
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
switch (e.type) {
|
||||||
|
case 'touchstart':
|
||||||
|
fn = this._touchStart;
|
||||||
|
break;
|
||||||
|
case 'touchmove':
|
||||||
|
fn = this._touchMove;
|
||||||
|
break;
|
||||||
|
case 'touchend':
|
||||||
|
case 'touchcancel':
|
||||||
|
fn = this._touchEnd;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < e.changedTouches.length; i++) {
|
||||||
|
let touch = e.changedTouches[i];
|
||||||
|
fn.call(this, touch.identifier, touch.clientX, touch.clientY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_touchStart(id, x, y) {
|
||||||
|
// Ignore any new touches if there is already an active gesture,
|
||||||
|
// or we're in a cleanup state
|
||||||
|
if (this._hasDetectedGesture() || (this._state === GH_NOGESTURE)) {
|
||||||
|
this._ignored.push(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did it take too long between touches that we should no longer
|
||||||
|
// consider this a single gesture?
|
||||||
|
if ((this._tracked.length > 0) &&
|
||||||
|
((Date.now() - this._tracked[0].started) > GH_MULTITOUCH_TIMEOUT)) {
|
||||||
|
this._state = GH_NOGESTURE;
|
||||||
|
this._ignored.push(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're waiting for fingers to release then we should no longer
|
||||||
|
// recognize new touches
|
||||||
|
if (this._waitingRelease) {
|
||||||
|
this._state = GH_NOGESTURE;
|
||||||
|
this._ignored.push(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tracked.push({
|
||||||
|
id: id,
|
||||||
|
started: Date.now(),
|
||||||
|
active: true,
|
||||||
|
firstX: x,
|
||||||
|
firstY: y,
|
||||||
|
lastX: x,
|
||||||
|
lastY: y,
|
||||||
|
angle: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (this._tracked.length) {
|
||||||
|
case 1:
|
||||||
|
this._startLongpressTimeout();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
this._state &= ~(GH_ONETAP | GH_DRAG | GH_LONGPRESS);
|
||||||
|
this._stopLongpressTimeout();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
this._state &= ~(GH_TWOTAP | GH_TWODRAG | GH_PINCH);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this._state = GH_NOGESTURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_touchMove(id, x, y) {
|
||||||
|
let touch = this._tracked.find(t => t.id === id);
|
||||||
|
|
||||||
|
// If this is an update for a touch we're not tracking, ignore it
|
||||||
|
if (touch === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the touches last position with the event coordinates
|
||||||
|
touch.lastX = x;
|
||||||
|
touch.lastY = y;
|
||||||
|
|
||||||
|
let deltaX = x - touch.firstX;
|
||||||
|
let deltaY = y - touch.firstY;
|
||||||
|
|
||||||
|
// Update angle when the touch has moved
|
||||||
|
if ((touch.firstX !== touch.lastX) ||
|
||||||
|
(touch.firstY !== touch.lastY)) {
|
||||||
|
touch.angle = Math.atan2(deltaY, deltaX) * 180 / Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._hasDetectedGesture()) {
|
||||||
|
// Ignore moves smaller than the minimum threshold
|
||||||
|
if (Math.hypot(deltaX, deltaY) < GH_MOVE_THRESHOLD) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't be a tap or long press as we've seen movement
|
||||||
|
this._state &= ~(GH_ONETAP | GH_TWOTAP | GH_THREETAP | GH_LONGPRESS);
|
||||||
|
this._stopLongpressTimeout();
|
||||||
|
|
||||||
|
if (this._tracked.length !== 1) {
|
||||||
|
this._state &= ~(GH_DRAG);
|
||||||
|
}
|
||||||
|
if (this._tracked.length !== 2) {
|
||||||
|
this._state &= ~(GH_TWODRAG | GH_PINCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to figure out which of our different two touch gestures
|
||||||
|
// this might be
|
||||||
|
if (this._tracked.length === 2) {
|
||||||
|
|
||||||
|
// The other touch is the one where the id doesn't match
|
||||||
|
let prevTouch = this._tracked.find(t => t.id !== id);
|
||||||
|
|
||||||
|
// How far the previous touch point has moved since start
|
||||||
|
let prevDeltaMove = Math.hypot(prevTouch.firstX - prevTouch.lastX,
|
||||||
|
prevTouch.firstY - prevTouch.lastY);
|
||||||
|
|
||||||
|
// We know that the current touch moved far enough,
|
||||||
|
// but unless both touches moved further than their
|
||||||
|
// threshold we don't want to disqualify any gestures
|
||||||
|
if (prevDeltaMove > GH_MOVE_THRESHOLD) {
|
||||||
|
|
||||||
|
// The angle difference between the direction of the touch points
|
||||||
|
let deltaAngle = Math.abs(touch.angle - prevTouch.angle);
|
||||||
|
deltaAngle = Math.abs(((deltaAngle + 180) % 360) - 180);
|
||||||
|
|
||||||
|
// PINCH or TWODRAG can be eliminated depending on the angle
|
||||||
|
if (deltaAngle > GH_ANGLE_THRESHOLD) {
|
||||||
|
this._state &= ~GH_TWODRAG;
|
||||||
|
} else {
|
||||||
|
this._state &= ~GH_PINCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._isTwoTouchTimeoutRunning()) {
|
||||||
|
this._stopTwoTouchTimeout();
|
||||||
|
}
|
||||||
|
} else if (!this._isTwoTouchTimeoutRunning()) {
|
||||||
|
// We can't determine the gesture right now, let's
|
||||||
|
// wait and see if more events are on their way
|
||||||
|
this._startTwoTouchTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._hasDetectedGesture()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._pushEvent('gesturestart');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._pushEvent('gesturemove');
|
||||||
|
}
|
||||||
|
|
||||||
|
_touchEnd(id, x, y) {
|
||||||
|
// Check if this is an ignored touch
|
||||||
|
if (this._ignored.indexOf(id) !== -1) {
|
||||||
|
// Remove this touch from ignored
|
||||||
|
this._ignored.splice(this._ignored.indexOf(id), 1);
|
||||||
|
|
||||||
|
// And reset the state if there are no more touches
|
||||||
|
if ((this._ignored.length === 0) &&
|
||||||
|
(this._tracked.length === 0)) {
|
||||||
|
this._state = GH_INITSTATE;
|
||||||
|
this._waitingRelease = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got a touchend before the timer triggered,
|
||||||
|
// this cannot result in a gesture anymore.
|
||||||
|
if (!this._hasDetectedGesture() &&
|
||||||
|
this._isTwoTouchTimeoutRunning()) {
|
||||||
|
this._stopTwoTouchTimeout();
|
||||||
|
this._state = GH_NOGESTURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some gestures don't trigger until a touch is released
|
||||||
|
if (!this._hasDetectedGesture()) {
|
||||||
|
// Can't be a gesture that relies on movement
|
||||||
|
this._state &= ~(GH_DRAG | GH_TWODRAG | GH_PINCH);
|
||||||
|
// Or something that relies on more time
|
||||||
|
this._state &= ~GH_LONGPRESS;
|
||||||
|
this._stopLongpressTimeout();
|
||||||
|
|
||||||
|
if (!this._waitingRelease) {
|
||||||
|
this._releaseStart = Date.now();
|
||||||
|
this._waitingRelease = true;
|
||||||
|
|
||||||
|
// Can't be a tap that requires more touches than we current have
|
||||||
|
switch (this._tracked.length) {
|
||||||
|
case 1:
|
||||||
|
this._state &= ~(GH_TWOTAP | GH_THREETAP);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
this._state &= ~(GH_ONETAP | GH_THREETAP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waiting for all touches to release? (i.e. some tap)
|
||||||
|
if (this._waitingRelease) {
|
||||||
|
// Were all touches released at roughly the same time?
|
||||||
|
if ((Date.now() - this._releaseStart) > GH_MULTITOUCH_TIMEOUT) {
|
||||||
|
this._state = GH_NOGESTURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did too long time pass between press and release?
|
||||||
|
if (this._tracked.some(t => (Date.now() - t.started) > GH_TAP_TIMEOUT)) {
|
||||||
|
this._state = GH_NOGESTURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
let touch = this._tracked.find(t => t.id === id);
|
||||||
|
touch.active = false;
|
||||||
|
|
||||||
|
// Are we still waiting for more releases?
|
||||||
|
if (this._hasDetectedGesture()) {
|
||||||
|
this._pushEvent('gesturestart');
|
||||||
|
} else {
|
||||||
|
// Have we reached a dead end?
|
||||||
|
if (this._state !== GH_NOGESTURE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._hasDetectedGesture()) {
|
||||||
|
this._pushEvent('gestureend');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore any remaining touches until they are ended
|
||||||
|
for (let i = 0; i < this._tracked.length; i++) {
|
||||||
|
if (this._tracked[i].active) {
|
||||||
|
this._ignored.push(this._tracked[i].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._tracked = [];
|
||||||
|
|
||||||
|
this._state = GH_NOGESTURE;
|
||||||
|
|
||||||
|
// Remove this touch from ignored if it's in there
|
||||||
|
if (this._ignored.indexOf(id) !== -1) {
|
||||||
|
this._ignored.splice(this._ignored.indexOf(id), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We reset the state if ignored is empty
|
||||||
|
if ((this._ignored.length === 0)) {
|
||||||
|
this._state = GH_INITSTATE;
|
||||||
|
this._waitingRelease = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasDetectedGesture() {
|
||||||
|
if (this._state === GH_NOGESTURE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check to see if the bitmask value is a power of 2
|
||||||
|
// (i.e. only one bit set). If it is, we have a state.
|
||||||
|
if (this._state & (this._state - 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For taps we also need to have all touches released
|
||||||
|
// before we've fully detected the gesture
|
||||||
|
if (this._state & (GH_ONETAP | GH_TWOTAP | GH_THREETAP)) {
|
||||||
|
if (this._tracked.some(t => t.active)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_startLongpressTimeout() {
|
||||||
|
this._stopLongpressTimeout();
|
||||||
|
this._longpressTimeoutId = setTimeout(() => this._longpressTimeout(),
|
||||||
|
GH_LONGPRESS_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
_stopLongpressTimeout() {
|
||||||
|
clearTimeout(this._longpressTimeoutId);
|
||||||
|
this._longpressTimeoutId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_longpressTimeout() {
|
||||||
|
if (this._hasDetectedGesture()) {
|
||||||
|
throw new Error("A longpress gesture failed, conflict with a different gesture");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._state = GH_LONGPRESS;
|
||||||
|
this._pushEvent('gesturestart');
|
||||||
|
}
|
||||||
|
|
||||||
|
_startTwoTouchTimeout() {
|
||||||
|
this._stopTwoTouchTimeout();
|
||||||
|
this._twoTouchTimeoutId = setTimeout(() => this._twoTouchTimeout(),
|
||||||
|
GH_TWOTOUCH_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
_stopTwoTouchTimeout() {
|
||||||
|
clearTimeout(this._twoTouchTimeoutId);
|
||||||
|
this._twoTouchTimeoutId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isTwoTouchTimeoutRunning() {
|
||||||
|
return this._twoTouchTimeoutId !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_twoTouchTimeout() {
|
||||||
|
if (this._tracked.length === 0) {
|
||||||
|
throw new Error("A pinch or two drag gesture failed, no tracked touches");
|
||||||
|
}
|
||||||
|
|
||||||
|
// How far each touch point has moved since start
|
||||||
|
let avgM = this._getAverageMovement();
|
||||||
|
let avgMoveH = Math.abs(avgM.x);
|
||||||
|
let avgMoveV = Math.abs(avgM.y);
|
||||||
|
|
||||||
|
// The difference in the distance between where
|
||||||
|
// the touch points started and where they are now
|
||||||
|
let avgD = this._getAverageDistance();
|
||||||
|
let deltaTouchDistance = Math.abs(Math.hypot(avgD.first.x, avgD.first.y) -
|
||||||
|
Math.hypot(avgD.last.x, avgD.last.y));
|
||||||
|
|
||||||
|
if ((avgMoveV < deltaTouchDistance) &&
|
||||||
|
(avgMoveH < deltaTouchDistance)) {
|
||||||
|
this._state = GH_PINCH;
|
||||||
|
} else {
|
||||||
|
this._state = GH_TWODRAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._pushEvent('gesturestart');
|
||||||
|
this._pushEvent('gesturemove');
|
||||||
|
}
|
||||||
|
|
||||||
|
_pushEvent(type) {
|
||||||
|
let detail = { type: this._stateToGesture(this._state) };
|
||||||
|
|
||||||
|
// For most gesture events the current (average) position is the
|
||||||
|
// most useful
|
||||||
|
let avg = this._getPosition();
|
||||||
|
let pos = avg.last;
|
||||||
|
|
||||||
|
// However we have a slight distance to detect gestures, so for the
|
||||||
|
// first gesture event we want to use the first positions we saw
|
||||||
|
if (type === 'gesturestart') {
|
||||||
|
pos = avg.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For these gestures, we always want the event coordinates
|
||||||
|
// to be where the gesture began, not the current touch location.
|
||||||
|
switch (this._state) {
|
||||||
|
case GH_TWODRAG:
|
||||||
|
case GH_PINCH:
|
||||||
|
pos = avg.first;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
detail['clientX'] = pos.x;
|
||||||
|
detail['clientY'] = pos.y;
|
||||||
|
|
||||||
|
// FIXME: other coordinates?
|
||||||
|
|
||||||
|
// Some gestures also have a magnitude
|
||||||
|
if (this._state === GH_PINCH) {
|
||||||
|
let distance = this._getAverageDistance();
|
||||||
|
if (type === 'gesturestart') {
|
||||||
|
detail['magnitudeX'] = distance.first.x;
|
||||||
|
detail['magnitudeY'] = distance.first.y;
|
||||||
|
} else {
|
||||||
|
detail['magnitudeX'] = distance.last.x;
|
||||||
|
detail['magnitudeY'] = distance.last.y;
|
||||||
|
}
|
||||||
|
} else if (this._state === GH_TWODRAG) {
|
||||||
|
if (type === 'gesturestart') {
|
||||||
|
detail['magnitudeX'] = 0.0;
|
||||||
|
detail['magnitudeY'] = 0.0;
|
||||||
|
} else {
|
||||||
|
let movement = this._getAverageMovement();
|
||||||
|
detail['magnitudeX'] = movement.x;
|
||||||
|
detail['magnitudeY'] = movement.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let gev = new CustomEvent(type, { detail: detail });
|
||||||
|
this._target.dispatchEvent(gev);
|
||||||
|
}
|
||||||
|
|
||||||
|
_stateToGesture(state) {
|
||||||
|
switch (state) {
|
||||||
|
case GH_ONETAP:
|
||||||
|
return 'onetap';
|
||||||
|
case GH_TWOTAP:
|
||||||
|
return 'twotap';
|
||||||
|
case GH_THREETAP:
|
||||||
|
return 'threetap';
|
||||||
|
case GH_DRAG:
|
||||||
|
return 'drag';
|
||||||
|
case GH_LONGPRESS:
|
||||||
|
return 'longpress';
|
||||||
|
case GH_TWODRAG:
|
||||||
|
return 'twodrag';
|
||||||
|
case GH_PINCH:
|
||||||
|
return 'pinch';
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown gesture state: " + state);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getPosition() {
|
||||||
|
if (this._tracked.length === 0) {
|
||||||
|
throw new Error("Failed to get gesture position, no tracked touches");
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = this._tracked.length;
|
||||||
|
let fx = 0, fy = 0, lx = 0, ly = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < this._tracked.length; i++) {
|
||||||
|
fx += this._tracked[i].firstX;
|
||||||
|
fy += this._tracked[i].firstY;
|
||||||
|
lx += this._tracked[i].lastX;
|
||||||
|
ly += this._tracked[i].lastY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { first: { x: fx / size,
|
||||||
|
y: fy / size },
|
||||||
|
last: { x: lx / size,
|
||||||
|
y: ly / size } };
|
||||||
|
}
|
||||||
|
|
||||||
|
_getAverageMovement() {
|
||||||
|
if (this._tracked.length === 0) {
|
||||||
|
throw new Error("Failed to get gesture movement, no tracked touches");
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalH, totalV;
|
||||||
|
totalH = totalV = 0;
|
||||||
|
let size = this._tracked.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < this._tracked.length; i++) {
|
||||||
|
totalH += this._tracked[i].lastX - this._tracked[i].firstX;
|
||||||
|
totalV += this._tracked[i].lastY - this._tracked[i].firstY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x: totalH / size,
|
||||||
|
y: totalV / size };
|
||||||
|
}
|
||||||
|
|
||||||
|
_getAverageDistance() {
|
||||||
|
if (this._tracked.length === 0) {
|
||||||
|
throw new Error("Failed to get gesture distance, no tracked touches");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance between the first and last tracked touches
|
||||||
|
|
||||||
|
let first = this._tracked[0];
|
||||||
|
let last = this._tracked[this._tracked.length - 1];
|
||||||
|
|
||||||
|
let fdx = Math.abs(last.firstX - first.firstX);
|
||||||
|
let fdy = Math.abs(last.firstY - first.firstY);
|
||||||
|
|
||||||
|
let ldx = Math.abs(last.lastX - first.lastX);
|
||||||
|
let ldy = Math.abs(last.lastY - first.lastY);
|
||||||
|
|
||||||
|
return { first: { x: fdx, y: fdy },
|
||||||
|
last: { x: ldx, y: ldy } };
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -118,9 +118,7 @@ export default class Keyboard {
|
||||||
|
|
||||||
// We cannot handle keys we cannot track, but we also need
|
// We cannot handle keys we cannot track, but we also need
|
||||||
// to deal with virtual keyboards which omit key info
|
// to deal with virtual keyboards which omit key info
|
||||||
// (iOS omits tracking info on keyup events, which forces us to
|
if (code === 'Unidentified') {
|
||||||
// special treat that platform here)
|
|
||||||
if ((code === 'Unidentified') || browser.isIOS()) {
|
|
||||||
if (keysym) {
|
if (keysym) {
|
||||||
// If it's a virtual keyboard then it should be
|
// If it's a virtual keyboard then it should be
|
||||||
// sufficient to just send press and release right
|
// sufficient to just send press and release right
|
||||||
|
@ -137,7 +135,7 @@ export default class Keyboard {
|
||||||
// keys around a bit to make things more sane for the remote
|
// keys around a bit to make things more sane for the remote
|
||||||
// server. This method is used by RealVNC and TigerVNC (and
|
// server. This method is used by RealVNC and TigerVNC (and
|
||||||
// possibly others).
|
// possibly others).
|
||||||
if (browser.isMac()) {
|
if (browser.isMac() || browser.isIOS()) {
|
||||||
switch (keysym) {
|
switch (keysym) {
|
||||||
case KeyTable.XK_Super_L:
|
case KeyTable.XK_Super_L:
|
||||||
keysym = KeyTable.XK_Alt_L;
|
keysym = KeyTable.XK_Alt_L;
|
||||||
|
@ -164,7 +162,7 @@ export default class Keyboard {
|
||||||
// state change events. That gets extra confusing for CapsLock
|
// state change events. That gets extra confusing for CapsLock
|
||||||
// which toggles on each press, but not on release. So pretend
|
// which toggles on each press, but not on release. So pretend
|
||||||
// it was a quick press and release of the button.
|
// it was a quick press and release of the button.
|
||||||
if (browser.isMac() && (code === 'CapsLock')) {
|
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
||||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
|
@ -276,13 +274,28 @@ export default class Keyboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
// See comment in _handleKeyDown()
|
// See comment in _handleKeyDown()
|
||||||
if (browser.isMac() && (code === 'CapsLock')) {
|
if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
|
||||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sendKeyEvent(this._keyDownList[code], code, false);
|
this._sendKeyEvent(this._keyDownList[code], code, false);
|
||||||
|
|
||||||
|
// Windows has a rather nasty bug where it won't send key
|
||||||
|
// release events for a Shift button if the other Shift is still
|
||||||
|
// pressed
|
||||||
|
if (browser.isWindows() && ((code === 'ShiftLeft') ||
|
||||||
|
(code === 'ShiftRight'))) {
|
||||||
|
if ('ShiftRight' in this._keyDownList) {
|
||||||
|
this._sendKeyEvent(this._keyDownList['ShiftRight'],
|
||||||
|
'ShiftRight', false);
|
||||||
|
}
|
||||||
|
if ('ShiftLeft' in this._keyDownList) {
|
||||||
|
this._sendKeyEvent(this._keyDownList['ShiftLeft'],
|
||||||
|
'ShiftLeft', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleAltGrTimeout() {
|
_handleAltGrTimeout() {
|
||||||
|
@ -299,8 +312,11 @@ export default class Keyboard {
|
||||||
Log.Debug("<< Keyboard.allKeysUp");
|
Log.Debug("<< Keyboard.allKeysUp");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Firefox Alt workaround, see below
|
// Alt workaround for Firefox on Windows, see below
|
||||||
_checkAlt(e) {
|
_checkAlt(e) {
|
||||||
|
if (e.skipCheckAlt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (e.altKey) {
|
if (e.altKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -315,6 +331,7 @@ export default class Keyboard {
|
||||||
const event = new KeyboardEvent('keyup',
|
const event = new KeyboardEvent('keyup',
|
||||||
{ key: downList[code],
|
{ key: downList[code],
|
||||||
code: code });
|
code: code });
|
||||||
|
event.skipCheckAlt = true;
|
||||||
target.dispatchEvent(event);
|
target.dispatchEvent(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -331,9 +348,10 @@ export default class Keyboard {
|
||||||
// Release (key up) if window loses focus
|
// Release (key up) if window loses focus
|
||||||
window.addEventListener('blur', this._eventHandlers.blur);
|
window.addEventListener('blur', this._eventHandlers.blur);
|
||||||
|
|
||||||
// Firefox has broken handling of Alt, so we need to poll as
|
// Firefox on Windows has broken handling of Alt, so we need to
|
||||||
// best we can for releases (still doesn't prevent the menu
|
// poll as best we can for releases (still doesn't prevent the
|
||||||
// from popping up though as we can't call preventDefault())
|
// menu from popping up though as we can't call
|
||||||
|
// preventDefault())
|
||||||
if (browser.isWindows() && browser.isFirefox()) {
|
if (browser.isWindows() && browser.isFirefox()) {
|
||||||
const handler = this._eventHandlers.checkalt;
|
const handler = this._eventHandlers.checkalt;
|
||||||
['mousedown', 'mouseup', 'mousemove', 'wheel',
|
['mousedown', 'mouseup', 'mousemove', 'wheel',
|
||||||
|
|
|
@ -1,276 +0,0 @@
|
||||||
/*
|
|
||||||
* noVNC: HTML5 VNC client
|
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
|
||||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as Log from '../util/logging.js';
|
|
||||||
import { isTouchDevice } from '../util/browser.js';
|
|
||||||
import { setCapture, stopEvent, getPointerEvent } from '../util/events.js';
|
|
||||||
|
|
||||||
const WHEEL_STEP = 10; // Delta threshold for a mouse wheel step
|
|
||||||
const WHEEL_STEP_TIMEOUT = 50; // ms
|
|
||||||
const WHEEL_LINE_HEIGHT = 19;
|
|
||||||
|
|
||||||
export default class Mouse {
|
|
||||||
constructor(target) {
|
|
||||||
this._target = target || document;
|
|
||||||
|
|
||||||
this._doubleClickTimer = null;
|
|
||||||
this._lastTouchPos = null;
|
|
||||||
|
|
||||||
this._pos = null;
|
|
||||||
this._wheelStepXTimer = null;
|
|
||||||
this._wheelStepYTimer = null;
|
|
||||||
this._accumulatedWheelDeltaX = 0;
|
|
||||||
this._accumulatedWheelDeltaY = 0;
|
|
||||||
|
|
||||||
this._eventHandlers = {
|
|
||||||
'mousedown': this._handleMouseDown.bind(this),
|
|
||||||
'mouseup': this._handleMouseUp.bind(this),
|
|
||||||
'mousemove': this._handleMouseMove.bind(this),
|
|
||||||
'mousewheel': this._handleMouseWheel.bind(this),
|
|
||||||
'mousedisable': this._handleMouseDisable.bind(this)
|
|
||||||
};
|
|
||||||
|
|
||||||
// ===== PROPERTIES =====
|
|
||||||
|
|
||||||
this.touchButton = 1; // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
|
|
||||||
|
|
||||||
// ===== EVENT HANDLERS =====
|
|
||||||
|
|
||||||
this.onmousebutton = () => {}; // Handler for mouse button click/release
|
|
||||||
this.onmousemove = () => {}; // Handler for mouse movement
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== PRIVATE METHODS =====
|
|
||||||
|
|
||||||
_resetDoubleClickTimer() {
|
|
||||||
this._doubleClickTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleMouseButton(e, down) {
|
|
||||||
this._updateMousePosition(e);
|
|
||||||
let pos = this._pos;
|
|
||||||
|
|
||||||
let bmask;
|
|
||||||
if (e.touches || e.changedTouches) {
|
|
||||||
// Touch device
|
|
||||||
|
|
||||||
// When two touches occur within 500 ms of each other and are
|
|
||||||
// close enough together a double click is triggered.
|
|
||||||
if (down == 1) {
|
|
||||||
if (this._doubleClickTimer === null) {
|
|
||||||
this._lastTouchPos = pos;
|
|
||||||
} else {
|
|
||||||
clearTimeout(this._doubleClickTimer);
|
|
||||||
|
|
||||||
// When the distance between the two touches is small enough
|
|
||||||
// force the position of the latter touch to the position of
|
|
||||||
// the first.
|
|
||||||
|
|
||||||
const xs = this._lastTouchPos.x - pos.x;
|
|
||||||
const ys = this._lastTouchPos.y - pos.y;
|
|
||||||
const d = Math.sqrt((xs * xs) + (ys * ys));
|
|
||||||
|
|
||||||
// The goal is to trigger on a certain physical width, the
|
|
||||||
// devicePixelRatio brings us a bit closer but is not optimal.
|
|
||||||
const threshold = 20 * (window.devicePixelRatio || 1);
|
|
||||||
if (d < threshold) {
|
|
||||||
pos = this._lastTouchPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
|
|
||||||
}
|
|
||||||
bmask = this.touchButton;
|
|
||||||
// If bmask is set
|
|
||||||
} else if (e.which) {
|
|
||||||
/* everything except IE */
|
|
||||||
bmask = 1 << e.button;
|
|
||||||
} else {
|
|
||||||
/* IE including 9 */
|
|
||||||
bmask = (e.button & 0x1) + // Left
|
|
||||||
(e.button & 0x2) * 2 + // Right
|
|
||||||
(e.button & 0x4) / 2; // Middle
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Debug("onmousebutton " + (down ? "down" : "up") +
|
|
||||||
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
|
|
||||||
this.onmousebutton(pos.x, pos.y, down, bmask);
|
|
||||||
|
|
||||||
stopEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleMouseDown(e) {
|
|
||||||
// Touch events have implicit capture
|
|
||||||
if (e.type === "mousedown") {
|
|
||||||
setCapture(this._target);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handleMouseButton(e, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleMouseUp(e) {
|
|
||||||
this._handleMouseButton(e, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mouse wheel events are sent in steps over VNC. This means that the VNC
|
|
||||||
// protocol can't handle a wheel event with specific distance or speed.
|
|
||||||
// Therefor, if we get a lot of small mouse wheel events we combine them.
|
|
||||||
_generateWheelStepX() {
|
|
||||||
|
|
||||||
if (this._accumulatedWheelDeltaX < 0) {
|
|
||||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
|
|
||||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5);
|
|
||||||
} else if (this._accumulatedWheelDeltaX > 0) {
|
|
||||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6);
|
|
||||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._accumulatedWheelDeltaX = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_generateWheelStepY() {
|
|
||||||
|
|
||||||
if (this._accumulatedWheelDeltaY < 0) {
|
|
||||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
|
|
||||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3);
|
|
||||||
} else if (this._accumulatedWheelDeltaY > 0) {
|
|
||||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4);
|
|
||||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._accumulatedWheelDeltaY = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_resetWheelStepTimers() {
|
|
||||||
window.clearTimeout(this._wheelStepXTimer);
|
|
||||||
window.clearTimeout(this._wheelStepYTimer);
|
|
||||||
this._wheelStepXTimer = null;
|
|
||||||
this._wheelStepYTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleMouseWheel(e) {
|
|
||||||
this._resetWheelStepTimers();
|
|
||||||
|
|
||||||
this._updateMousePosition(e);
|
|
||||||
|
|
||||||
let dX = e.deltaX;
|
|
||||||
let dY = e.deltaY;
|
|
||||||
|
|
||||||
// Pixel units unless it's non-zero.
|
|
||||||
// Note that if deltamode is line or page won't matter since we aren't
|
|
||||||
// sending the mouse wheel delta to the server anyway.
|
|
||||||
// The difference between pixel and line can be important however since
|
|
||||||
// we have a threshold that can be smaller than the line height.
|
|
||||||
if (e.deltaMode !== 0) {
|
|
||||||
dX *= WHEEL_LINE_HEIGHT;
|
|
||||||
dY *= WHEEL_LINE_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._accumulatedWheelDeltaX += dX;
|
|
||||||
this._accumulatedWheelDeltaY += dY;
|
|
||||||
|
|
||||||
// Generate a mouse wheel step event when the accumulated delta
|
|
||||||
// for one of the axes is large enough.
|
|
||||||
// Small delta events that do not pass the threshold get sent
|
|
||||||
// after a timeout.
|
|
||||||
if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
|
|
||||||
this._generateWheelStepX();
|
|
||||||
} else {
|
|
||||||
this._wheelStepXTimer =
|
|
||||||
window.setTimeout(this._generateWheelStepX.bind(this),
|
|
||||||
WHEEL_STEP_TIMEOUT);
|
|
||||||
}
|
|
||||||
if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
|
|
||||||
this._generateWheelStepY();
|
|
||||||
} else {
|
|
||||||
this._wheelStepYTimer =
|
|
||||||
window.setTimeout(this._generateWheelStepY.bind(this),
|
|
||||||
WHEEL_STEP_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
stopEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleMouseMove(e) {
|
|
||||||
this._updateMousePosition(e);
|
|
||||||
this.onmousemove(this._pos.x, this._pos.y);
|
|
||||||
stopEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleMouseDisable(e) {
|
|
||||||
/*
|
|
||||||
* Stop propagation if inside canvas area
|
|
||||||
* Note: This is only needed for the 'click' event as it fails
|
|
||||||
* to fire properly for the target element so we have
|
|
||||||
* to listen on the document element instead.
|
|
||||||
*/
|
|
||||||
if (e.target == this._target) {
|
|
||||||
stopEvent(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update coordinates relative to target
|
|
||||||
_updateMousePosition(e) {
|
|
||||||
e = getPointerEvent(e);
|
|
||||||
const bounds = this._target.getBoundingClientRect();
|
|
||||||
let x;
|
|
||||||
let y;
|
|
||||||
// Clip to target bounds
|
|
||||||
if (e.clientX < bounds.left) {
|
|
||||||
x = 0;
|
|
||||||
} else if (e.clientX >= bounds.right) {
|
|
||||||
x = bounds.width - 1;
|
|
||||||
} else {
|
|
||||||
x = e.clientX - bounds.left;
|
|
||||||
}
|
|
||||||
if (e.clientY < bounds.top) {
|
|
||||||
y = 0;
|
|
||||||
} else if (e.clientY >= bounds.bottom) {
|
|
||||||
y = bounds.height - 1;
|
|
||||||
} else {
|
|
||||||
y = e.clientY - bounds.top;
|
|
||||||
}
|
|
||||||
this._pos = {x: x, y: y};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== PUBLIC METHODS =====
|
|
||||||
|
|
||||||
grab() {
|
|
||||||
if (isTouchDevice) {
|
|
||||||
this._target.addEventListener('touchstart', this._eventHandlers.mousedown);
|
|
||||||
this._target.addEventListener('touchend', this._eventHandlers.mouseup);
|
|
||||||
this._target.addEventListener('touchmove', this._eventHandlers.mousemove);
|
|
||||||
}
|
|
||||||
this._target.addEventListener('mousedown', this._eventHandlers.mousedown);
|
|
||||||
this._target.addEventListener('mouseup', this._eventHandlers.mouseup);
|
|
||||||
this._target.addEventListener('mousemove', this._eventHandlers.mousemove);
|
|
||||||
this._target.addEventListener('wheel', this._eventHandlers.mousewheel);
|
|
||||||
|
|
||||||
/* Prevent middle-click pasting (see above for why we bind to document) */
|
|
||||||
document.addEventListener('click', this._eventHandlers.mousedisable);
|
|
||||||
|
|
||||||
/* preventDefault() on mousedown doesn't stop this event for some
|
|
||||||
reason so we have to explicitly block it */
|
|
||||||
this._target.addEventListener('contextmenu', this._eventHandlers.mousedisable);
|
|
||||||
}
|
|
||||||
|
|
||||||
ungrab() {
|
|
||||||
this._resetWheelStepTimers();
|
|
||||||
|
|
||||||
if (isTouchDevice) {
|
|
||||||
this._target.removeEventListener('touchstart', this._eventHandlers.mousedown);
|
|
||||||
this._target.removeEventListener('touchend', this._eventHandlers.mouseup);
|
|
||||||
this._target.removeEventListener('touchmove', this._eventHandlers.mousemove);
|
|
||||||
}
|
|
||||||
this._target.removeEventListener('mousedown', this._eventHandlers.mousedown);
|
|
||||||
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup);
|
|
||||||
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove);
|
|
||||||
this._target.removeEventListener('wheel', this._eventHandlers.mousewheel);
|
|
||||||
|
|
||||||
document.removeEventListener('click', this._eventHandlers.mousedisable);
|
|
||||||
|
|
||||||
this._target.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import KeyTable from "./keysym.js";
|
||||||
import keysyms from "./keysymdef.js";
|
import keysyms from "./keysymdef.js";
|
||||||
import vkeys from "./vkeys.js";
|
import vkeys from "./vkeys.js";
|
||||||
import fixedkeys from "./fixedkeys.js";
|
import fixedkeys from "./fixedkeys.js";
|
||||||
|
@ -91,6 +92,8 @@ export function getKey(evt) {
|
||||||
// Mozilla isn't fully in sync with the spec yet
|
// Mozilla isn't fully in sync with the spec yet
|
||||||
switch (evt.key) {
|
switch (evt.key) {
|
||||||
case 'OS': return 'Meta';
|
case 'OS': return 'Meta';
|
||||||
|
case 'LaunchMyComputer': return 'LaunchApplication1';
|
||||||
|
case 'LaunchCalculator': return 'LaunchApplication2';
|
||||||
}
|
}
|
||||||
|
|
||||||
// iOS leaks some OS names
|
// iOS leaks some OS names
|
||||||
|
@ -102,9 +105,21 @@ export function getKey(evt) {
|
||||||
case 'UIKeyInputEscape': return 'Escape';
|
case 'UIKeyInputEscape': return 'Escape';
|
||||||
}
|
}
|
||||||
|
|
||||||
// IE and Edge have broken handling of AltGraph so we cannot
|
// Broken behaviour in Chrome
|
||||||
// trust them for printable characters
|
if ((evt.key === '\x00') && (evt.code === 'NumpadDecimal')) {
|
||||||
if ((evt.key.length !== 1) || (!browser.isIE() && !browser.isEdge())) {
|
return 'Delete';
|
||||||
|
}
|
||||||
|
|
||||||
|
// IE and Edge need special handling, but for everyone else we
|
||||||
|
// can trust the value provided
|
||||||
|
if (!browser.isIE() && !browser.isEdge()) {
|
||||||
|
return evt.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IE and Edge have broken handling of AltGraph so we can only
|
||||||
|
// trust them for non-printable characters (and unfortunately
|
||||||
|
// they also specify 'Unidentified' for some problem keys)
|
||||||
|
if ((evt.key.length !== 1) && (evt.key !== 'Unidentified')) {
|
||||||
return evt.key;
|
return evt.key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,10 +156,39 @@ export function getKeysym(evt) {
|
||||||
location = 2;
|
location = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// And for Clear
|
||||||
|
if ((key === 'Clear') && (location === 3)) {
|
||||||
|
let code = getKeycode(evt);
|
||||||
|
if (code === 'NumLock') {
|
||||||
|
location = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((location === undefined) || (location > 3)) {
|
if ((location === undefined) || (location > 3)) {
|
||||||
location = 0;
|
location = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The original Meta key now gets confused with the Windows key
|
||||||
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1020141
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1232918
|
||||||
|
if (key === 'Meta') {
|
||||||
|
let code = getKeycode(evt);
|
||||||
|
if (code === 'AltLeft') {
|
||||||
|
return KeyTable.XK_Meta_L;
|
||||||
|
} else if (code === 'AltRight') {
|
||||||
|
return KeyTable.XK_Meta_R;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// macOS has Clear instead of NumLock, but the remote system is
|
||||||
|
// probably not macOS, so lying here is probably best...
|
||||||
|
if (key === 'Clear') {
|
||||||
|
let code = getKeycode(evt);
|
||||||
|
if (code === 'NumLock') {
|
||||||
|
return KeyTable.XK_Num_Lock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return DOMKeyTable[key][location];
|
return DOMKeyTable[key][location];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
*
|
||||||
|
* Browser feature support detection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Log from './logging.js';
|
import * as Log from './logging.js';
|
||||||
|
@ -31,7 +33,7 @@ try {
|
||||||
const target = document.createElement('canvas');
|
const target = document.createElement('canvas');
|
||||||
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
|
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
|
||||||
|
|
||||||
if (target.style.cursor) {
|
if (target.style.cursor.indexOf("url") === 0) {
|
||||||
Log.Info("Data URI scheme cursor supported");
|
Log.Info("Data URI scheme cursor supported");
|
||||||
_supportsCursorURIs = true;
|
_supportsCursorURIs = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -52,6 +54,38 @@ try {
|
||||||
}
|
}
|
||||||
export const supportsImageMetadata = _supportsImageMetadata;
|
export const supportsImageMetadata = _supportsImageMetadata;
|
||||||
|
|
||||||
|
let _hasScrollbarGutter = true;
|
||||||
|
try {
|
||||||
|
// Create invisible container
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.style.visibility = 'hidden';
|
||||||
|
container.style.overflow = 'scroll'; // forcing scrollbars
|
||||||
|
document.body.appendChild(container);
|
||||||
|
|
||||||
|
// Create a div and place it in the container
|
||||||
|
const child = document.createElement('div');
|
||||||
|
container.appendChild(child);
|
||||||
|
|
||||||
|
// Calculate the difference between the container's full width
|
||||||
|
// and the child's width - the difference is the scrollbars
|
||||||
|
const scrollbarWidth = (container.offsetWidth - child.offsetWidth);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
container.parentNode.removeChild(container);
|
||||||
|
|
||||||
|
_hasScrollbarGutter = scrollbarWidth != 0;
|
||||||
|
} catch (exc) {
|
||||||
|
Log.Error("Scrollbar test exception: " + exc);
|
||||||
|
}
|
||||||
|
export const hasScrollbarGutter = _hasScrollbarGutter;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The functions for detection of platforms and browsers below are exported
|
||||||
|
* but the use of these should be minimized as much as possible.
|
||||||
|
*
|
||||||
|
* It's better to use feature detection than platform detection.
|
||||||
|
*/
|
||||||
|
|
||||||
export function isMac() {
|
export function isMac() {
|
||||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||||
}
|
}
|
||||||
|
@ -67,10 +101,6 @@ export function isIOS() {
|
||||||
!!(/ipod/i).exec(navigator.platform));
|
!!(/ipod/i).exec(navigator.platform));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAndroid() {
|
|
||||||
return navigator && !!(/android/i).exec(navigator.userAgent);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSafari() {
|
export function isSafari() {
|
||||||
return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
|
return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
|
||||||
navigator.userAgent.indexOf('Chrome') === -1);
|
navigator.userAgent.indexOf('Chrome') === -1);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ export default class Cursor {
|
||||||
this._canvas.style.pointerEvents = 'none';
|
this._canvas.style.pointerEvents = 'none';
|
||||||
// Can't use "display" because of Firefox bug #1445997
|
// Can't use "display" because of Firefox bug #1445997
|
||||||
this._canvas.style.visibility = 'hidden';
|
this._canvas.style.visibility = 'hidden';
|
||||||
document.body.appendChild(this._canvas);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._position = { x: 0, y: 0 };
|
this._position = { x: 0, y: 0 };
|
||||||
|
@ -31,9 +30,6 @@ export default class Cursor {
|
||||||
'mouseleave': this._handleMouseLeave.bind(this),
|
'mouseleave': this._handleMouseLeave.bind(this),
|
||||||
'mousemove': this._handleMouseMove.bind(this),
|
'mousemove': this._handleMouseMove.bind(this),
|
||||||
'mouseup': this._handleMouseUp.bind(this),
|
'mouseup': this._handleMouseUp.bind(this),
|
||||||
'touchstart': this._handleTouchStart.bind(this),
|
|
||||||
'touchmove': this._handleTouchMove.bind(this),
|
|
||||||
'touchend': this._handleTouchEnd.bind(this),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +41,8 @@ export default class Cursor {
|
||||||
this._target = target;
|
this._target = target;
|
||||||
|
|
||||||
if (useFallback) {
|
if (useFallback) {
|
||||||
|
document.body.appendChild(this._canvas);
|
||||||
|
|
||||||
// FIXME: These don't fire properly except for mouse
|
// FIXME: These don't fire properly except for mouse
|
||||||
/// movement in IE. We want to also capture element
|
/// movement in IE. We want to also capture element
|
||||||
// movement, size changes, visibility, etc.
|
// movement, size changes, visibility, etc.
|
||||||
|
@ -53,17 +51,16 @@ export default class Cursor {
|
||||||
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||||
this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
|
this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||||
this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
|
this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||||
|
|
||||||
// There is no "touchleave" so we monitor touchstart globally
|
|
||||||
window.addEventListener('touchstart', this._eventHandlers.touchstart, options);
|
|
||||||
this._target.addEventListener('touchmove', this._eventHandlers.touchmove, options);
|
|
||||||
this._target.addEventListener('touchend', this._eventHandlers.touchend, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
detach() {
|
detach() {
|
||||||
|
if (!this._target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (useFallback) {
|
if (useFallback) {
|
||||||
const options = { capture: true, passive: true };
|
const options = { capture: true, passive: true };
|
||||||
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
|
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||||
|
@ -71,9 +68,7 @@ export default class Cursor {
|
||||||
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
|
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||||
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
|
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||||
|
|
||||||
window.removeEventListener('touchstart', this._eventHandlers.touchstart, options);
|
document.body.removeChild(this._canvas);
|
||||||
this._target.removeEventListener('touchmove', this._eventHandlers.touchmove, options);
|
|
||||||
this._target.removeEventListener('touchend', this._eventHandlers.touchend, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._target = null;
|
this._target = null;
|
||||||
|
@ -124,6 +119,27 @@ export default class Cursor {
|
||||||
this._hotSpot.y = 0;
|
this._hotSpot.y = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mouse events might be emulated, this allows
|
||||||
|
// moving the cursor in such cases
|
||||||
|
move(clientX, clientY) {
|
||||||
|
if (!useFallback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// clientX/clientY are relative the _visual viewport_,
|
||||||
|
// but our position is relative the _layout viewport_,
|
||||||
|
// so try to compensate when we can
|
||||||
|
if (window.visualViewport) {
|
||||||
|
this._position.x = clientX + window.visualViewport.offsetLeft;
|
||||||
|
this._position.y = clientY + window.visualViewport.offsetTop;
|
||||||
|
} else {
|
||||||
|
this._position.x = clientX;
|
||||||
|
this._position.y = clientY;
|
||||||
|
}
|
||||||
|
this._updatePosition();
|
||||||
|
let target = document.elementFromPoint(clientX, clientY);
|
||||||
|
this._updateVisibility(target);
|
||||||
|
}
|
||||||
|
|
||||||
_handleMouseOver(event) {
|
_handleMouseOver(event) {
|
||||||
// This event could be because we're entering the target, or
|
// This event could be because we're entering the target, or
|
||||||
// moving around amongst its sub elements. Let the move handler
|
// moving around amongst its sub elements. Let the move handler
|
||||||
|
@ -132,7 +148,8 @@ export default class Cursor {
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleMouseLeave(event) {
|
_handleMouseLeave(event) {
|
||||||
this._hideCursor();
|
// Check if we should show the cursor on the element we are leaving to
|
||||||
|
this._updateVisibility(event.relatedTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleMouseMove(event) {
|
_handleMouseMove(event) {
|
||||||
|
@ -150,27 +167,29 @@ export default class Cursor {
|
||||||
// now and adjust visibility based on that.
|
// now and adjust visibility based on that.
|
||||||
let target = document.elementFromPoint(event.clientX, event.clientY);
|
let target = document.elementFromPoint(event.clientX, event.clientY);
|
||||||
this._updateVisibility(target);
|
this._updateVisibility(target);
|
||||||
}
|
|
||||||
|
|
||||||
_handleTouchStart(event) {
|
// Captures end with a mouseup but we can't know the event order of
|
||||||
// Just as for mouseover, we let the move handler deal with it
|
// mouseup vs releaseCapture.
|
||||||
this._handleTouchMove(event);
|
//
|
||||||
}
|
// In the cases when releaseCapture comes first, the code above is
|
||||||
|
// enough.
|
||||||
_handleTouchMove(event) {
|
//
|
||||||
this._updateVisibility(event.target);
|
// In the cases when the mouseup comes first, we need wait for the
|
||||||
|
// browser to flush all events and then check again if the cursor
|
||||||
this._position.x = event.changedTouches[0].clientX - this._hotSpot.x;
|
// should be visible.
|
||||||
this._position.y = event.changedTouches[0].clientY - this._hotSpot.y;
|
if (this._captureIsActive()) {
|
||||||
|
window.setTimeout(() => {
|
||||||
this._updatePosition();
|
// We might have detached at this point
|
||||||
}
|
if (!this._target) {
|
||||||
|
return;
|
||||||
_handleTouchEnd(event) {
|
}
|
||||||
// Same principle as for mouseup
|
// Refresh the target from elementFromPoint since queued events
|
||||||
let target = document.elementFromPoint(event.changedTouches[0].clientX,
|
// might have altered the DOM
|
||||||
event.changedTouches[0].clientY);
|
target = document.elementFromPoint(event.clientX,
|
||||||
this._updateVisibility(target);
|
event.clientY);
|
||||||
|
this._updateVisibility(target);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_showCursor() {
|
_showCursor() {
|
||||||
|
@ -189,6 +208,9 @@ export default class Cursor {
|
||||||
// (i.e. are we over the target, or a child of the target without a
|
// (i.e. are we over the target, or a child of the target without a
|
||||||
// different cursor set)
|
// different cursor set)
|
||||||
_shouldShowCursor(target) {
|
_shouldShowCursor(target) {
|
||||||
|
if (!target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Easy case
|
// Easy case
|
||||||
if (target === this._target) {
|
if (target === this._target) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -207,6 +229,11 @@ export default class Cursor {
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateVisibility(target) {
|
_updateVisibility(target) {
|
||||||
|
// When the cursor target has capture we want to show the cursor.
|
||||||
|
// So, if a capture is active - look at the captured element instead.
|
||||||
|
if (this._captureIsActive()) {
|
||||||
|
target = document.captureElement;
|
||||||
|
}
|
||||||
if (this._shouldShowCursor(target)) {
|
if (this._shouldShowCursor(target)) {
|
||||||
this._showCursor();
|
this._showCursor();
|
||||||
} else {
|
} else {
|
||||||
|
@ -218,4 +245,9 @@ export default class Cursor {
|
||||||
this._canvas.style.left = this._position.x + "px";
|
this._canvas.style.left = this._position.x + "px";
|
||||||
this._canvas.style.top = this._position.y + "px";
|
this._canvas.style.top = this._position.y + "px";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_captureIsActive() {
|
||||||
|
return document.captureElement &&
|
||||||
|
document.documentElement.contains(document.captureElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
32
static/js/novnc/core/util/element.js
Executable file
32
static/js/novnc/core/util/element.js
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* noVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 The noVNC Authors
|
||||||
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
|
*
|
||||||
|
* See README.md for usage and integration instructions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HTML element utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function clientToElement(x, y, elem) {
|
||||||
|
const bounds = elem.getBoundingClientRect();
|
||||||
|
let pos = { x: 0, y: 0 };
|
||||||
|
// Clip to target bounds
|
||||||
|
if (x < bounds.left) {
|
||||||
|
pos.x = 0;
|
||||||
|
} else if (x >= bounds.right) {
|
||||||
|
pos.x = bounds.width - 1;
|
||||||
|
} else {
|
||||||
|
pos.x = x - bounds.left;
|
||||||
|
}
|
||||||
|
if (y < bounds.top) {
|
||||||
|
pos.y = 0;
|
||||||
|
} else if (y >= bounds.bottom) {
|
||||||
|
pos.y = bounds.height - 1;
|
||||||
|
} else {
|
||||||
|
pos.y = y - bounds.top;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
|
@ -21,7 +21,8 @@ export function stopEvent(e) {
|
||||||
|
|
||||||
// Emulate Element.setCapture() when not supported
|
// Emulate Element.setCapture() when not supported
|
||||||
let _captureRecursion = false;
|
let _captureRecursion = false;
|
||||||
let _captureElem = null;
|
let _elementForUnflushedEvents = null;
|
||||||
|
document.captureElement = null;
|
||||||
function _captureProxy(e) {
|
function _captureProxy(e) {
|
||||||
// Recursion protection as we'll see our own event
|
// Recursion protection as we'll see our own event
|
||||||
if (_captureRecursion) return;
|
if (_captureRecursion) return;
|
||||||
|
@ -30,7 +31,11 @@ function _captureProxy(e) {
|
||||||
const newEv = new e.constructor(e.type, e);
|
const newEv = new e.constructor(e.type, e);
|
||||||
|
|
||||||
_captureRecursion = true;
|
_captureRecursion = true;
|
||||||
_captureElem.dispatchEvent(newEv);
|
if (document.captureElement) {
|
||||||
|
document.captureElement.dispatchEvent(newEv);
|
||||||
|
} else {
|
||||||
|
_elementForUnflushedEvents.dispatchEvent(newEv);
|
||||||
|
}
|
||||||
_captureRecursion = false;
|
_captureRecursion = false;
|
||||||
|
|
||||||
// Avoid double events
|
// Avoid double events
|
||||||
|
@ -48,58 +53,56 @@ function _captureProxy(e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Follow cursor style of target element
|
// Follow cursor style of target element
|
||||||
function _captureElemChanged() {
|
function _capturedElemChanged() {
|
||||||
const captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||||
captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
|
proxyElem.style.cursor = window.getComputedStyle(document.captureElement).cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _captureObserver = new MutationObserver(_captureElemChanged);
|
const _captureObserver = new MutationObserver(_capturedElemChanged);
|
||||||
|
|
||||||
let _captureIndex = 0;
|
export function setCapture(target) {
|
||||||
|
if (target.setCapture) {
|
||||||
|
|
||||||
export function setCapture(elem) {
|
target.setCapture();
|
||||||
if (elem.setCapture) {
|
document.captureElement = target;
|
||||||
|
|
||||||
elem.setCapture();
|
|
||||||
|
|
||||||
// IE releases capture on 'click' events which might not trigger
|
// IE releases capture on 'click' events which might not trigger
|
||||||
elem.addEventListener('mouseup', releaseCapture);
|
target.addEventListener('mouseup', releaseCapture);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Release any existing capture in case this method is
|
// Release any existing capture in case this method is
|
||||||
// called multiple times without coordination
|
// called multiple times without coordination
|
||||||
releaseCapture();
|
releaseCapture();
|
||||||
|
|
||||||
let captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
let proxyElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||||
|
|
||||||
if (captureElem === null) {
|
if (proxyElem === null) {
|
||||||
captureElem = document.createElement("div");
|
proxyElem = document.createElement("div");
|
||||||
captureElem.id = "noVNC_mouse_capture_elem";
|
proxyElem.id = "noVNC_mouse_capture_elem";
|
||||||
captureElem.style.position = "fixed";
|
proxyElem.style.position = "fixed";
|
||||||
captureElem.style.top = "0px";
|
proxyElem.style.top = "0px";
|
||||||
captureElem.style.left = "0px";
|
proxyElem.style.left = "0px";
|
||||||
captureElem.style.width = "100%";
|
proxyElem.style.width = "100%";
|
||||||
captureElem.style.height = "100%";
|
proxyElem.style.height = "100%";
|
||||||
captureElem.style.zIndex = 10000;
|
proxyElem.style.zIndex = 10000;
|
||||||
captureElem.style.display = "none";
|
proxyElem.style.display = "none";
|
||||||
document.body.appendChild(captureElem);
|
document.body.appendChild(proxyElem);
|
||||||
|
|
||||||
// This is to make sure callers don't get confused by having
|
// This is to make sure callers don't get confused by having
|
||||||
// our blocking element as the target
|
// our blocking element as the target
|
||||||
captureElem.addEventListener('contextmenu', _captureProxy);
|
proxyElem.addEventListener('contextmenu', _captureProxy);
|
||||||
|
|
||||||
captureElem.addEventListener('mousemove', _captureProxy);
|
proxyElem.addEventListener('mousemove', _captureProxy);
|
||||||
captureElem.addEventListener('mouseup', _captureProxy);
|
proxyElem.addEventListener('mouseup', _captureProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
_captureElem = elem;
|
document.captureElement = target;
|
||||||
_captureIndex++;
|
|
||||||
|
|
||||||
// Track cursor and get initial cursor
|
// Track cursor and get initial cursor
|
||||||
_captureObserver.observe(elem, {attributes: true});
|
_captureObserver.observe(target, {attributes: true});
|
||||||
_captureElemChanged();
|
_capturedElemChanged();
|
||||||
|
|
||||||
captureElem.style.display = "";
|
proxyElem.style.display = "";
|
||||||
|
|
||||||
// We listen to events on window in order to keep tracking if it
|
// We listen to events on window in order to keep tracking if it
|
||||||
// happens to leave the viewport
|
// happens to leave the viewport
|
||||||
|
@ -112,26 +115,26 @@ export function releaseCapture() {
|
||||||
if (document.releaseCapture) {
|
if (document.releaseCapture) {
|
||||||
|
|
||||||
document.releaseCapture();
|
document.releaseCapture();
|
||||||
|
document.captureElement = null;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (!_captureElem) {
|
if (!document.captureElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// There might be events already queued, so we need to wait for
|
// There might be events already queued. The event proxy needs
|
||||||
// them to flush. E.g. contextmenu in Microsoft Edge
|
// access to the captured element for these queued events.
|
||||||
window.setTimeout((expected) => {
|
// E.g. contextmenu (right-click) in Microsoft Edge
|
||||||
// Only clear it if it's the expected grab (i.e. no one
|
//
|
||||||
// else has initiated a new grab)
|
// Before removing the capturedElem pointer we save it to a
|
||||||
if (_captureIndex === expected) {
|
// temporary variable that the unflushed events can use.
|
||||||
_captureElem = null;
|
_elementForUnflushedEvents = document.captureElement;
|
||||||
}
|
document.captureElement = null;
|
||||||
}, 0, _captureIndex);
|
|
||||||
|
|
||||||
_captureObserver.disconnect();
|
_captureObserver.disconnect();
|
||||||
|
|
||||||
const captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||||
captureElem.style.display = "none";
|
proxyElem.style.display = "none";
|
||||||
|
|
||||||
window.removeEventListener('mousemove', _captureProxy);
|
window.removeEventListener('mousemove', _captureProxy);
|
||||||
window.removeEventListener('mouseup', _captureProxy);
|
window.removeEventListener('mouseup', _captureProxy);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
|
15
static/js/novnc/core/util/int.js
Executable file
15
static/js/novnc/core/util/int.js
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* noVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 The noVNC Authors
|
||||||
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
|
*
|
||||||
|
* See README.md for usage and integration instructions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function toUnsigned32bit(toConvert) {
|
||||||
|
return toConvert >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toSigned32bit(toConvert) {
|
||||||
|
return toConvert | 0;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -10,18 +10,18 @@
|
||||||
* Logging/debug routines
|
* Logging/debug routines
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let _log_level = 'warn';
|
let _logLevel = 'warn';
|
||||||
|
|
||||||
let Debug = () => {};
|
let Debug = () => {};
|
||||||
let Info = () => {};
|
let Info = () => {};
|
||||||
let Warn = () => {};
|
let Warn = () => {};
|
||||||
let Error = () => {};
|
let Error = () => {};
|
||||||
|
|
||||||
export function init_logging(level) {
|
export function initLogging(level) {
|
||||||
if (typeof level === 'undefined') {
|
if (typeof level === 'undefined') {
|
||||||
level = _log_level;
|
level = _logLevel;
|
||||||
} else {
|
} else {
|
||||||
_log_level = level;
|
_logLevel = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug = Info = Warn = Error = () => {};
|
Debug = Info = Warn = Error = () => {};
|
||||||
|
@ -46,11 +46,11 @@ export function init_logging(level) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_logging() {
|
export function getLogging() {
|
||||||
return _log_level;
|
return _logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Debug, Info, Warn, Error };
|
export { Debug, Info, Warn, Error };
|
||||||
|
|
||||||
// Initialize logging level
|
// Initialize logging level
|
||||||
init_logging();
|
initLogging();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2020 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -52,3 +52,10 @@ if (typeof Object.assign != 'function') {
|
||||||
window.CustomEvent = CustomEvent;
|
window.CustomEvent = CustomEvent;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
/* Number.isInteger() (taken from MDN) */
|
||||||
|
Number.isInteger = Number.isInteger || function isInteger(value) {
|
||||||
|
return typeof value === 'number' &&
|
||||||
|
isFinite(value) &&
|
||||||
|
Math.floor(value) === value;
|
||||||
|
};
|
||||||
|
|
|
@ -1,14 +1,28 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
// Decode from UTF-8
|
||||||
* Decode from UTF-8
|
export function decodeUTF8(utf8string, allowLatin1=false) {
|
||||||
*/
|
try {
|
||||||
export function decodeUTF8(utf8string) {
|
return decodeURIComponent(escape(utf8string));
|
||||||
return decodeURIComponent(escape(utf8string));
|
} catch (e) {
|
||||||
|
if (e instanceof URIError) {
|
||||||
|
if (allowLatin1) {
|
||||||
|
// If we allow Latin1 we can ignore any decoding fails
|
||||||
|
// and in these cases return the original string
|
||||||
|
return utf8string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode to UTF-8
|
||||||
|
export function encodeUTF8(DOMString) {
|
||||||
|
return unescape(encodeURIComponent(DOMString));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Websock: high-performance binary WebSockets
|
* Websock: high-performance binary WebSockets
|
||||||
* Copyright (C) 2018 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* Websock is similar to the standard WebSocket object but with extra
|
* Websock is similar to the standard WebSocket object but with extra
|
||||||
|
@ -17,6 +17,8 @@ import * as Log from './util/logging.js';
|
||||||
// this has performance issues in some versions Chromium, and
|
// this has performance issues in some versions Chromium, and
|
||||||
// doesn't gain a tremendous amount of performance increase in Firefox
|
// doesn't gain a tremendous amount of performance increase in Firefox
|
||||||
// at the moment. It may be valuable to turn it on in the future.
|
// at the moment. It may be valuable to turn it on in the future.
|
||||||
|
// Also copyWithin() for TypedArrays is not supported in IE 11 or
|
||||||
|
// Safari 13 (at the moment we want to support Safari 11).
|
||||||
const ENABLE_COPYWITHIN = false;
|
const ENABLE_COPYWITHIN = false;
|
||||||
const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
|
const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
|
||||||
|
|
||||||
|
@ -27,7 +29,6 @@ export default class Websock {
|
||||||
this._rQi = 0; // Receive queue index
|
this._rQi = 0; // Receive queue index
|
||||||
this._rQlen = 0; // Next write position in the receive queue
|
this._rQlen = 0; // Next write position in the receive queue
|
||||||
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
|
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
|
||||||
this._rQmax = this._rQbufferSize / 8;
|
|
||||||
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
|
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
|
||||||
this._rQ = null; // Receive queue
|
this._rQ = null; // Receive queue
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ export default class Websock {
|
||||||
|
|
||||||
flush() {
|
flush() {
|
||||||
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
|
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
|
||||||
this._websocket.send(this._encode_message());
|
this._websocket.send(this._encodeMessage());
|
||||||
this._sQlen = 0;
|
this._sQlen = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +155,7 @@ export default class Websock {
|
||||||
this.flush();
|
this.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
send_string(str) {
|
sendString(str) {
|
||||||
this.send(str.split('').map(chr => chr.charCodeAt(0)));
|
this.send(str.split('').map(chr => chr.charCodeAt(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,13 +168,13 @@ export default class Websock {
|
||||||
this._eventHandlers[evt] = handler;
|
this._eventHandlers[evt] = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
_allocate_buffers() {
|
_allocateBuffers() {
|
||||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||||
this._sQ = new Uint8Array(this._sQbufferSize);
|
this._sQ = new Uint8Array(this._sQbufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._allocate_buffers();
|
this._allocateBuffers();
|
||||||
this._rQi = 0;
|
this._rQi = 0;
|
||||||
this._websocket = null;
|
this._websocket = null;
|
||||||
}
|
}
|
||||||
|
@ -184,7 +185,7 @@ export default class Websock {
|
||||||
this._websocket = new WebSocket(uri, protocols);
|
this._websocket = new WebSocket(uri, protocols);
|
||||||
this._websocket.binaryType = 'arraybuffer';
|
this._websocket.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
this._websocket.onmessage = this._recv_message.bind(this);
|
this._websocket.onmessage = this._recvMessage.bind(this);
|
||||||
this._websocket.onopen = () => {
|
this._websocket.onopen = () => {
|
||||||
Log.Debug('>> WebSock.onopen');
|
Log.Debug('>> WebSock.onopen');
|
||||||
if (this._websocket.protocol) {
|
if (this._websocket.protocol) {
|
||||||
|
@ -219,42 +220,46 @@ export default class Websock {
|
||||||
}
|
}
|
||||||
|
|
||||||
// private methods
|
// private methods
|
||||||
_encode_message() {
|
_encodeMessage() {
|
||||||
// Put in a binary arraybuffer
|
// Put in a binary arraybuffer
|
||||||
// according to the spec, you can send ArrayBufferViews with the send method
|
// according to the spec, you can send ArrayBufferViews with the send method
|
||||||
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
_expand_compact_rQ(min_fit) {
|
// We want to move all the unread data to the start of the queue,
|
||||||
const resizeNeeded = min_fit || this.rQlen > this._rQbufferSize / 2;
|
// e.g. compacting.
|
||||||
|
// The function also expands the receive que if needed, and for
|
||||||
|
// performance reasons we combine these two actions to avoid
|
||||||
|
// unneccessary copying.
|
||||||
|
_expandCompactRQ(minFit) {
|
||||||
|
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
|
||||||
|
// instead of resizing
|
||||||
|
const requiredBufferSize = (this._rQlen - this._rQi + minFit) * 8;
|
||||||
|
const resizeNeeded = this._rQbufferSize < requiredBufferSize;
|
||||||
|
|
||||||
if (resizeNeeded) {
|
if (resizeNeeded) {
|
||||||
if (!min_fit) {
|
// Make sure we always *at least* double the buffer size, and have at least space for 8x
|
||||||
// just double the size if we need to do compaction
|
// the current amount of data
|
||||||
this._rQbufferSize *= 2;
|
this._rQbufferSize = Math.max(this._rQbufferSize * 2, requiredBufferSize);
|
||||||
} else {
|
|
||||||
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
|
|
||||||
this._rQbufferSize = (this.rQlen + min_fit) * 8;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't want to grow unboundedly
|
// we don't want to grow unboundedly
|
||||||
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
||||||
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
||||||
if (this._rQbufferSize - this.rQlen < min_fit) {
|
if (this._rQbufferSize - this.rQlen < minFit) {
|
||||||
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resizeNeeded) {
|
if (resizeNeeded) {
|
||||||
const old_rQbuffer = this._rQ.buffer;
|
const oldRQbuffer = this._rQ.buffer;
|
||||||
this._rQmax = this._rQbufferSize / 8;
|
|
||||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||||
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
|
this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
|
||||||
} else {
|
} else {
|
||||||
if (ENABLE_COPYWITHIN) {
|
if (ENABLE_COPYWITHIN) {
|
||||||
this._rQ.copyWithin(0, this._rQi);
|
this._rQ.copyWithin(0, this._rQi, this._rQlen);
|
||||||
} else {
|
} else {
|
||||||
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
|
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,26 +267,25 @@ export default class Websock {
|
||||||
this._rQi = 0;
|
this._rQi = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode_message(data) {
|
// push arraybuffer values onto the end of the receive que
|
||||||
// push arraybuffer values onto the end
|
_DecodeMessage(data) {
|
||||||
const u8 = new Uint8Array(data);
|
const u8 = new Uint8Array(data);
|
||||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||||
this._expand_compact_rQ(u8.length);
|
this._expandCompactRQ(u8.length);
|
||||||
}
|
}
|
||||||
this._rQ.set(u8, this._rQlen);
|
this._rQ.set(u8, this._rQlen);
|
||||||
this._rQlen += u8.length;
|
this._rQlen += u8.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
_recv_message(e) {
|
_recvMessage(e) {
|
||||||
this._decode_message(e.data);
|
this._DecodeMessage(e.data);
|
||||||
if (this.rQlen > 0) {
|
if (this.rQlen > 0) {
|
||||||
this._eventHandlers.message();
|
this._eventHandlers.message();
|
||||||
// Compact the receive queue
|
|
||||||
if (this._rQlen == this._rQi) {
|
if (this._rQlen == this._rQi) {
|
||||||
|
// All data has now been processed, this means we
|
||||||
|
// can reset the receive queue.
|
||||||
this._rQlen = 0;
|
this._rQlen = 0;
|
||||||
this._rQi = 0;
|
this._rQi = 0;
|
||||||
} else if (this._rQlen > this._rQmax) {
|
|
||||||
this._expand_compact_rQ();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.Debug("Ignoring empty message");
|
Log.Debug("Ignoring empty message");
|
||||||
|
|
88
static/js/novnc/package.json
Normal file
88
static/js/novnc/package.json
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
{
|
||||||
|
"name": "@novnc/novnc",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"description": "An HTML5 VNC client",
|
||||||
|
"browser": "lib/rfb",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib",
|
||||||
|
"doc": "docs",
|
||||||
|
"test": "tests"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"AUTHORS",
|
||||||
|
"VERSION",
|
||||||
|
"docs/API.md",
|
||||||
|
"docs/LIBRARY.md",
|
||||||
|
"docs/LICENSE*",
|
||||||
|
"core",
|
||||||
|
"vendor/pako"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint app core po/po2js po/xgettext-html tests utils",
|
||||||
|
"test": "karma start karma.conf.js",
|
||||||
|
"prepublish": "node ./utils/use_require.js --as commonjs --clean"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/novnc/noVNC.git"
|
||||||
|
},
|
||||||
|
"author": "Joel Martin <github@martintribe.org> (https://github.com/kanaka)",
|
||||||
|
"contributors": [
|
||||||
|
"Solly Ross <sross@redhat.com> (https://github.com/directxman12)",
|
||||||
|
"Peter Åstrand <astrand@cendio.se> (https://github.com/astrand)",
|
||||||
|
"Samuel Mannehed <samuel@cendio.se> (https://github.com/samhed)",
|
||||||
|
"Pierre Ossman <ossman@cendio.se> (https://github.com/CendioOssman)"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/novnc/noVNC/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/novnc/noVNC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "*",
|
||||||
|
"@babel/plugin-syntax-dynamic-import": "*",
|
||||||
|
"@babel/plugin-transform-modules-amd": "*",
|
||||||
|
"@babel/plugin-transform-modules-commonjs": "*",
|
||||||
|
"@babel/plugin-transform-modules-systemjs": "*",
|
||||||
|
"@babel/plugin-transform-modules-umd": "*",
|
||||||
|
"@babel/preset-env": "*",
|
||||||
|
"@babel/cli": "*",
|
||||||
|
"babel-plugin-import-redirect": "*",
|
||||||
|
"browserify": "*",
|
||||||
|
"babelify": "*",
|
||||||
|
"core-js": "*",
|
||||||
|
"chai": "*",
|
||||||
|
"commander": "*",
|
||||||
|
"es-module-loader": "*",
|
||||||
|
"eslint": "*",
|
||||||
|
"fs-extra": "*",
|
||||||
|
"jsdom": "*",
|
||||||
|
"karma": "*",
|
||||||
|
"karma-mocha": "*",
|
||||||
|
"karma-chrome-launcher": "*",
|
||||||
|
"@chiragrupani/karma-chromium-edge-launcher": "*",
|
||||||
|
"karma-firefox-launcher": "*",
|
||||||
|
"karma-ie-launcher": "*",
|
||||||
|
"karma-mocha-reporter": "*",
|
||||||
|
"karma-safari-launcher": "*",
|
||||||
|
"karma-script-launcher": "*",
|
||||||
|
"karma-sinon-chai": "*",
|
||||||
|
"mocha": "*",
|
||||||
|
"node-getopt": "*",
|
||||||
|
"po2json": "*",
|
||||||
|
"requirejs": "*",
|
||||||
|
"rollup": "*",
|
||||||
|
"rollup-plugin-node-resolve": "*",
|
||||||
|
"sinon": "*",
|
||||||
|
"sinon-chai": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"keywords": [
|
||||||
|
"vnc",
|
||||||
|
"rfb",
|
||||||
|
"novnc",
|
||||||
|
"websockify"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ It's based heavily on
|
||||||
https://github.com/ModuleLoader/browser-es-module-loader, but uses
|
https://github.com/ModuleLoader/browser-es-module-loader, but uses
|
||||||
WebWorkers to compile the modules in the background.
|
WebWorkers to compile the modules in the background.
|
||||||
|
|
||||||
To generate, run `rollup -c` in this directory, and then run `browserify
|
To generate, run `npx rollup -c` in this directory, and then run
|
||||||
src/babel-worker.js > dist/babel-worker.js`.
|
`./genworker.js`.
|
||||||
|
|
||||||
LICENSE
|
LICENSE
|
||||||
-------
|
-------
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
13
static/js/novnc/vendor/browser-es-module-loader/genworker.js
vendored
Executable file
13
static/js/novnc/vendor/browser-es-module-loader/genworker.js
vendored
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var fs = require("fs");
|
||||||
|
var browserify = require("browserify");
|
||||||
|
|
||||||
|
browserify("src/babel-worker.js")
|
||||||
|
.transform("babelify", {
|
||||||
|
presets: [ [ "@babel/preset-env", { targets: "ie >= 11" } ] ],
|
||||||
|
global: true,
|
||||||
|
ignore: [ "../../node_modules/core-js" ]
|
||||||
|
})
|
||||||
|
.bundle()
|
||||||
|
.pipe(fs.createWriteStream("dist/babel-worker.js"));
|
|
@ -1,16 +1,15 @@
|
||||||
import nodeResolve from 'rollup-plugin-node-resolve';
|
import nodeResolve from 'rollup-plugin-node-resolve';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
entry: 'src/browser-es-module-loader.js',
|
input: 'src/browser-es-module-loader.js',
|
||||||
dest: 'dist/browser-es-module-loader.js',
|
output: {
|
||||||
format: 'umd',
|
file: 'dist/browser-es-module-loader.js',
|
||||||
moduleName: 'BrowserESModuleLoader',
|
format: 'umd',
|
||||||
sourceMap: true,
|
name: 'BrowserESModuleLoader',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
nodeResolve(),
|
nodeResolve(),
|
||||||
],
|
],
|
||||||
|
|
||||||
// skip rollup warnings (specifically the eval warning)
|
|
||||||
onwarn: function() {}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
/*import { transform as babelTransform } from 'babel-core';
|
// Polyfills needed for Babel to function
|
||||||
import babelTransformDynamicImport from 'babel-plugin-syntax-dynamic-import';
|
require("core-js");
|
||||||
import babelTransformES2015ModulesSystemJS from 'babel-plugin-transform-es2015-modules-systemjs';*/
|
|
||||||
|
|
||||||
// sadly, due to how rollup works, we can't use es6 imports here
|
var babelTransform = require('@babel/core').transform;
|
||||||
var babelTransform = require('babel-core').transform;
|
var babelTransformDynamicImport = require('@babel/plugin-syntax-dynamic-import');
|
||||||
var babelTransformDynamicImport = require('babel-plugin-syntax-dynamic-import');
|
var babelTransformModulesSystemJS = require('@babel/plugin-transform-modules-systemjs');
|
||||||
var babelTransformES2015ModulesSystemJS = require('babel-plugin-transform-es2015-modules-systemjs');
|
var babelPresetEnv = require('@babel/preset-env');
|
||||||
var babelPresetES2015 = require('babel-preset-es2015');
|
|
||||||
|
|
||||||
self.onmessage = function (evt) {
|
self.onmessage = function (evt) {
|
||||||
// transform source with Babel
|
// transform source with Babel
|
||||||
|
@ -17,8 +15,8 @@ self.onmessage = function (evt) {
|
||||||
moduleIds: false,
|
moduleIds: false,
|
||||||
sourceMaps: 'inline',
|
sourceMaps: 'inline',
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
plugins: [babelTransformDynamicImport, babelTransformES2015ModulesSystemJS],
|
plugins: [babelTransformDynamicImport, babelTransformModulesSystemJS],
|
||||||
presets: [babelPresetES2015],
|
presets: [ [ babelPresetEnv, { targets: 'ie >= 11' } ] ],
|
||||||
});
|
});
|
||||||
|
|
||||||
self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source});
|
self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import RegisterLoader from 'es-module-loader/core/register-loader.js';
|
import RegisterLoader from 'es-module-loader/core/register-loader.js';
|
||||||
import { InternalModuleNamespace as ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js';
|
|
||||||
|
|
||||||
import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
|
import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
|
||||||
import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
|
import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
|
||||||
|
@ -35,7 +34,7 @@ if (typeof document != 'undefined' && document.getElementsByTagName) {
|
||||||
|
|
||||||
// throw so it still shows up in the console
|
// throw so it still shows up in the console
|
||||||
throw err;
|
throw err;
|
||||||
};
|
}
|
||||||
|
|
||||||
var ready = function() {
|
var ready = function() {
|
||||||
document.removeEventListener('DOMContentLoaded', ready, false );
|
document.removeEventListener('DOMContentLoaded', ready, false );
|
||||||
|
@ -63,7 +62,7 @@ if (typeof document != 'undefined' && document.getElementsByTagName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// simple DOM ready
|
// simple DOM ready
|
||||||
if (document.readyState !== 'loading')
|
if (document.readyState !== 'loading')
|
||||||
|
@ -105,10 +104,10 @@ function xhrFetch(url, resolve, reject) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
var load = function(source) {
|
var load = function(source) {
|
||||||
resolve(xhr.responseText);
|
resolve(xhr.responseText);
|
||||||
};
|
}
|
||||||
var error = function() {
|
var error = function() {
|
||||||
reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url));
|
reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url));
|
||||||
};
|
}
|
||||||
|
|
||||||
xhr.onreadystatechange = function () {
|
xhr.onreadystatechange = function () {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
|
@ -235,7 +234,7 @@ BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, proc
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
// anonymous module
|
// anonymous module
|
||||||
if (anonSources[key]) {
|
if (anonSources[key]) {
|
||||||
resolve(anonSources[key]);
|
resolve(anonSources[key])
|
||||||
anonSources[key] = undefined;
|
anonSources[key] = undefined;
|
||||||
}
|
}
|
||||||
// otherwise we fetch
|
// otherwise we fetch
|
||||||
|
|
|
@ -4,7 +4,7 @@ export function shrinkBuf (buf, size) {
|
||||||
if (buf.subarray) { return buf.subarray(0, size); }
|
if (buf.subarray) { return buf.subarray(0, size); }
|
||||||
buf.length = size;
|
buf.length = size;
|
||||||
return buf;
|
return buf;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export function arraySet (dest, src, src_offs, len, dest_offs) {
|
export function arraySet (dest, src, src_offs, len, dest_offs) {
|
||||||
|
|
60
static/js/novnc/vendor/pako/lib/zlib/deflate.js
vendored
60
static/js/novnc/vendor/pako/lib/zlib/deflate.js
vendored
|
@ -9,51 +9,51 @@ import msg from "./messages.js";
|
||||||
|
|
||||||
|
|
||||||
/* Allowed flush values; see deflate() and inflate() below for details */
|
/* Allowed flush values; see deflate() and inflate() below for details */
|
||||||
var Z_NO_FLUSH = 0;
|
export const Z_NO_FLUSH = 0;
|
||||||
var Z_PARTIAL_FLUSH = 1;
|
export const Z_PARTIAL_FLUSH = 1;
|
||||||
//var Z_SYNC_FLUSH = 2;
|
//export const Z_SYNC_FLUSH = 2;
|
||||||
var Z_FULL_FLUSH = 3;
|
export const Z_FULL_FLUSH = 3;
|
||||||
var Z_FINISH = 4;
|
export const Z_FINISH = 4;
|
||||||
var Z_BLOCK = 5;
|
export const Z_BLOCK = 5;
|
||||||
//var Z_TREES = 6;
|
//export const Z_TREES = 6;
|
||||||
|
|
||||||
|
|
||||||
/* Return codes for the compression/decompression functions. Negative values
|
/* Return codes for the compression/decompression functions. Negative values
|
||||||
* are errors, positive values are used for special but normal events.
|
* are errors, positive values are used for special but normal events.
|
||||||
*/
|
*/
|
||||||
var Z_OK = 0;
|
export const Z_OK = 0;
|
||||||
var Z_STREAM_END = 1;
|
export const Z_STREAM_END = 1;
|
||||||
//var Z_NEED_DICT = 2;
|
//export const Z_NEED_DICT = 2;
|
||||||
//var Z_ERRNO = -1;
|
//export const Z_ERRNO = -1;
|
||||||
var Z_STREAM_ERROR = -2;
|
export const Z_STREAM_ERROR = -2;
|
||||||
var Z_DATA_ERROR = -3;
|
export const Z_DATA_ERROR = -3;
|
||||||
//var Z_MEM_ERROR = -4;
|
//export const Z_MEM_ERROR = -4;
|
||||||
var Z_BUF_ERROR = -5;
|
export const Z_BUF_ERROR = -5;
|
||||||
//var Z_VERSION_ERROR = -6;
|
//export const Z_VERSION_ERROR = -6;
|
||||||
|
|
||||||
|
|
||||||
/* compression levels */
|
/* compression levels */
|
||||||
//var Z_NO_COMPRESSION = 0;
|
//export const Z_NO_COMPRESSION = 0;
|
||||||
//var Z_BEST_SPEED = 1;
|
//export const Z_BEST_SPEED = 1;
|
||||||
//var Z_BEST_COMPRESSION = 9;
|
//export const Z_BEST_COMPRESSION = 9;
|
||||||
var Z_DEFAULT_COMPRESSION = -1;
|
export const Z_DEFAULT_COMPRESSION = -1;
|
||||||
|
|
||||||
|
|
||||||
var Z_FILTERED = 1;
|
export const Z_FILTERED = 1;
|
||||||
var Z_HUFFMAN_ONLY = 2;
|
export const Z_HUFFMAN_ONLY = 2;
|
||||||
var Z_RLE = 3;
|
export const Z_RLE = 3;
|
||||||
var Z_FIXED = 4;
|
export const Z_FIXED = 4;
|
||||||
var Z_DEFAULT_STRATEGY = 0;
|
export const Z_DEFAULT_STRATEGY = 0;
|
||||||
|
|
||||||
/* Possible values of the data_type field (though see inflate()) */
|
/* Possible values of the data_type field (though see inflate()) */
|
||||||
//var Z_BINARY = 0;
|
//export const Z_BINARY = 0;
|
||||||
//var Z_TEXT = 1;
|
//export const Z_TEXT = 1;
|
||||||
//var Z_ASCII = 1; // = Z_TEXT
|
//export const Z_ASCII = 1; // = Z_TEXT
|
||||||
var Z_UNKNOWN = 2;
|
export const Z_UNKNOWN = 2;
|
||||||
|
|
||||||
|
|
||||||
/* The deflate compression method */
|
/* The deflate compression method */
|
||||||
var Z_DEFLATED = 8;
|
export const Z_DEFLATED = 8;
|
||||||
|
|
||||||
/*============================================================================*/
|
/*============================================================================*/
|
||||||
|
|
||||||
|
|
|
@ -277,7 +277,7 @@ export default function inflate_fast(strm, start) {
|
||||||
}
|
}
|
||||||
else if ((op & 64) === 0) { /* 2nd level distance code */
|
else if ((op & 64) === 0) { /* 2nd level distance code */
|
||||||
here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
|
here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
|
||||||
continue;
|
continue dodist;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
strm.msg = 'invalid distance code';
|
strm.msg = 'invalid distance code';
|
||||||
|
@ -290,7 +290,7 @@ export default function inflate_fast(strm, start) {
|
||||||
}
|
}
|
||||||
else if ((op & 64) === 0) { /* 2nd level length code */
|
else if ((op & 64) === 0) { /* 2nd level length code */
|
||||||
here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
|
here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
|
||||||
continue;
|
continue dolen;
|
||||||
}
|
}
|
||||||
else if (op & 32) { /* end-of-block */
|
else if (op & 32) { /* end-of-block */
|
||||||
//Tracevv((stderr, "inflate: end of block\n"));
|
//Tracevv((stderr, "inflate: end of block\n"));
|
||||||
|
@ -320,5 +320,5 @@ export default function inflate_fast(strm, start) {
|
||||||
strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
|
strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
|
||||||
state.hold = hold;
|
state.hold = hold;
|
||||||
state.bits = bits;
|
state.bits = bits;
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
34
static/js/novnc/vendor/pako/lib/zlib/inflate.js
vendored
34
static/js/novnc/vendor/pako/lib/zlib/inflate.js
vendored
|
@ -13,30 +13,30 @@ var DISTS = 2;
|
||||||
|
|
||||||
|
|
||||||
/* Allowed flush values; see deflate() and inflate() below for details */
|
/* Allowed flush values; see deflate() and inflate() below for details */
|
||||||
//var Z_NO_FLUSH = 0;
|
//export const Z_NO_FLUSH = 0;
|
||||||
//var Z_PARTIAL_FLUSH = 1;
|
//export const Z_PARTIAL_FLUSH = 1;
|
||||||
//var Z_SYNC_FLUSH = 2;
|
//export const Z_SYNC_FLUSH = 2;
|
||||||
//var Z_FULL_FLUSH = 3;
|
//export const Z_FULL_FLUSH = 3;
|
||||||
var Z_FINISH = 4;
|
export const Z_FINISH = 4;
|
||||||
var Z_BLOCK = 5;
|
export const Z_BLOCK = 5;
|
||||||
var Z_TREES = 6;
|
export const Z_TREES = 6;
|
||||||
|
|
||||||
|
|
||||||
/* Return codes for the compression/decompression functions. Negative values
|
/* Return codes for the compression/decompression functions. Negative values
|
||||||
* are errors, positive values are used for special but normal events.
|
* are errors, positive values are used for special but normal events.
|
||||||
*/
|
*/
|
||||||
var Z_OK = 0;
|
export const Z_OK = 0;
|
||||||
var Z_STREAM_END = 1;
|
export const Z_STREAM_END = 1;
|
||||||
var Z_NEED_DICT = 2;
|
export const Z_NEED_DICT = 2;
|
||||||
//var Z_ERRNO = -1;
|
//export const Z_ERRNO = -1;
|
||||||
var Z_STREAM_ERROR = -2;
|
export const Z_STREAM_ERROR = -2;
|
||||||
var Z_DATA_ERROR = -3;
|
export const Z_DATA_ERROR = -3;
|
||||||
var Z_MEM_ERROR = -4;
|
export const Z_MEM_ERROR = -4;
|
||||||
var Z_BUF_ERROR = -5;
|
export const Z_BUF_ERROR = -5;
|
||||||
//var Z_VERSION_ERROR = -6;
|
//export const Z_VERSION_ERROR = -6;
|
||||||
|
|
||||||
/* The deflate compression method */
|
/* The deflate compression method */
|
||||||
var Z_DEFLATED = 8;
|
export const Z_DEFLATED = 8;
|
||||||
|
|
||||||
|
|
||||||
/* STATES ====================================================================*/
|
/* STATES ====================================================================*/
|
||||||
|
|
14043
static/js/novnc/vendor/sinon.js
vendored
14043
static/js/novnc/vendor/sinon.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue