1
0
Fork 0
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:
catborise 2020-08-11 15:05:09 +03:00
parent bb935b3713
commit 43f1461e29
53 changed files with 109904 additions and 56529 deletions

View file

@ -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">

View file

@ -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 }};

View file

@ -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.

View file

@ -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

View file

@ -0,0 +1 @@
DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES.

View 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": "キャンセル"
}

View file

@ -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"
} }

View file

@ -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": "取消"
} }

View file

@ -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

View file

@ -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;
}

View file

@ -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();

View file

@ -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("&");
} }

View file

@ -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.

View file

@ -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.

View file

@ -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);

View file

@ -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;
} }

View file

@ -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.

View file

@ -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

View file

@ -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;
} }

View 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;
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -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);
} }

View file

@ -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);

View 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 } };
}
}

View file

@ -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',

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);

View file

@ -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);
}
} }

View 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;
}

View file

@ -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);

View file

@ -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.

View 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;
}

View file

@ -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();

View file

@ -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;
};

View file

@ -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));
} }

View file

@ -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");

View 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"
]
}

View file

@ -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 one or more lines are too long

View 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"));

View file

@ -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() {}
}; };

View file

@ -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});

View file

@ -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

View file

@ -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) {

View file

@ -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;
/*============================================================================*/ /*============================================================================*/

View file

@ -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;
}; };

View file

@ -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 ====================================================================*/

File diff suppressed because one or more lines are too long