Merge pull request #15 from kasmtech/feature/KASM-1871_scroll_sensitivity
Feature/kasm 1871 scroll sensitivity
This commit is contained in:
commit
a4cf389029
22
app/ui.js
22
app/ui.js
|
@ -20,6 +20,16 @@ window.addEventListener("load", function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.updateSetting = (name, value) => {
|
||||||
|
WebUtil.writeSetting(name, value);
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case "translate_shortcuts":
|
||||||
|
UI.updateShortcutTranslation();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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, hasScrollbarGutter, dragThreshold }
|
import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold }
|
||||||
|
@ -190,6 +200,7 @@ const UI = {
|
||||||
UI.initSetting('quality', 6);
|
UI.initSetting('quality', 6);
|
||||||
UI.initSetting('dynamic_quality_min', 3);
|
UI.initSetting('dynamic_quality_min', 3);
|
||||||
UI.initSetting('dynamic_quality_max', 9);
|
UI.initSetting('dynamic_quality_max', 9);
|
||||||
|
UI.initSetting('translate_shortcuts', true);
|
||||||
UI.initSetting('treat_lossless', 7);
|
UI.initSetting('treat_lossless', 7);
|
||||||
UI.initSetting('jpeg_video_quality', 5);
|
UI.initSetting('jpeg_video_quality', 5);
|
||||||
UI.initSetting('webp_video_quality', 5);
|
UI.initSetting('webp_video_quality', 5);
|
||||||
|
@ -411,6 +422,8 @@ const UI = {
|
||||||
UI.addSettingChangeHandler('dynamic_quality_min', UI.updateQuality);
|
UI.addSettingChangeHandler('dynamic_quality_min', UI.updateQuality);
|
||||||
UI.addSettingChangeHandler('dynamic_quality_max');
|
UI.addSettingChangeHandler('dynamic_quality_max');
|
||||||
UI.addSettingChangeHandler('dynamic_quality_max', UI.updateQuality);
|
UI.addSettingChangeHandler('dynamic_quality_max', UI.updateQuality);
|
||||||
|
UI.addSettingChangeHandler('translate_shortcuts');
|
||||||
|
UI.addSettingChangeHandler('translate_shortcuts', UI.updateShortcutTranslation);
|
||||||
UI.addSettingChangeHandler('treat_lossless');
|
UI.addSettingChangeHandler('treat_lossless');
|
||||||
UI.addSettingChangeHandler('treat_lossless', UI.updateQuality);
|
UI.addSettingChangeHandler('treat_lossless', UI.updateQuality);
|
||||||
UI.addSettingChangeHandler('anti_aliasing');
|
UI.addSettingChangeHandler('anti_aliasing');
|
||||||
|
@ -1266,6 +1279,7 @@ const UI = {
|
||||||
document.addEventListener('mousedown', UI.mouseDownVNC);
|
document.addEventListener('mousedown', UI.mouseDownVNC);
|
||||||
UI.rfb.addEventListener("bell", UI.bell);
|
UI.rfb.addEventListener("bell", UI.bell);
|
||||||
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
|
UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
|
||||||
|
UI.rfb.translateShortcuts = UI.getSetting('translate_shortcuts');
|
||||||
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';
|
||||||
|
@ -1731,12 +1745,18 @@ const UI = {
|
||||||
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ------^-------
|
/* ------^-------
|
||||||
* /COMPRESSION
|
* /COMPRESSION
|
||||||
* ==============
|
* ==============
|
||||||
* KEYBOARD
|
* MOUSE AND KEYBOARD
|
||||||
* ------v------*/
|
* ------v------*/
|
||||||
|
|
||||||
|
updateShortcutTranslation() {
|
||||||
|
UI.rfb.translateShortcuts = UI.getSetting('translate_shortcuts');
|
||||||
|
},
|
||||||
|
|
||||||
showVirtualKeyboard() {
|
showVirtualKeyboard() {
|
||||||
if (!isTouchDevice) return;
|
if (!isTouchDevice) return;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { stopEvent } from '../util/events.js';
|
||||||
import * as KeyboardUtil from "./util.js";
|
import * as KeyboardUtil from "./util.js";
|
||||||
import KeyTable from "./keysym.js";
|
import KeyTable from "./keysym.js";
|
||||||
import * as browser from "../util/browser.js";
|
import * as browser from "../util/browser.js";
|
||||||
|
import UI from '../../app/ui.js';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Keyboard event handler
|
// Keyboard event handler
|
||||||
|
@ -42,7 +43,11 @@ export default class Keyboard {
|
||||||
} else {
|
} else {
|
||||||
// On MacOs zoom and shortcut actions are CMD based so we need to
|
// On MacOs zoom and shortcut actions are CMD based so we need to
|
||||||
// let the remote know that it should unselect the CTRL key instead
|
// let the remote know that it should unselect the CTRL key instead
|
||||||
if (browser.isMac() && code === "MetaLeft" && this._keyDownList["ControlLeft"]) {
|
if (
|
||||||
|
browser.isMac() &&
|
||||||
|
this._keyDownList["ControlLeft"] &&
|
||||||
|
(code === "MetaLeft" || code === "MetaRight")
|
||||||
|
) {
|
||||||
keysym = KeyTable.XK_Control_L;
|
keysym = KeyTable.XK_Control_L;
|
||||||
code = "ControlLeft";
|
code = "ControlLeft";
|
||||||
}
|
}
|
||||||
|
@ -133,6 +138,21 @@ export default class Keyboard {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translate MacOs CMD based shortcuts to their CTRL based counterpart
|
||||||
|
if (
|
||||||
|
browser.isMac() &&
|
||||||
|
UI.rfb && UI.rfb.translateShortcuts &&
|
||||||
|
code !== "MetaLeft" && code !== "MetaRight" &&
|
||||||
|
e.metaKey && !e.ctrlKey && !e.altKey
|
||||||
|
) {
|
||||||
|
this._sendKeyEvent(this._keyDownList["MetaLeft"], "MetaLeft", false);
|
||||||
|
this._sendKeyEvent(this._keyDownList["MetaRight"], "MetaRight", false);
|
||||||
|
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
|
this._sendKeyEvent(keysym, code, true);
|
||||||
|
stopEvent(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Alt behaves more like AltGraph on macOS, so shuffle the
|
// Alt behaves more like AltGraph on macOS, so shuffle the
|
||||||
// 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
|
||||||
|
|
125
core/rfb.js
125
core/rfb.js
|
@ -10,7 +10,7 @@
|
||||||
import { toUnsigned32bit, toSigned32bit } from './util/int.js';
|
import { toUnsigned32bit, toSigned32bit } from './util/int.js';
|
||||||
import * as Log from './util/logging.js';
|
import * as Log from './util/logging.js';
|
||||||
import { encodeUTF8, decodeUTF8 } from './util/strings.js';
|
import { encodeUTF8, decodeUTF8 } from './util/strings.js';
|
||||||
import { dragThreshold, supportsCursorURIs, isTouchDevice, isMac } from './util/browser.js';
|
import { dragThreshold, supportsCursorURIs, isTouchDevice, isWindows, isMac } from './util/browser.js';
|
||||||
import { clientToElement } from './util/element.js';
|
import { clientToElement } from './util/element.js';
|
||||||
import { setCapture } from './util/events.js';
|
import { setCapture } from './util/events.js';
|
||||||
import EventTargetMixin from './util/eventtarget.js';
|
import EventTargetMixin from './util/eventtarget.js';
|
||||||
|
@ -44,8 +44,7 @@ var _enableWebP = false;
|
||||||
const MOUSE_MOVE_DELAY = 17;
|
const MOUSE_MOVE_DELAY = 17;
|
||||||
|
|
||||||
// Wheel thresholds
|
// Wheel thresholds
|
||||||
const WHEEL_STEP = 50; // Pixels needed for one step
|
let WHEEL_LINE_HEIGHT = 19; // Pixels for one line step (on Windows)
|
||||||
const WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step
|
|
||||||
|
|
||||||
// Gesture thresholds
|
// Gesture thresholds
|
||||||
const GESTURE_ZOOMSENS = 75;
|
const GESTURE_ZOOMSENS = 75;
|
||||||
|
@ -183,19 +182,6 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._accumulatedWheelDeltaX = 0;
|
this._accumulatedWheelDeltaX = 0;
|
||||||
this._accumulatedWheelDeltaY = 0;
|
this._accumulatedWheelDeltaY = 0;
|
||||||
|
|
||||||
// On MacOs we simulate the CTRL key being pressed on pinch and zoom
|
|
||||||
// so we need to manually unselect it whenever the action is completed (500ms since last scroll)
|
|
||||||
if (isMac()) {
|
|
||||||
setInterval(() => {
|
|
||||||
const timeSinceLastPinchAndZoom = Math.max(0, +new Date() - this._mouseLastPinchAndZoomTime);
|
|
||||||
|
|
||||||
if (timeSinceLastPinchAndZoom > 500) {
|
|
||||||
this._keyboard._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", false);
|
|
||||||
this._mouseLastPinchAndZoomTime = Infinity;
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gesture state
|
// Gesture state
|
||||||
this._gestureLastTapTime = null;
|
this._gestureLastTapTime = null;
|
||||||
this._gestureFirstDoubleTapEv = null;
|
this._gestureFirstDoubleTapEv = null;
|
||||||
|
@ -1223,6 +1209,18 @@ export default class RFB extends EventTargetMixin {
|
||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case 'mousedown':
|
case 'mousedown':
|
||||||
setCapture(this._canvas);
|
setCapture(this._canvas);
|
||||||
|
|
||||||
|
// Translate CMD+Click into CTRL+click on MacOs
|
||||||
|
if (
|
||||||
|
isMac() &&
|
||||||
|
ev.metaKey &&
|
||||||
|
(this._keyboard._keyDownList["MetaLeft"] || this._keyboard._keyDownList["MetaRight"])
|
||||||
|
) {
|
||||||
|
this._keyboard._sendKeyEvent(this._keyboard._keyDownList["MetaLeft"], "MetaLeft", false);
|
||||||
|
this._keyboard._sendKeyEvent(this._keyboard._keyDownList["MetaRight"], "MetaRight", false);
|
||||||
|
this._keyboard._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
|
}
|
||||||
|
|
||||||
this._handleMouseButton(pos.x, pos.y,
|
this._handleMouseButton(pos.x, pos.y,
|
||||||
true, 1 << ev.button);
|
true, 1 << ev.button);
|
||||||
break;
|
break;
|
||||||
|
@ -1330,6 +1328,13 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._display.absY(y), mask);
|
this._display.absY(y), mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_sendScroll(x, y, dX, dY) {
|
||||||
|
if (this._rfbConnectionState !== 'connected') { return; }
|
||||||
|
if (this._viewOnly) { return; } // View only, skip mouse events
|
||||||
|
|
||||||
|
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), 0, dX, dY);
|
||||||
|
}
|
||||||
|
|
||||||
_handleWheel(ev) {
|
_handleWheel(ev) {
|
||||||
if (this._rfbConnectionState !== 'connected') { return; }
|
if (this._rfbConnectionState !== 'connected') { return; }
|
||||||
if (this._viewOnly) { return; } // View only, skip mouse events
|
if (this._viewOnly) { return; } // View only, skip mouse events
|
||||||
|
@ -1337,64 +1342,50 @@ export default class RFB extends EventTargetMixin {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
let pos = clientToElement(ev.clientX, ev.clientY,
|
// On MacOs we need to translate zooming CMD+wheel to CTRL+wheel
|
||||||
this._canvas);
|
if (isMac() && (this._keyboard._keyDownList["MetaLeft"] || this._keyboard._keyDownList["MetaRight"])) {
|
||||||
|
this._keyboard._sendKeyEvent(this._keyboard._keyDownList["MetaLeft"], "MetaLeft", false);
|
||||||
|
this._keyboard._sendKeyEvent(this._keyboard._keyDownList["MetaRight"], "MetaRight", false);
|
||||||
|
this._keyboard._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
|
}
|
||||||
|
|
||||||
let dX = ev.deltaX;
|
// In a pinch and zoom gesture we're sending only a wheel event so we need
|
||||||
let dY = ev.deltaY;
|
// to make sure a CTRL press event is sent alongside it if we want to trigger zooming.
|
||||||
|
// Moreover, we don't have a way to know that the gesture has stopped so we
|
||||||
|
// need to check manually every now and then and "unpress" the CTRL key when it ends.
|
||||||
|
if (ev.ctrlKey && !this._keyboard._keyDownList["ControlLeft"]) {
|
||||||
|
this._keyboard._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
|
|
||||||
|
this._watchForPinchAndZoom = this._watchForPinchAndZoom || setInterval(() => {
|
||||||
|
const timeSinceLastPinchAndZoom = +new Date() - this._mouseLastPinchAndZoomTime;
|
||||||
|
if (timeSinceLastPinchAndZoom > 250) {
|
||||||
|
clearInterval(this._watchForPinchAndZoom);
|
||||||
|
this._keyboard._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", false);
|
||||||
|
this._watchForPinchAndZoom = null;
|
||||||
|
this._mouseLastPinchAndZoomTime = 0;
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._watchForPinchAndZoom) {
|
||||||
|
this._mouseLastPinchAndZoomTime = +new Date();
|
||||||
|
}
|
||||||
|
|
||||||
// Pixel units unless it's non-zero.
|
// Pixel units unless it's non-zero.
|
||||||
// Note that if deltamode is line or page won't matter since we aren't
|
// Note that if deltamode is line or page won't matter since we aren't
|
||||||
// sending the mouse wheel delta to the server anyway.
|
// sending the mouse wheel delta to the server anyway.
|
||||||
// The difference between pixel and line can be important however since
|
// The difference between pixel and line can be important however since
|
||||||
// we have a threshold that can be smaller than the line height.
|
// we have a threshold that can be smaller than the line height.
|
||||||
|
let dX = ev.deltaX;
|
||||||
|
let dY = ev.deltaY;
|
||||||
|
|
||||||
if (ev.deltaMode !== 0) {
|
if (ev.deltaMode !== 0) {
|
||||||
dX *= WHEEL_LINE_HEIGHT;
|
dX *= WHEEL_LINE_HEIGHT;
|
||||||
dY *= WHEEL_LINE_HEIGHT;
|
dY *= WHEEL_LINE_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mouse wheel events are sent in steps over VNC. This means that the VNC
|
const pointer = clientToElement(ev.clientX, ev.clientY, this._canvas);
|
||||||
// protocol can't handle a wheel event with specific distance or speed.
|
this._sendScroll(pointer.x, pointer.y, dX, dY);
|
||||||
// Therefor, if we get a lot of small mouse wheel events we combine them.
|
|
||||||
this._accumulatedWheelDeltaX += dX;
|
|
||||||
this._accumulatedWheelDeltaY += dY;
|
|
||||||
|
|
||||||
// On MacOs we need to translate zooming CMD+wheel to CTRL+wheel
|
|
||||||
if (isMac() && this._keyboard._keyDownList["MetaLeft"]) {
|
|
||||||
this._keyboard._sendKeyEvent(this._keyboard._keyDownList["MetaLeft"], "MetaLeft", false);
|
|
||||||
this._keyboard._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// On MacOs we need to send a CTRL key to let the remote know we are pinch and zooming
|
|
||||||
if (isMac() && ev.ctrlKey && !this._keyboard._keyDownList["ControlLeft"]) {
|
|
||||||
this._keyboard._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
|
||||||
this._mouseLastPinchAndZoomTime = +new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a mouse wheel step event when the accumulated delta
|
|
||||||
// for one of the axes is large enough.
|
|
||||||
if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) {
|
|
||||||
if (this._accumulatedWheelDeltaX < 0) {
|
|
||||||
this._handleMouseButton(pos.x, pos.y, true, 1 << 5);
|
|
||||||
this._handleMouseButton(pos.x, pos.y, false, 1 << 5);
|
|
||||||
} else if (this._accumulatedWheelDeltaX > 0) {
|
|
||||||
this._handleMouseButton(pos.x, pos.y, true, 1 << 6);
|
|
||||||
this._handleMouseButton(pos.x, pos.y, false, 1 << 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._accumulatedWheelDeltaX = 0;
|
|
||||||
}
|
|
||||||
if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) {
|
|
||||||
if (this._accumulatedWheelDeltaY < 0) {
|
|
||||||
this._handleMouseButton(pos.x, pos.y, true, 1 << 3);
|
|
||||||
this._handleMouseButton(pos.x, pos.y, false, 1 << 3);
|
|
||||||
} else if (this._accumulatedWheelDeltaY > 0) {
|
|
||||||
this._handleMouseButton(pos.x, pos.y, true, 1 << 4);
|
|
||||||
this._handleMouseButton(pos.x, pos.y, false, 1 << 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._accumulatedWheelDeltaY = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_fakeMouseMove(ev, elementX, elementY) {
|
_fakeMouseMove(ev, elementX, elementY) {
|
||||||
|
@ -3029,7 +3020,7 @@ RFB.messages = {
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
pointerEvent(sock, x, y, mask) {
|
pointerEvent(sock, x, y, mask, dX = 0, dY = 0) {
|
||||||
const buff = sock._sQ;
|
const buff = sock._sQ;
|
||||||
const offset = sock._sQlen;
|
const offset = sock._sQlen;
|
||||||
|
|
||||||
|
@ -3043,7 +3034,13 @@ RFB.messages = {
|
||||||
buff[offset + 4] = y >> 8;
|
buff[offset + 4] = y >> 8;
|
||||||
buff[offset + 5] = y;
|
buff[offset + 5] = y;
|
||||||
|
|
||||||
sock._sQlen += 6;
|
buff[offset + 6] = dX >> 8;
|
||||||
|
buff[offset + 7] = dX;
|
||||||
|
|
||||||
|
buff[offset + 8] = dY >> 8;
|
||||||
|
buff[offset + 9] = dY;
|
||||||
|
|
||||||
|
sock._sQlen += 10;
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
9
vnc.html
9
vnc.html
|
@ -190,7 +190,14 @@
|
||||||
<li>
|
<li>
|
||||||
<label><input id="noVNC_setting_clipboard_seamless" type="checkbox" /> Clipboard Seamless</label></li>
|
<label><input id="noVNC_setting_clipboard_seamless" type="checkbox" /> Clipboard Seamless</label></li>
|
||||||
<li>
|
<li>
|
||||||
<label><input id="noVNC_setting_prefer_local_cursor" type="checkbox" /> Prefer Local Cursor</label></li>
|
<label><input id="noVNC_setting_prefer_local_cursor" type="checkbox" /> Prefer Local Cursor</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input id="noVNC_setting_translate_shortcuts" type="checkbox" />Translate keyboard shurtcuts
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label><input id="noVNC_setting_enable_webp" type="checkbox" /> Enable WebP Compression</label></li>
|
<label><input id="noVNC_setting_enable_webp" type="checkbox" /> Enable WebP Compression</label></li>
|
||||||
<li>
|
<li>
|
||||||
|
|
Loading…
Reference in New Issue