diff --git a/app/images/splash.jpg b/app/images/splash.jpg new file mode 100644 index 00000000..a600b5a9 Binary files /dev/null and b/app/images/splash.jpg differ diff --git a/app/styles/base.css b/app/styles/base.css index e83e08cd..a3096d21 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -22,7 +22,8 @@ body { margin:0; padding:0; - font-family: Helvetica; + font-family: "Poppins", "Helvetica"; + letter-spacing: 0.05em; background: white url('../images/icons/kasm_logo.png') no-repeat fixed center; height:100%; touch-action: none; @@ -37,7 +38,7 @@ html { } .noVNC_disabled { - color: rgb(128, 128, 128); + color: rgb(128, 128, 128) !important; } /* ---------------------------------------- @@ -351,8 +352,9 @@ select:active { /* Edge misrenders animations wihthout this */ transform: translateX(0); } + :root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle { - opacity: 0.8; + /* opacity: 0.8; */ } #noVNC_control_bar_anchor.noVNC_right { left: auto; @@ -365,12 +367,12 @@ select:active { transition: 0.5s ease-in-out; - background-color: rgb(80, 89, 101); + background-color: rgb(9 2 2 / 0.6); border-radius: 0 10px 10px 0; - + border-style: inset; + border-color: rgb(255 255 255 / 0.6); } #noVNC_control_bar.noVNC_open { - box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); left: 0; } #noVNC_control_bar::before { @@ -400,28 +402,31 @@ select:active { #noVNC_control_bar_handle { position: absolute; left: -15px; - top: 0; transform: translateY(35px); width: calc(100% + 30px); height: 50px; z-index: -1; cursor: pointer; border-radius: 5px; - background-color: rgb(83, 99, 122); background-image: url("../images/handle_bg.svg"); background-repeat: no-repeat; background-position: right; - box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5); } #noVNC_control_bar_handle:after { content: ""; transition: transform 0.5s ease-in-out; background: url("../images/handle.svg"); + background-repeat: no-repeat; + background-position: center; position: absolute; - top: 22px; /* (50px-6px)/2 */ - right: 5px; - width: 5px; - height: 6px; + right: 0px; + width: 15px; + height: 60px; + background-color: rgb(9 2 2 / 0.6); + border-bottom-right-radius: 10px; + border-top-right-radius: 10px; + border-color: rgb(255 255 255 / 0.6); + border-style: inset; } #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after { transform: translateX(1px) rotate(180deg); @@ -431,6 +436,7 @@ select:active { } .noVNC_right #noVNC_control_bar_handle { background-position: left; + } .noVNC_right #noVNC_control_bar_handle:after { left: 5px; @@ -495,18 +501,23 @@ select:active { transform: translateY(-50%) scale(1); } +.noVNC_button_div { + display: block; + color: #fff; + font-size: 13px; +} + /* General button style */ .noVNC_button { - display: block; + display: inline; padding: 4px 4px; margin: 10px 0; vertical-align: middle; - border:1px solid rgba(255, 255, 255, 0.2); - border-radius: 6px; } + .noVNC_button.noVNC_selected { border-color: rgba(0, 0, 0, 0.8); - background: rgba(0, 0, 0, 0.5); + background: rgba(153, 151, 157, 0.68); } .noVNC_button:disabled { opacity: 0.4; @@ -523,10 +534,10 @@ select:active { :root:not(.noVNC_touch) .noVNC_button.noVNC_selected:hover, .noVNC_button.noVNC_selected:focus { border-color: rgba(0, 0, 0, 0.4); - background: rgba(0, 0, 0, 0.2); + background: rgba(153, 151, 157, 0.68); } -:root:not(.noVNC_touch) .noVNC_button:hover, -.noVNC_button:focus { +:root:not(.noVNC_touch) .noVNC_button_div:hover, +.noVNC_button_div:focus { background: rgba(255, 255, 255, 0.2); } .noVNC_button.noVNC_hidden { @@ -539,6 +550,7 @@ select:active { transition: 0.5s ease-in-out; + width: 300px; max-height: 100vh; /* Chrome is buggy with 100% */ overflow-x: hidden; overflow-y: auto; @@ -548,11 +560,9 @@ select:active { padding: 15px; - background: #fff; + background: rgb(9 9 0 / 0.77); border-radius: 10px; color: #000; - border: 2px solid #E0E0E0; - box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); } .noVNC_panel.noVNC_open { visibility: visible; @@ -578,10 +588,11 @@ select:active { .noVNC_panel label { display: block; white-space: nowrap; + color:white; } .noVNC_panel .noVNC_heading { - background-color: rgb(110, 132, 163); + background-color: rgb(54,58,64); border-radius: 5px; padding: 5px; /* Compensate for padding in image */ @@ -602,12 +613,16 @@ select:active { /* Expanders */ .noVNC_expander { cursor: pointer; + color:white; + } .noVNC_expander::before { content: url("../images/expander.svg"); display: inline-block; margin-right: 5px; transition: 0.2s ease-in-out; + -webkit-filter: invert(.75); /* safari 6.0 - 9.0 */ + filter: invert(.75); } .noVNC_expander.noVNC_open::before { transform: rotateZ(90deg); @@ -630,6 +645,14 @@ select:active { text-align: center; } +:root:not(.noVNC_disconnected) .noVNC_hide_on_connect { + display: none +} + +:root:not(.noVNC_connected) .noVNC_hide_on_disconnect { + display: none; +} + :root:not(.noVNC_connected) #noVNC_view_drag_button { display: none; } @@ -687,6 +710,7 @@ select:active { list-style: none; margin: 0px; padding: 0px; + color:white; } #noVNC_setting_port { width: 80px; @@ -776,81 +800,6 @@ select:active { content: url("../images/warning.svg") " "; } -/* ---------------------------------------- - * Connect Dialog - * ---------------------------------------- - */ - -#noVNC_connect_dlg { - transition: 0.5s ease-in-out; - - transform: scale(0, 0); - visibility: hidden; - opacity: 0; -} -#noVNC_connect_dlg.noVNC_open { - transform: scale(1, 1); - visibility: visible; - opacity: 1; -} -#noVNC_connect_dlg .noVNC_logo { - transition: 0.5s ease-in-out; - padding: 10px; - margin-bottom: 10px; - - font-size: 80px; - text-align: center; - - border-radius: 5px; -} -@media (max-width: 440px) { - #noVNC_connect_dlg { - max-width: calc(100vw - 100px); - } - #noVNC_connect_dlg .noVNC_logo { - font-size: calc(25vw - 30px); - } -} -#noVNC_connect_button { - cursor: pointer; - - /* - padding: 10px; - - color: white; - background-color: rgb(110, 132, 163); - border-radius: 12px; - box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); - */ - - text-align: center; - font-size: 20px; - margin-top: 130px; -} -#noVNC_connect_button div { - margin: 2px; - padding: 5px 30px; - border: 1px solid rgb(83, 99, 122); - border-bottom-width: 2px; - border-radius: 5px; - background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147)); - - /* This avoids it jumping around when :active */ - vertical-align: middle; - color: white; -} -#noVNC_connect_button div:active { - border-bottom-width: 1px; - margin-top: 3px; -} -:root:not(.noVNC_touch) #noVNC_connect_button div:hover { - background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155)); -} - -#noVNC_connect_button img { - vertical-align: bottom; - height: 1.3em; -} /* ---------------------------------------- * Password Dialog @@ -930,9 +879,7 @@ select:active { #noVNC_container { width: 100%; height: 100%; - background-color: rgb(74, 144, 217, 0.5); - border-bottom-right-radius: 800px 600px; - /*border-top-left-radius: 800px 600px;*/ + background-image: url('../images/splash.jpg') } #noVNC_keyboardinput { @@ -962,6 +909,10 @@ select:active { font-family: 'Orbitron', 'OrbitronTTF', sans-serif; line-height:90%; text-shadow: 0.1em 0.1em 0 black; + margin-bottom: 0px; +} +.noVNC_logo img { + width: 45% } .noVNC_logo span{ color:green; @@ -1139,3 +1090,75 @@ body { font-size: 90px; } } + +/* ---------------------------------------- + * Slider Check boxes + * ---------------------------------------- + */ +/* The switch - the box around the slider */ +.switch { + position: relative; + display: inline-block; + width: 30px; + height: 16px; + margin: 5px; +} + +/* Hide default HTML checkbox */ +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 14px; + width: 14px; + left: 4px; + bottom: 1px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #2196F3; +} + +input:focus + .slider { + box-shadow: 0 0 1px #2196F3; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(10px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} + +.slider-label { + padding-left: 26px; +} diff --git a/app/ui.js b/app/ui.js index e60b4809..84e1585a 100644 --- a/app/ui.js +++ b/app/ui.js @@ -128,6 +128,7 @@ const UI = { UI.addControlbarHandlers(); UI.addTouchSpecificHandlers(); UI.addExtraKeysHandlers(); + UI.addGamingHandlers(); UI.addMachineHandlers(); UI.addConnectionControlHandlers(); UI.addClipboardHandlers(); @@ -146,8 +147,6 @@ const UI = { UI.connect(); } else { autoconnect = false; - // Show the connect panel on first load unless autoconnecting - UI.openConnectPanel(); } window.parent.postMessage({ @@ -178,10 +177,9 @@ const UI = { document.documentElement.mozRequestFullScreen || document.documentElement.webkitRequestFullscreen || document.body.msRequestFullscreen)) { - document.getElementById('noVNC_fullscreen_button') - .classList.remove("noVNC_hidden"); - UI.addFullscreenHandlers(); - } + UI.showControlInput("noVNC_fullscreen_button") + UI.addFullscreenHandlers(); + } }, initSettings() { @@ -348,12 +346,7 @@ const UI = { document.getElementById("noVNC_control_bar") .addEventListener('keydown', UI.keepControlbar); - document.getElementById("noVNC_view_drag_button") - .addEventListener('click', UI.toggleViewDrag); - - document - .getElementById("noVNC_setting_pointer_lock") - .addEventListener("click", UI.togglePointerLock); + UI.addClickHandle('noVNC_view_drag_button', UI.toggleViewDrag); document.getElementById("noVNC_control_bar_handle") .addEventListener('mousedown', UI.controlbarHandleMouseDown); @@ -406,8 +399,8 @@ const UI = { }, addExtraKeysHandlers() { - document.getElementById("noVNC_toggle_extra_keys_button") - .addEventListener('click', UI.toggleExtraKeys); + UI.addClickHandle('noVNC_toggle_extra_keys_button', UI.toggleExtraKeys); + document.getElementById("noVNC_toggle_ctrl_button") .addEventListener('click', UI.toggleCtrl); document.getElementById("noVNC_toggle_windows_button") @@ -420,24 +413,29 @@ const UI = { .addEventListener('click', UI.sendEsc); document.getElementById("noVNC_send_ctrl_alt_del_button") .addEventListener('click', UI.sendCtrlAltDel); - document.getElementById("noVNC_game_mode_button") - .addEventListener("click", UI.toggleRelativePointer) + }, + + addGamingHandlers() { + UI.addClickHandle('noVNC_game_mode_button', UI.toggleRelativePointer); + document + .getElementById("noVNC_setting_pointer_lock") + .addEventListener("click", UI.togglePointerLock); }, addMachineHandlers() { + UI.addClickHandle('noVNC_power_button', UI.togglePowerPanel); + document.getElementById("noVNC_shutdown_button") .addEventListener('click', () => UI.rfb.machineShutdown()); document.getElementById("noVNC_reboot_button") .addEventListener('click', () => UI.rfb.machineReboot()); document.getElementById("noVNC_reset_button") .addEventListener('click', () => UI.rfb.machineReset()); - document.getElementById("noVNC_power_button") - .addEventListener('click', UI.togglePowerPanel); }, addConnectionControlHandlers() { - document.getElementById("noVNC_disconnect_button") - .addEventListener('click', UI.disconnect); + UI.addClickHandle('noVNC_disconnect_button', UI.disconnect); + var connect_btn_el = document.getElementById("noVNC_connect_button"); if (typeof(connect_btn_el) != 'undefined' && connect_btn_el != null) { @@ -451,8 +449,8 @@ const UI = { }, addClipboardHandlers() { - document.getElementById("noVNC_clipboard_button") - .addEventListener('click', UI.toggleClipboardPanel); + UI.addClickHandle('noVNC_clipboard_button', UI.toggleClipboardPanel); + document.getElementById("noVNC_clipboard_text") .addEventListener('change', UI.clipboardSend); document.getElementById("noVNC_clipboard_clear_button") @@ -472,8 +470,7 @@ const UI = { }, addSettingsHandlers() { - document.getElementById("noVNC_settings_button") - .addEventListener('click', UI.toggleSettingsPanel); + UI.addClickHandle('noVNC_settings_button', UI.toggleSettingsPanel); document.getElementById("noVNC_setting_enable_perf_stats").addEventListener('click', UI.showStats); @@ -542,8 +539,7 @@ const UI = { }, addFullscreenHandlers() { - document.getElementById("noVNC_fullscreen_button") - .addEventListener('click', UI.toggleFullscreen); + UI.addClickHandle('noVNC_fullscreen_button', UI.toggleFullscreen); window.addEventListener('fullscreenchange', UI.updateFullscreenButton); window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton); @@ -556,6 +552,20 @@ const UI = { * ============== * VISUAL * ------v------*/ + // Ignore clicks that are propogated from child elements in sub panels + isControlPanelItemClick(e) { + if (!(e && e.target && e.target.classList && e.target.parentNode && + ( + e.target.classList.contains('noVNC_button') && e.target.parentNode.id !== 'noVNC_modifiers' || + e.target.classList.contains('noVNC_button_div') || + e.target.classList.contains('noVNC_heading') + ) + )) { + return false; + } + + return true; + }, // Disable/enable controls depending on connection state updateVisualState(state) { @@ -564,6 +574,7 @@ const UI = { document.documentElement.classList.remove("noVNC_connected"); document.documentElement.classList.remove("noVNC_disconnecting"); document.documentElement.classList.remove("noVNC_reconnecting"); + document.documentElement.classList.remove("noVNC_disconnected"); const transitionElem = document.getElementById("noVNC_transition_text"); if (WebUtil.isInsideKasmVDI()) @@ -586,6 +597,7 @@ const UI = { document.documentElement.classList.add("noVNC_disconnecting"); break; case 'disconnected': + document.documentElement.classList.add("noVNC_disconnected"); break; case 'reconnecting': transitionElem.textContent = _("Reconnecting..."); @@ -746,7 +758,9 @@ const UI = { UI.closeAllPanels(); document.getElementById('noVNC_control_bar') .classList.remove("noVNC_open"); - UI.rfb.focus(); + if (UI.rfb) { + UI.rfb.focus(); + } }, toggleControlbar() { @@ -919,6 +933,48 @@ const UI = { } }, + addClickHandle(domElementName, funcToCall) { + /* Add click handler, will attach to parent if appropriate */ + var control = document.getElementById(domElementName); + if (control.parentNode.classList.contains('noVNC_button_div')) { + control.parentNode.addEventListener('click', funcToCall); + } else { + control.addEventListener('click', funcToCall); + } + }, + + showControlInput(name) { + var control = document.getElementById(name); + /*var control_label = document.getElementById(name + '_label'); + if (control) { + control.classList.remove("noVNC_hidden"); + } + if (control_label) { + control_label.classList.remove("noVNC_hidden"); + } */ + if (control.parentNode.classList.contains('noVNC_button_div')) { + control.parentNode.classList.remove("noVNC_hidden") + } else { + control.classList.remove("noVNC_hidden") + } + }, + + hideControlInput(name) { + var control = document.getElementById(name); + /*var control_label = document.getElementById(name + '_label'); + if (control) { + control.classList.add("noVNC_hidden"); + } + if (control_label) { + control_label.classList.add("noVNC_hidden"); + }*/ + if (control.parentNode.classList.contains('noVNC_button_div')) { + control.parentNode.classList.add("noVNC_hidden") + } else { + control.classList.add("noVNC_hidden") + } + }, + /* ------^------- * /VISUAL * ============== @@ -1085,7 +1141,11 @@ const UI = { .classList.remove("noVNC_selected"); }, - toggleSettingsPanel() { + toggleSettingsPanel(e) { + if (!UI.isControlPanelItemClick(e)) { + return false; + } + if (document.getElementById('noVNC_settings') .classList.contains("noVNC_open")) { UI.closeSettingsPanel(); @@ -1117,7 +1177,11 @@ const UI = { .classList.remove("noVNC_selected"); }, - togglePowerPanel() { + togglePowerPanel(e) { + if (!UI.isControlPanelItemClick(e)) { + return false; + } + if (document.getElementById('noVNC_power') .classList.contains("noVNC_open")) { UI.closePowerPanel(); @@ -1131,11 +1195,9 @@ const UI = { if (UI.connected && UI.rfb.capabilities.power && !UI.rfb.viewOnly) { - document.getElementById('noVNC_power_button') - .classList.remove("noVNC_hidden"); + UI.showControlInput('noVNC_power_button') } else { - document.getElementById('noVNC_power_button') - .classList.add("noVNC_hidden"); + UI.hideControlInput('noVNC_power_button'); // Close power panel if open UI.closePowerPanel(); } @@ -1164,7 +1226,11 @@ const UI = { .classList.remove("noVNC_selected"); }, - toggleClipboardPanel() { + toggleClipboardPanel(e) { + if (!UI.isControlPanelItemClick(e)) { + return false; + } + if (document.getElementById('noVNC_clipboard') .classList.contains("noVNC_open")) { UI.closeClipboardPanel(); @@ -1254,16 +1320,6 @@ const UI = { * CONNECTION * ------v------*/ - openConnectPanel() { - document.getElementById('noVNC_connect_dlg') - .classList.add("noVNC_open"); - }, - - closeConnectPanel() { - document.getElementById('noVNC_connect_dlg') - .classList.remove("noVNC_open"); - }, - connect(event, password) { // Ignore when rfb already exists @@ -1292,8 +1348,6 @@ const UI = { return; } - UI.closeConnectPanel(); - UI.updateVisualState('connecting'); let url; @@ -1383,7 +1437,6 @@ const UI = { } UI.rfb.addEventListener("disconnect", UI.disconnectedRx); document.getElementById('noVNC_control_bar_anchor').setAttribute('style', 'display: none'); - document.getElementById('noVNC_connect_dlg').innerHTML = ''; //keep alive for websocket connection to stay open, since we may not control reverse proxies //send a keep alive within a window that we control @@ -1464,7 +1517,6 @@ const UI = { UI.updateVisualState('disconnected'); UI.openControlbar(); - UI.openConnectPanel(); }, connectFinished(e) { @@ -1518,7 +1570,6 @@ const UI = { document.title = PAGE_TITLE; UI.openControlbar(); - UI.openConnectPanel(); if (UI.forceReconnect) { UI.forceReconnect = false; @@ -1791,19 +1842,11 @@ const UI = { (document.pointerLockElement !== undefined || document.mozPointerLockElement !== undefined) ) { - document - .getElementById("noVNC_setting_pointer_lock") - .classList.remove("noVNC_hidden"); - document - .getElementById("noVNC_game_mode_button") - .classList.remove("noVNC_hidden"); + UI.showControlInput("noVNC_setting_pointer_lock"); + UI.showControlInput("noVNC_game_mode_button"); } else { - document - .getElementById("noVNC_setting_pointer_lock") - .classList.add("noVNC_hidden"); - document - .getElementById("noVNC_game_mode_button") - .classList.add("noVNC_hidden"); + UI.hideControlInput("noVNC_setting_pointer_lock"); + UI.hideControlInput("noVNC_game_mode_button"); } }, @@ -1878,9 +1921,9 @@ const UI = { } if (UI.rfb.clipViewport) { - viewDragButton.classList.remove("noVNC_hidden"); + UI.showControlInput('noVNC_view_drag_button'); } else { - viewDragButton.classList.add("noVNC_hidden"); + UI.hideControlInput('noVNC_view_drag_button'); } }, @@ -2178,7 +2221,11 @@ const UI = { .classList.remove("noVNC_selected"); }, - toggleExtraKeys() { + toggleExtraKeys(e) { + if (!UI.isControlPanelItemClick(e)) { + return false; + } + if (document.getElementById('noVNC_modifiers').classList.contains("noVNC_open")) { UI.closeExtraKeys(); } else { @@ -2272,23 +2319,15 @@ const UI = { // Hide input related buttons in view only mode if (UI.rfb.viewOnly) { - document.getElementById('noVNC_keyboard_button') - .classList.add('noVNC_hidden'); - document.getElementById('noVNC_toggle_extra_keys_button') - .classList.add('noVNC_hidden'); - document.getElementById('noVNC_clipboard_button') - .classList.add('noVNC_hidden'); - document.getElementById('noVNC_game_mode_button') - .classList.add('noVNC_hidden'); + UI.hideControlInput("noVNC_keyboard_button"); + UI.hideControlInput("noVNC_toggle_extra_keys_button"); + UI.hideControlInput("noVNC_clipboard_button"); + UI.hideControlInput("noVNC_game_mode_button"); } else { - document.getElementById('noVNC_keyboard_button') - .classList.remove('noVNC_hidden'); - document.getElementById('noVNC_toggle_extra_keys_button') - .classList.remove('noVNC_hidden'); - document.getElementById('noVNC_clipboard_button') - .classList.remove('noVNC_hidden'); - document.getElementById('noVNC_game_mode_button') - .classList.remove('noVNC_hidden'); + UI.showControlInput("noVNC_keyboard_button"); + UI.showControlInput("noVNC_toggle_extra_keys_button"); + UI.showControlInput("noVNC_clipboard_button"); + UI.showControlInput("noVNC_game_mode_button"); } }, diff --git a/vnc.html b/vnc.html index cd5f4b2d..6531283d 100644 --- a/vnc.html +++ b/vnc.html @@ -86,329 +86,434 @@ Loading statistics... -
-
- - - - - - - -
-
-
-
-
+
+
+
-

+

- - - - - - - - - -
-
-
- Power + +
+ + Drag Viewport
- - - -
-
- - -
-
-
- Clipboard + + +
+ +
+
+ + + + + + + +
+
+ Keys
- -
- -
-
- - + +
+ + +
+
+
+ Power +
+ + + +
+
+ Power +
- - + +
+ + Clipboard +
+
+
+ Clipboard +
+ +
+ +
+
+
- - -
-
-
    -
  • - Settings -
  • -
  • - -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • - -
  • -
  • - -
  • -
  • -
  • -
  • -
  • -
  • - -
  • - -
  • -
  • -
  • -
  • -
  • - - -
  • -

  • -
  • - -
  • -
  • - - -
  • -

  • -
  • -
    Keyboard Shortcuts
    -
    + +
    + + Fullscreen +
    + + +
    + + Game Cursor Mode +
    + + +
    + +
    +
      +
    • + Settings +
    • -
    • -
    • Ctrl+Shift+
    • -
    • 1 - Toggle Control Panel
    • -
    • 2 - Toggle Game Pointer Mode
    • -
    • 3 - Toggle Pointer Lock
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • + + +
    • + +
    • +
    • + +
    • +
    • + + +
    • +

    • +
    • + +
    • +
    • + + +
    • +

    • +
    • +
      Keyboard Shortcuts
      +
      +
        +
      • + +
      • +
      • Ctrl+Shift+
      • +
      • 1 - Toggle Control Panel
      • +
      • 2 - Toggle Game Pointer Mode
      • +
      • 3 - Toggle Pointer Lock
      • +
      +
      +
    • +

    • +
    • +
      Stream Quality
      +
      +
        +
      • + + +
      • +
      • + + +
      • +
      • + + +
      • + +
      • + + + 3 +
      • +
      • + + + 9 +
      • +
      • + + + 7 +
      • +
      • + + +
      • +
      • + + + 5 +
      • +
      • + + + 5 +
      • +
      • + + + 65 +
      • +
      • + + + 5 +
      • +
      • + + + 3 +
      • +
      • + + +
      • +
      • + + +
      • +
      • + + +
      • +
      +
      +
    • +

    • +
    • +
      Advanced
      +
      +
        +
      • + + +
      • +

      • +
      • + + +
      • +
      • +
        WebSocket
        +
        +
          +
        • + +
        • +
        • + + +
        • +
        • + + +
        • +
        • + + +
        • +
        +
        +
      • +

      • +
      • + +
      • +
      • + + +
      • +

      • +
      • + +
      • +

      • + +
      • + +
      • +
      +
      +
    • +

    • +
    • + Version: + +
    -
  • -

  • -
  • -
    Stream Quality
    -
      -
    • - - -
    • -
    • - - -
    • -
    • - - -
    • - -
    • - - - 3 -
    • -
    • - - - 9 -
    • -
    • - - - 7 -
    • -
    • - - -
    • -
    • - - - 5 -
    • -
    • - - - 5 -
    • -
    • - - - 65 -
    • -
    • - - - 5 -
    • -
    • - - - 3 -
    • -
    • - - -
    • -
    • - - -
    • -
    • - - -
    • -
    -
  • -

  • -
  • -
    Advanced
    -
      -
    • - - -
    • -

    • -
    • - - -
    • -
    • -
      WebSocket
      -
        -
      • - -
      • -
      • - - -
      • -
      • - - -
      • -
      • - - -
      • -
      -
    • -

    • -
    • - -
    • -
    • - - -
    • -

    • -
    • - -
    • -

    • - -
    • - -
    • -
    -
  • -

  • -
  • - Version: - -
  • -
-
-
+
+ Settings +
- - + +
+ + Disconnect +
+ + +
+ + Connect +
@@ -423,10 +528,11 @@
- -
- Connect -
+