From 2b738be8e672721efae06841dfd3badd4aa72a28 Mon Sep 17 00:00:00 2001 From: Rui Reis Date: Tue, 28 May 2024 14:41:15 +0200 Subject: [PATCH 1/9] Add UltraVNC touch gestures support --- app/ui.js | 7 +- core/encodings.js | 1 + core/input/touchhandlerultravnc.js | 112 ++++++++++++++++++ core/rfb.js | 184 +++++++++++++++++++++++++++-- vnc.html | 1 + 5 files changed, 297 insertions(+), 8 deletions(-) create mode 100644 core/input/touchhandlerultravnc.js diff --git a/app/ui.js b/app/ui.js index f27dfe28..8c1098e4 100644 --- a/app/ui.js +++ b/app/ui.js @@ -180,6 +180,7 @@ const UI = { UI.initSetting('shared', true); UI.initSetting('view_only', false); UI.initSetting('show_dot', false); + UI.initSetting('ultravnc_gestures', false); UI.initSetting('path', 'websockify'); UI.initSetting('repeaterID', ''); UI.initSetting('reconnect', false); @@ -368,6 +369,7 @@ const UI = { UI.addSettingChangeHandler('view_only', UI.updateViewOnly); UI.addSettingChangeHandler('show_dot'); UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor); + UI.addSettingChangeHandler('ultravnc_gestures'); UI.addSettingChangeHandler('host'); UI.addSettingChangeHandler('port'); UI.addSettingChangeHandler('path'); @@ -438,6 +440,7 @@ const UI = { UI.disableSetting('port'); UI.disableSetting('path'); UI.disableSetting('repeaterID'); + UI.disableSetting('ultravnc_gestures'); // Hide the controlbar after 2 seconds UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000); @@ -448,6 +451,7 @@ const UI = { UI.enableSetting('port'); UI.enableSetting('path'); UI.enableSetting('repeaterID'); + UI.enableSetting('ultravnc_gestures'); UI.updatePowerButton(); UI.keepControlbar(); } @@ -1045,7 +1049,8 @@ const UI = { UI.rfb = new RFB(document.getElementById('noVNC_container'), url, { shared: UI.getSetting('shared'), repeaterID: UI.getSetting('repeaterID'), - credentials: { password: password } }); + credentials: { password: password }, + useUltraVNCGestures: UI.getSetting('ultravnc_gestures') }); } catch (exc) { Log.Error("Failed to connect to server: " + exc); UI.updateVisualState('disconnected'); diff --git a/core/encodings.js b/core/encodings.js index 1a79989d..1e43738b 100644 --- a/core/encodings.js +++ b/core/encodings.js @@ -23,6 +23,7 @@ export const encodings = { pseudoEncodingCursor: -239, pseudoEncodingQEMUExtendedKeyEvent: -258, pseudoEncodingQEMULedEvent: -261, + pseudoEncodingGii: -305, pseudoEncodingDesktopName: -307, pseudoEncodingExtendedDesktopSize: -308, pseudoEncodingXvp: -309, diff --git a/core/input/touchhandlerultravnc.js b/core/input/touchhandlerultravnc.js new file mode 100644 index 00000000..c9ccac91 --- /dev/null +++ b/core/input/touchhandlerultravnc.js @@ -0,0 +1,112 @@ +import * as Log from '../util/logging.js'; + +export default class TouchHandlerUltraVNC { + static PF_flag = 0x80000000; // Pressed Flag : active if the touch event is pressed, inactive if it's being released. + static R1_flag = 0x40000000; // Reserved 1 + static IF_flag = 0x20000000; // Primary Flag : active if the touch event is the primary touch event. + static S1_flag = 0x10000000; // Size Flag : active if the message contains information about the size of the touch event. The events are currently all sent as symetrical ellipses. + static S2_flag = 0x8000000; // Reserved for asymetrical ellipses. Not supported yet and should be 0. + static RT_flag = 0x4000000; // Rectangle : the touch event is a rectangle instead of an ellipse. + static PR_flag = 0x2000000; // Pressure Flag : pressure of the touch. Currently unused. + static TI_flag = 0x1000000; // Timestamp : the timestamp of the touch event. + static HC_flag = 0x800000; // High Performance Counter + + static LENGTH_16_flag = 0x10; // 16 bits signed for x touch coordinate followed by 16 bits signed for y together in a 32 bits word + static IDFORMAT_32 = 0x1; // 32 bits ID + static IDFORMAT_CLEAR = 0xF; // No more touch points + + constructor() { + this._target = null; + + this._currentTouches = []; + this._sendTouchesIntervalId = -1; + this._giiDeviceOrigin = 0; + this._isUltraVNCTouchActivated = false; + + this._boundEventHandler = this._handleTouch.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._target.removeEventListener('touchstart', + this._boundEventHandler); + this._target.removeEventListener('touchmove', + this._boundEventHandler); + this._target.removeEventListener('touchend', + this._boundEventHandler); + this._target.removeEventListener('touchcancel', + this._boundEventHandler); + + clearInterval(this._sendTouchesIntervalId); + this._sendTouchesIntervalId = -1; + + this._target = null; + } + + _handleTouch(ev) { + Log.Debug("Gesture: " + ev.type); + + if (!this._isUltraVNCTouchActivated) { + return; + } + + if (ev.type === "touchstart") { + for (let i = 0; i < ev.changedTouches.length; i++) { + this._currentTouches.push({ event: ev.changedTouches[i], status: "POINTER_DOWN" }); + } + + if (this._sendTouchesIntervalId === -1 && this._target) { + this._dispatchTouchEvent(ev); + this._sendTouchesIntervalId = setInterval(() => { + this._dispatchTouchEvent(ev); + }, 200); + } + } else if (ev.type === "touchmove") { + for (let i = 0; i < ev.changedTouches.length; i++) { + const index = this._currentTouches.findIndex(t => t.event.identifier === ev.changedTouches[i].identifier); + if (index !== -1) { + this._currentTouches[index].event = ev.changedTouches[i]; + this._currentTouches[index].status = "POINTER_UPDATE"; + } + } + } else if (ev.type === "touchend" || ev.type === "touchcancel") { + for (let i = 0; i < ev.changedTouches.length; i++) { + const index = this._currentTouches.findIndex(t => t.event.identifier === ev.changedTouches[i].identifier); + if (index !== -1) { + this._currentTouches[index].status = "POINTER_UP"; + } + } + } + } + + _dispatchTouchEvent(ev) { + let tev = new CustomEvent('ultravnctouch', { event: ev, detail: { currentTouches: this._currentTouches, giiDeviceOrigin: this._giiDeviceOrigin } }); + this._target.dispatchEvent(tev); + } + + _removeTouch(index) { + this._currentTouches.splice(index, 1); + } + + _interruptTouches() { + clearInterval(this._sendTouchesIntervalId); + this._sendTouchesIntervalId = -1; + } +} \ No newline at end of file diff --git a/core/rfb.js b/core/rfb.js index f2deb0e7..b45b5172 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -19,6 +19,7 @@ import Inflator from "./inflator.js"; import Deflator from "./deflator.js"; import Keyboard from "./input/keyboard.js"; import GestureHandler from "./input/gesturehandler.js"; +import TouchHandlerUltraVNC from "./input/touchhandlerultravnc.js"; import Cursor from "./util/cursor.js"; import Websock from "./websock.js"; import KeyTable from "./input/keysym.js"; @@ -117,6 +118,7 @@ export default class RFB extends EventTargetMixin { this._shared = 'shared' in options ? !!options.shared : true; this._repeaterID = options.repeaterID || ''; this._wsProtocols = options.wsProtocols || []; + this._useUltraVNCGestures = options.useUltraVNCGestures || false; // Internal state this._rfbConnectionState = ''; @@ -202,6 +204,7 @@ export default class RFB extends EventTargetMixin { handleMouse: this._handleMouse.bind(this), handleWheel: this._handleWheel.bind(this), handleGesture: this._handleGesture.bind(this), + handleUltraVNCTouch: this._handleUltraVNCTouch.bind(this), handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this), handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this), }; @@ -263,7 +266,11 @@ export default class RFB extends EventTargetMixin { this._remoteCapsLock = null; // Null indicates unknown or irrelevant this._remoteNumLock = null; - this._gestures = new GestureHandler(); + if (this._useUltraVNCGestures) { + this._gestures = new TouchHandlerUltraVNC(); + } else { + this._gestures = new GestureHandler(); + } this._sock = new Websock(); this._sock.on('open', this._socketOpen.bind(this)); @@ -594,9 +601,13 @@ export default class RFB extends EventTargetMixin { this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel); // Gesture events - this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture); - this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture); - this._canvas.addEventListener("gestureend", this._eventHandlers.handleGesture); + if (this._useUltraVNCGestures) { + this._canvas.addEventListener('ultravnctouch', this._eventHandlers.handleUltraVNCTouch); + } else { + this._canvas.addEventListener('gesturestart', this._eventHandlers.handleGesture); + this._canvas.addEventListener('gesturemove', this._eventHandlers.handleGesture); + this._canvas.addEventListener('gestureend', this._eventHandlers.handleGesture); + } Log.Debug("<< RFB.connect"); } @@ -604,9 +615,13 @@ export default class RFB extends EventTargetMixin { _disconnect() { Log.Debug(">> RFB.disconnect"); this._cursor.detach(); - this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture); - this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture); - this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture); + if (this._useUltraVNCGestures) { + this._canvas.removeEventListener('ultravnctouch', this._eventHandlers.handleUltraVNCTouch); + } else { + this._canvas.removeEventListener('gesturestart', this._eventHandlers.handleGesture); + this._canvas.removeEventListener('gesturemove', this._eventHandlers.handleGesture); + this._canvas.removeEventListener('gestureend', this._eventHandlers.handleGesture); + } this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel); this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse); this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse); @@ -1374,6 +1389,87 @@ export default class RFB extends EventTargetMixin { } } + _handleUltraVNCTouch(ev) { + Log.Debug("SENDING " + ev.detail.currentTouches.length + " TOUCH(ES)"); + this._sock.sQpush8(253); // GII message type + this._sock.sQpush8(128); // GII event + this._sock.sQpush16(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // Length + this._sock.sQpush8(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // eventSize + this._sock.sQpush8(12); // eventType + this._sock.sQpush16(0); // padding + this._sock.sQpush32(ev.detail.giiDeviceOrigin); // deviceOrigin + this._sock.sQpush32(ev.detail.currentTouches.length); // first + this._sock.sQpush32(6 * ev.detail.currentTouches.length); // count + + let pointerUpIds = []; + + // Send all current touches + for (let i = 0; i < ev.detail.currentTouches.length; i++) { + Log.Debug("Touch Id: " + ev.detail.currentTouches[i].event.identifier); + let valuatorFlag = 0x00000000; + valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; + valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_32; + if (ev.detail.currentTouches[i].status !== "POINTER_UP") valuatorFlag |= TouchHandlerUltraVNC.PF_flag; + if (ev.detail.currentTouches[i].event.identifier === 0) valuatorFlag |= TouchHandlerUltraVNC.IF_flag; // IF_flag + + this._sock.sQpush32(valuatorFlag); + this._sock.sQpush32(ev.detail.currentTouches[i].event.identifier); + + let scaledPosition = clientToElement(ev.detail.currentTouches[i].event.clientX, ev.detail.currentTouches[i].event.clientY, + this._canvas); + + if ((valuatorFlag & TouchHandlerUltraVNC.LENGTH_16_flag) !== 0) { + let scaledX16 = Math.floor(scaledPosition.x) & 0xFFFF; + let scaledY16 = Math.floor(scaledPosition.y) & 0xFFFF; + let coordinates = (Math.floor(scaledX16) << 16) | (Math.floor(scaledY16)); + this._sock.sQpush32(coordinates); + } + + // Keep track of last released touches + if (ev.detail.currentTouches[i].status === "POINTER_UP") { + pointerUpIds.push(ev.detail.currentTouches[i].event.identifier); + } + } + + this._sock.flush(); + + // Remove released touches from current touches in handler + for (let i = 0; i < pointerUpIds.length; i++) { + const index = ev.detail.currentTouches.findIndex(t => t.event.identifier === pointerUpIds[i]); + if (index !== -1) { + this._gestures._removeTouch(index); + } + } + + // Interrupt touch sending interval + if (ev.detail.currentTouches.length === 0 && this._sendTouchesIntervalId !== -1) { + Log.Debug("NO MORE TOUCHES\n"); + this._gestures._interruptTouches(); + this._sendEmptyTouch(ev.detail.giiDeviceOrigin); + return; + } + } + + _sendEmptyTouch(giiDeviceOrigin) { + let valuatorFlag = 0x00000000; + valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; + valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_CLEAR; + + this._sock.sQpush8(253); // GII message type + this._sock.sQpush8(128); // GII event + this._sock.sQpush16(24); // Header length + this._sock.sQpush8(24); // Event size + this._sock.sQpush8(12); // eventType + this._sock.sQpush16(0); // padding + this._sock.sQpush32(giiDeviceOrigin); // deviceOrigin + this._sock.sQpush32(1); // first + this._sock.sQpush32(4); // Count + this._sock.sQpush32(valuatorFlag); // Flag + this._sock.sQpush32(0); // Empty Id + this._sock.sQpush32(0); // Empty coordinates + this._sock.flush(); + } + // Message Handlers _negotiateProtocolVersion() { @@ -2139,6 +2235,10 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.pseudoEncodingDesktopName); encs.push(encodings.pseudoEncodingExtendedClipboard); + if (this._useUltraVNCGestures) { + encs.push(encodings.pseudoEncodingGii); + } + if (this._fbDepth == 24) { encs.push(encodings.pseudoEncodingVMwareCursor); encs.push(encodings.pseudoEncodingCursor); @@ -2434,6 +2534,25 @@ export default class RFB extends EventTargetMixin { return true; } + _handleGiiMsg() { + let giiMsgSubtype = this._sock.rQshift8(); + + switch (giiMsgSubtype) { + case 129: // GII Version Message + this._sock.rQskipBytes(34); + RFB.messages.giiVersionMessage(this._sock); + RFB.messages.giiDeviceCreation(this._sock); + break; + case 130: // GII Device Creation + this._sock.rQshiftBytes(2); + this._gestures._giiDeviceOrigin = this._sock.rQshift32(); + if (this._gestures._giiDeviceOrigin) this._gestures._isUltraVNCTouchActivated = true; + break; + } + + return true; + } + _normalMsg() { let msgType; if (this._FBU.rects > 0) { @@ -2485,6 +2604,9 @@ export default class RFB extends EventTargetMixin { case 250: // XVP return this._handleXvpMsg(); + case 253: // GII + return this._handleGiiMsg(); + default: this._fail("Unexpected server message (type " + msgType + ")"); Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30)); @@ -2933,6 +3055,16 @@ export default class RFB extends EventTargetMixin { "raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]); return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge); } + + static stringAsByteArrayWithPadding(str, size) { + let full = new Uint8Array(size); + let utf8Encode = new TextEncoder(); + let strArray = utf8Encode.encode(str); + for (let i = 0; i < strArray.length; i++) { + full[i] = strArray[i]; + } + return full; + } } // Class Methods @@ -3224,6 +3356,44 @@ RFB.messages = { sock.sQpush8(ver); sock.sQpush8(op); + sock.flush(); + }, + + giiVersionMessage(sock) { + sock.sQpush8(253); // gii msg-type + sock.sQpush8(129); // gii version sub-msg-type + sock.sQpush16(2); // length + sock.sQpush16(1); // version + + sock.flush(); + }, + + giiDeviceCreation(sock) { + sock.sQpush8(253); // gii msg-type + sock.sQpush8(130); // gii device creation sub-msg-type + sock.sQpush16(172); // length + sock.sQpushBytes(RFB.stringAsByteArrayWithPadding("NOVNC-MT", 31)); // device name + sock.sQpush8(0); // DNTerm + sock.sQpush32(0x0908); // vendorID + sock.sQpush32(0x000b); // productID + sock.sQpush32(0x00002000); // eventMask + sock.sQpush32(0); // numRegisters + sock.sQpush32(1); // numValuators + sock.sQpush32(5); // numButtons + sock.sQpush32(0); // index + sock.sQpushBytes(RFB.stringAsByteArrayWithPadding("NOVNC Multitouch Device", 74)); // longName + sock.sQpush8(0); // LNTerm + sock.sQpushBytes(RFB.stringAsByteArrayWithPadding("NMD", 4)); // shortName + sock.sQpush8(0); // SNTerm + sock.sQpush32(0); // rangeMin + sock.sQpush32(0); // rangeCenter + sock.sQpush32(0); // rangeMax + sock.sQpush32(0); // SIUnit + sock.sQpush32(0); // SIAdd + sock.sQpush32(0); // SIMul + sock.sQpush32(0); // SIDiv + sock.sQpush32(0); // SIShift + sock.flush(); } }; diff --git a/vnc.html b/vnc.html index 24a118db..6209aa65 100644 --- a/vnc.html +++ b/vnc.html @@ -220,6 +220,7 @@

  • +

  • From c1efe1f9871d1c1f9bad342cbde1f170958e7ade Mon Sep 17 00:00:00 2001 From: Rui Reis Date: Wed, 31 Jul 2024 15:44:55 +0200 Subject: [PATCH 2/9] Fix local scaling touch position --- core/rfb.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index b45b5172..2c58471d 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1419,9 +1419,9 @@ export default class RFB extends EventTargetMixin { this._canvas); if ((valuatorFlag & TouchHandlerUltraVNC.LENGTH_16_flag) !== 0) { - let scaledX16 = Math.floor(scaledPosition.x) & 0xFFFF; - let scaledY16 = Math.floor(scaledPosition.y) & 0xFFFF; - let coordinates = (Math.floor(scaledX16) << 16) | (Math.floor(scaledY16)); + let scaledX16 = this._display.absX(scaledPosition.x) & 0xFFFF; + let scaledY16 = this._display.absY(scaledPosition.y) & 0xFFFF; + let coordinates = (scaledX16 << 16) | scaledY16; this._sock.sQpush32(coordinates); } @@ -2535,15 +2535,20 @@ export default class RFB extends EventTargetMixin { } _handleGiiMsg() { + if (this._sock.rQwait("GII message subtype", 1, 1)) { + return false; + } let giiMsgSubtype = this._sock.rQshift8(); switch (giiMsgSubtype) { case 129: // GII Version Message + if (this._sock.rQwait("GII version message", 34, 1)) { return false; } this._sock.rQskipBytes(34); RFB.messages.giiVersionMessage(this._sock); RFB.messages.giiDeviceCreation(this._sock); break; case 130: // GII Device Creation + if (this._sock.rQwait("GII device creation", 6, 1)) { return false; } this._sock.rQshiftBytes(2); this._gestures._giiDeviceOrigin = this._sock.rQshift32(); if (this._gestures._giiDeviceOrigin) this._gestures._isUltraVNCTouchActivated = true; From 6b770c9da26e47454416e5d49a16e2365c8d9f23 Mon Sep 17 00:00:00 2001 From: Rui Reis Date: Wed, 31 Jul 2024 15:45:33 +0200 Subject: [PATCH 3/9] Fix touches with same id not being removed properly --- core/input/touchhandlerultravnc.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/core/input/touchhandlerultravnc.js b/core/input/touchhandlerultravnc.js index c9ccac91..05fba0ef 100644 --- a/core/input/touchhandlerultravnc.js +++ b/core/input/touchhandlerultravnc.js @@ -88,14 +88,20 @@ export default class TouchHandlerUltraVNC { } } else if (ev.type === "touchend" || ev.type === "touchcancel") { for (let i = 0; i < ev.changedTouches.length; i++) { - const index = this._currentTouches.findIndex(t => t.event.identifier === ev.changedTouches[i].identifier); - if (index !== -1) { - this._currentTouches[index].status = "POINTER_UP"; - } + const indexes = this._getAllIndexes(this._currentTouches, (t) => t.event.identifier === ev.changedTouches[i].identifier) + indexes.forEach((index) => this._currentTouches[index].status = "POINTER_UP"); } } } + _getAllIndexes(arr, func) { + var indexes = [], i; + for (i = 0; i < arr.length; i++) + if (func(arr[i])) + indexes.push(i); + return indexes; + } + _dispatchTouchEvent(ev) { let tev = new CustomEvent('ultravnctouch', { event: ev, detail: { currentTouches: this._currentTouches, giiDeviceOrigin: this._giiDeviceOrigin } }); this._target.dispatchEvent(tev); From 43ddc6c982114dbe1299539f908aa3783b19d4a0 Mon Sep 17 00:00:00 2001 From: Rui Reis Date: Tue, 24 Sep 2024 09:23:45 +0200 Subject: [PATCH 4/9] Add preventDefault in UltraVNC touch and manage touch ids manually --- core/input/touchhandlerultravnc.js | 12 +++++++++++- core/rfb.js | 4 +--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/core/input/touchhandlerultravnc.js b/core/input/touchhandlerultravnc.js index 05fba0ef..48bb0015 100644 --- a/core/input/touchhandlerultravnc.js +++ b/core/input/touchhandlerultravnc.js @@ -61,7 +61,8 @@ export default class TouchHandlerUltraVNC { } _handleTouch(ev) { - Log.Debug("Gesture: " + ev.type); + ev.preventDefault(); + ev.stopImmediatePropagation(); if (!this._isUltraVNCTouchActivated) { return; @@ -69,6 +70,7 @@ export default class TouchHandlerUltraVNC { if (ev.type === "touchstart") { for (let i = 0; i < ev.changedTouches.length; i++) { + ev.changedTouches[i].touchIdentifier = this._getTouchIdentifier(); this._currentTouches.push({ event: ev.changedTouches[i], status: "POINTER_DOWN" }); } @@ -82,6 +84,7 @@ export default class TouchHandlerUltraVNC { for (let i = 0; i < ev.changedTouches.length; i++) { const index = this._currentTouches.findIndex(t => t.event.identifier === ev.changedTouches[i].identifier); if (index !== -1) { + ev.changedTouches[i].touchIdentifier = this._currentTouches[index].event.touchIdentifier; this._currentTouches[index].event = ev.changedTouches[i]; this._currentTouches[index].status = "POINTER_UPDATE"; } @@ -102,6 +105,13 @@ export default class TouchHandlerUltraVNC { return indexes; } + _getTouchIdentifier() { + const ids = this._currentTouches.map((ev) => ev.event.touchIdentifier); + let i = 0; + while (ids.includes(i)) { i++; } + return i; + } + _dispatchTouchEvent(ev) { let tev = new CustomEvent('ultravnctouch', { event: ev, detail: { currentTouches: this._currentTouches, giiDeviceOrigin: this._giiDeviceOrigin } }); this._target.dispatchEvent(tev); diff --git a/core/rfb.js b/core/rfb.js index 2c58471d..8129ecf6 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1390,7 +1390,6 @@ export default class RFB extends EventTargetMixin { } _handleUltraVNCTouch(ev) { - Log.Debug("SENDING " + ev.detail.currentTouches.length + " TOUCH(ES)"); this._sock.sQpush8(253); // GII message type this._sock.sQpush8(128); // GII event this._sock.sQpush16(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // Length @@ -1405,7 +1404,6 @@ export default class RFB extends EventTargetMixin { // Send all current touches for (let i = 0; i < ev.detail.currentTouches.length; i++) { - Log.Debug("Touch Id: " + ev.detail.currentTouches[i].event.identifier); let valuatorFlag = 0x00000000; valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_32; @@ -1413,7 +1411,7 @@ export default class RFB extends EventTargetMixin { if (ev.detail.currentTouches[i].event.identifier === 0) valuatorFlag |= TouchHandlerUltraVNC.IF_flag; // IF_flag this._sock.sQpush32(valuatorFlag); - this._sock.sQpush32(ev.detail.currentTouches[i].event.identifier); + this._sock.sQpush32(ev.detail.currentTouches[i].event.touchIdentifier); let scaledPosition = clientToElement(ev.detail.currentTouches[i].event.clientX, ev.detail.currentTouches[i].event.clientY, this._canvas); From 663a198ad5a9384aa926698835c899345c4b4ac1 Mon Sep 17 00:00:00 2001 From: Rui Reis Date: Tue, 28 May 2024 14:41:15 +0200 Subject: [PATCH 5/9] Add UltraVNC touch gestures support --- app/ui.js | 7 +- core/encodings.js | 1 + core/input/touchhandlerultravnc.js | 112 ++++++++++++++++++ core/rfb.js | 184 +++++++++++++++++++++++++++-- vnc.html | 1 + 5 files changed, 297 insertions(+), 8 deletions(-) create mode 100644 core/input/touchhandlerultravnc.js diff --git a/app/ui.js b/app/ui.js index fd23c800..adab59a8 100644 --- a/app/ui.js +++ b/app/ui.js @@ -185,6 +185,7 @@ const UI = { UI.initSetting('bell', 'on'); UI.initSetting('view_only', false); UI.initSetting('show_dot', false); + UI.initSetting('ultravnc_gestures', false); UI.initSetting('path', 'websockify'); UI.initSetting('repeaterID', ''); UI.initSetting('reconnect', false); @@ -371,6 +372,7 @@ const UI = { UI.addSettingChangeHandler('view_only', UI.updateViewOnly); UI.addSettingChangeHandler('show_dot'); UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor); + UI.addSettingChangeHandler('ultravnc_gestures'); UI.addSettingChangeHandler('host'); UI.addSettingChangeHandler('port'); UI.addSettingChangeHandler('path'); @@ -441,6 +443,7 @@ const UI = { UI.disableSetting('port'); UI.disableSetting('path'); UI.disableSetting('repeaterID'); + UI.disableSetting('ultravnc_gestures'); // Hide the controlbar after 2 seconds UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000); @@ -451,6 +454,7 @@ const UI = { UI.enableSetting('port'); UI.enableSetting('path'); UI.enableSetting('repeaterID'); + UI.enableSetting('ultravnc_gestures'); UI.updatePowerButton(); UI.keepControlbar(); } @@ -1072,7 +1076,8 @@ const UI = { url.href, { shared: UI.getSetting('shared'), repeaterID: UI.getSetting('repeaterID'), - credentials: { password: password } }); + credentials: { password: password }, + useUltraVNCGestures: UI.getSetting('ultravnc_gestures') }); } catch (exc) { Log.Error("Failed to connect to server: " + exc); UI.updateVisualState('disconnected'); diff --git a/core/encodings.js b/core/encodings.js index bf25ac91..27a635da 100644 --- a/core/encodings.js +++ b/core/encodings.js @@ -25,6 +25,7 @@ export const encodings = { pseudoEncodingCursor: -239, pseudoEncodingQEMUExtendedKeyEvent: -258, pseudoEncodingQEMULedEvent: -261, + pseudoEncodingGii: -305, pseudoEncodingDesktopName: -307, pseudoEncodingExtendedDesktopSize: -308, pseudoEncodingXvp: -309, diff --git a/core/input/touchhandlerultravnc.js b/core/input/touchhandlerultravnc.js new file mode 100644 index 00000000..c9ccac91 --- /dev/null +++ b/core/input/touchhandlerultravnc.js @@ -0,0 +1,112 @@ +import * as Log from '../util/logging.js'; + +export default class TouchHandlerUltraVNC { + static PF_flag = 0x80000000; // Pressed Flag : active if the touch event is pressed, inactive if it's being released. + static R1_flag = 0x40000000; // Reserved 1 + static IF_flag = 0x20000000; // Primary Flag : active if the touch event is the primary touch event. + static S1_flag = 0x10000000; // Size Flag : active if the message contains information about the size of the touch event. The events are currently all sent as symetrical ellipses. + static S2_flag = 0x8000000; // Reserved for asymetrical ellipses. Not supported yet and should be 0. + static RT_flag = 0x4000000; // Rectangle : the touch event is a rectangle instead of an ellipse. + static PR_flag = 0x2000000; // Pressure Flag : pressure of the touch. Currently unused. + static TI_flag = 0x1000000; // Timestamp : the timestamp of the touch event. + static HC_flag = 0x800000; // High Performance Counter + + static LENGTH_16_flag = 0x10; // 16 bits signed for x touch coordinate followed by 16 bits signed for y together in a 32 bits word + static IDFORMAT_32 = 0x1; // 32 bits ID + static IDFORMAT_CLEAR = 0xF; // No more touch points + + constructor() { + this._target = null; + + this._currentTouches = []; + this._sendTouchesIntervalId = -1; + this._giiDeviceOrigin = 0; + this._isUltraVNCTouchActivated = false; + + this._boundEventHandler = this._handleTouch.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._target.removeEventListener('touchstart', + this._boundEventHandler); + this._target.removeEventListener('touchmove', + this._boundEventHandler); + this._target.removeEventListener('touchend', + this._boundEventHandler); + this._target.removeEventListener('touchcancel', + this._boundEventHandler); + + clearInterval(this._sendTouchesIntervalId); + this._sendTouchesIntervalId = -1; + + this._target = null; + } + + _handleTouch(ev) { + Log.Debug("Gesture: " + ev.type); + + if (!this._isUltraVNCTouchActivated) { + return; + } + + if (ev.type === "touchstart") { + for (let i = 0; i < ev.changedTouches.length; i++) { + this._currentTouches.push({ event: ev.changedTouches[i], status: "POINTER_DOWN" }); + } + + if (this._sendTouchesIntervalId === -1 && this._target) { + this._dispatchTouchEvent(ev); + this._sendTouchesIntervalId = setInterval(() => { + this._dispatchTouchEvent(ev); + }, 200); + } + } else if (ev.type === "touchmove") { + for (let i = 0; i < ev.changedTouches.length; i++) { + const index = this._currentTouches.findIndex(t => t.event.identifier === ev.changedTouches[i].identifier); + if (index !== -1) { + this._currentTouches[index].event = ev.changedTouches[i]; + this._currentTouches[index].status = "POINTER_UPDATE"; + } + } + } else if (ev.type === "touchend" || ev.type === "touchcancel") { + for (let i = 0; i < ev.changedTouches.length; i++) { + const index = this._currentTouches.findIndex(t => t.event.identifier === ev.changedTouches[i].identifier); + if (index !== -1) { + this._currentTouches[index].status = "POINTER_UP"; + } + } + } + } + + _dispatchTouchEvent(ev) { + let tev = new CustomEvent('ultravnctouch', { event: ev, detail: { currentTouches: this._currentTouches, giiDeviceOrigin: this._giiDeviceOrigin } }); + this._target.dispatchEvent(tev); + } + + _removeTouch(index) { + this._currentTouches.splice(index, 1); + } + + _interruptTouches() { + clearInterval(this._sendTouchesIntervalId); + this._sendTouchesIntervalId = -1; + } +} \ No newline at end of file diff --git a/core/rfb.js b/core/rfb.js index 9559e487..995ab798 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -19,6 +19,7 @@ import Inflator from "./inflator.js"; import Deflator from "./deflator.js"; import Keyboard from "./input/keyboard.js"; import GestureHandler from "./input/gesturehandler.js"; +import TouchHandlerUltraVNC from "./input/touchhandlerultravnc.js"; import Cursor from "./util/cursor.js"; import Websock from "./websock.js"; import KeyTable from "./input/keysym.js"; @@ -119,6 +120,7 @@ export default class RFB extends EventTargetMixin { this._shared = 'shared' in options ? !!options.shared : true; this._repeaterID = options.repeaterID || ''; this._wsProtocols = options.wsProtocols || []; + this._useUltraVNCGestures = options.useUltraVNCGestures || false; // Internal state this._rfbConnectionState = ''; @@ -204,6 +206,7 @@ export default class RFB extends EventTargetMixin { handleMouse: this._handleMouse.bind(this), handleWheel: this._handleWheel.bind(this), handleGesture: this._handleGesture.bind(this), + handleUltraVNCTouch: this._handleUltraVNCTouch.bind(this), handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this), handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this), }; @@ -267,7 +270,11 @@ export default class RFB extends EventTargetMixin { this._remoteCapsLock = null; // Null indicates unknown or irrelevant this._remoteNumLock = null; - this._gestures = new GestureHandler(); + if (this._useUltraVNCGestures) { + this._gestures = new TouchHandlerUltraVNC(); + } else { + this._gestures = new GestureHandler(); + } this._sock = new Websock(); this._sock.on('open', this._socketOpen.bind(this)); @@ -598,9 +605,13 @@ export default class RFB extends EventTargetMixin { this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel); // Gesture events - this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture); - this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture); - this._canvas.addEventListener("gestureend", this._eventHandlers.handleGesture); + if (this._useUltraVNCGestures) { + this._canvas.addEventListener('ultravnctouch', this._eventHandlers.handleUltraVNCTouch); + } else { + this._canvas.addEventListener('gesturestart', this._eventHandlers.handleGesture); + this._canvas.addEventListener('gesturemove', this._eventHandlers.handleGesture); + this._canvas.addEventListener('gestureend', this._eventHandlers.handleGesture); + } Log.Debug("<< RFB.connect"); } @@ -608,9 +619,13 @@ export default class RFB extends EventTargetMixin { _disconnect() { Log.Debug(">> RFB.disconnect"); this._cursor.detach(); - this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture); - this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture); - this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture); + if (this._useUltraVNCGestures) { + this._canvas.removeEventListener('ultravnctouch', this._eventHandlers.handleUltraVNCTouch); + } else { + this._canvas.removeEventListener('gesturestart', this._eventHandlers.handleGesture); + this._canvas.removeEventListener('gesturemove', this._eventHandlers.handleGesture); + this._canvas.removeEventListener('gestureend', this._eventHandlers.handleGesture); + } this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel); this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse); this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse); @@ -1378,6 +1393,87 @@ export default class RFB extends EventTargetMixin { } } + _handleUltraVNCTouch(ev) { + Log.Debug("SENDING " + ev.detail.currentTouches.length + " TOUCH(ES)"); + this._sock.sQpush8(253); // GII message type + this._sock.sQpush8(128); // GII event + this._sock.sQpush16(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // Length + this._sock.sQpush8(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // eventSize + this._sock.sQpush8(12); // eventType + this._sock.sQpush16(0); // padding + this._sock.sQpush32(ev.detail.giiDeviceOrigin); // deviceOrigin + this._sock.sQpush32(ev.detail.currentTouches.length); // first + this._sock.sQpush32(6 * ev.detail.currentTouches.length); // count + + let pointerUpIds = []; + + // Send all current touches + for (let i = 0; i < ev.detail.currentTouches.length; i++) { + Log.Debug("Touch Id: " + ev.detail.currentTouches[i].event.identifier); + let valuatorFlag = 0x00000000; + valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; + valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_32; + if (ev.detail.currentTouches[i].status !== "POINTER_UP") valuatorFlag |= TouchHandlerUltraVNC.PF_flag; + if (ev.detail.currentTouches[i].event.identifier === 0) valuatorFlag |= TouchHandlerUltraVNC.IF_flag; // IF_flag + + this._sock.sQpush32(valuatorFlag); + this._sock.sQpush32(ev.detail.currentTouches[i].event.identifier); + + let scaledPosition = clientToElement(ev.detail.currentTouches[i].event.clientX, ev.detail.currentTouches[i].event.clientY, + this._canvas); + + if ((valuatorFlag & TouchHandlerUltraVNC.LENGTH_16_flag) !== 0) { + let scaledX16 = Math.floor(scaledPosition.x) & 0xFFFF; + let scaledY16 = Math.floor(scaledPosition.y) & 0xFFFF; + let coordinates = (Math.floor(scaledX16) << 16) | (Math.floor(scaledY16)); + this._sock.sQpush32(coordinates); + } + + // Keep track of last released touches + if (ev.detail.currentTouches[i].status === "POINTER_UP") { + pointerUpIds.push(ev.detail.currentTouches[i].event.identifier); + } + } + + this._sock.flush(); + + // Remove released touches from current touches in handler + for (let i = 0; i < pointerUpIds.length; i++) { + const index = ev.detail.currentTouches.findIndex(t => t.event.identifier === pointerUpIds[i]); + if (index !== -1) { + this._gestures._removeTouch(index); + } + } + + // Interrupt touch sending interval + if (ev.detail.currentTouches.length === 0 && this._sendTouchesIntervalId !== -1) { + Log.Debug("NO MORE TOUCHES\n"); + this._gestures._interruptTouches(); + this._sendEmptyTouch(ev.detail.giiDeviceOrigin); + return; + } + } + + _sendEmptyTouch(giiDeviceOrigin) { + let valuatorFlag = 0x00000000; + valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; + valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_CLEAR; + + this._sock.sQpush8(253); // GII message type + this._sock.sQpush8(128); // GII event + this._sock.sQpush16(24); // Header length + this._sock.sQpush8(24); // Event size + this._sock.sQpush8(12); // eventType + this._sock.sQpush16(0); // padding + this._sock.sQpush32(giiDeviceOrigin); // deviceOrigin + this._sock.sQpush32(1); // first + this._sock.sQpush32(4); // Count + this._sock.sQpush32(valuatorFlag); // Flag + this._sock.sQpush32(0); // Empty Id + this._sock.sQpush32(0); // Empty coordinates + this._sock.flush(); + } + // Message handlers _negotiateProtocolVersion() { @@ -2147,6 +2243,10 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.pseudoEncodingDesktopName); encs.push(encodings.pseudoEncodingExtendedClipboard); + if (this._useUltraVNCGestures) { + encs.push(encodings.pseudoEncodingGii); + } + if (this._fbDepth == 24) { encs.push(encodings.pseudoEncodingVMwareCursor); encs.push(encodings.pseudoEncodingCursor); @@ -2442,6 +2542,25 @@ export default class RFB extends EventTargetMixin { return true; } + _handleGiiMsg() { + let giiMsgSubtype = this._sock.rQshift8(); + + switch (giiMsgSubtype) { + case 129: // GII Version Message + this._sock.rQskipBytes(34); + RFB.messages.giiVersionMessage(this._sock); + RFB.messages.giiDeviceCreation(this._sock); + break; + case 130: // GII Device Creation + this._sock.rQshiftBytes(2); + this._gestures._giiDeviceOrigin = this._sock.rQshift32(); + if (this._gestures._giiDeviceOrigin) this._gestures._isUltraVNCTouchActivated = true; + break; + } + + return true; + } + _normalMsg() { let msgType; if (this._FBU.rects > 0) { @@ -2493,6 +2612,9 @@ export default class RFB extends EventTargetMixin { case 250: // XVP return this._handleXvpMsg(); + case 253: // GII + return this._handleGiiMsg(); + default: this._fail("Unexpected server message (type " + msgType + ")"); Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30)); @@ -2941,6 +3063,16 @@ export default class RFB extends EventTargetMixin { "raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]); return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge); } + + static stringAsByteArrayWithPadding(str, size) { + let full = new Uint8Array(size); + let utf8Encode = new TextEncoder(); + let strArray = utf8Encode.encode(str); + for (let i = 0; i < strArray.length; i++) { + full[i] = strArray[i]; + } + return full; + } } // Class Methods @@ -3232,6 +3364,44 @@ RFB.messages = { sock.sQpush8(ver); sock.sQpush8(op); + sock.flush(); + }, + + giiVersionMessage(sock) { + sock.sQpush8(253); // gii msg-type + sock.sQpush8(129); // gii version sub-msg-type + sock.sQpush16(2); // length + sock.sQpush16(1); // version + + sock.flush(); + }, + + giiDeviceCreation(sock) { + sock.sQpush8(253); // gii msg-type + sock.sQpush8(130); // gii device creation sub-msg-type + sock.sQpush16(172); // length + sock.sQpushBytes(RFB.stringAsByteArrayWithPadding("NOVNC-MT", 31)); // device name + sock.sQpush8(0); // DNTerm + sock.sQpush32(0x0908); // vendorID + sock.sQpush32(0x000b); // productID + sock.sQpush32(0x00002000); // eventMask + sock.sQpush32(0); // numRegisters + sock.sQpush32(1); // numValuators + sock.sQpush32(5); // numButtons + sock.sQpush32(0); // index + sock.sQpushBytes(RFB.stringAsByteArrayWithPadding("NOVNC Multitouch Device", 74)); // longName + sock.sQpush8(0); // LNTerm + sock.sQpushBytes(RFB.stringAsByteArrayWithPadding("NMD", 4)); // shortName + sock.sQpush8(0); // SNTerm + sock.sQpush32(0); // rangeMin + sock.sQpush32(0); // rangeCenter + sock.sQpush32(0); // rangeMax + sock.sQpush32(0); // SIUnit + sock.sQpush32(0); // SIAdd + sock.sQpush32(0); // SIMul + sock.sQpush32(0); // SIDiv + sock.sQpush32(0); // SIShift + sock.flush(); } }; diff --git a/vnc.html b/vnc.html index c2cc4e55..44c43718 100644 --- a/vnc.html +++ b/vnc.html @@ -270,6 +270,7 @@

  • +

  • From 8af21c7082a61496f3ed7b44d522ac3f9906dc91 Mon Sep 17 00:00:00 2001 From: Rui Reis Date: Wed, 31 Jul 2024 15:44:55 +0200 Subject: [PATCH 6/9] Fix local scaling touch position --- core/rfb.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 995ab798..ee9f5631 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1423,9 +1423,9 @@ export default class RFB extends EventTargetMixin { this._canvas); if ((valuatorFlag & TouchHandlerUltraVNC.LENGTH_16_flag) !== 0) { - let scaledX16 = Math.floor(scaledPosition.x) & 0xFFFF; - let scaledY16 = Math.floor(scaledPosition.y) & 0xFFFF; - let coordinates = (Math.floor(scaledX16) << 16) | (Math.floor(scaledY16)); + let scaledX16 = this._display.absX(scaledPosition.x) & 0xFFFF; + let scaledY16 = this._display.absY(scaledPosition.y) & 0xFFFF; + let coordinates = (scaledX16 << 16) | scaledY16; this._sock.sQpush32(coordinates); } @@ -2543,15 +2543,20 @@ export default class RFB extends EventTargetMixin { } _handleGiiMsg() { + if (this._sock.rQwait("GII message subtype", 1, 1)) { + return false; + } let giiMsgSubtype = this._sock.rQshift8(); switch (giiMsgSubtype) { case 129: // GII Version Message + if (this._sock.rQwait("GII version message", 34, 1)) { return false; } this._sock.rQskipBytes(34); RFB.messages.giiVersionMessage(this._sock); RFB.messages.giiDeviceCreation(this._sock); break; case 130: // GII Device Creation + if (this._sock.rQwait("GII device creation", 6, 1)) { return false; } this._sock.rQshiftBytes(2); this._gestures._giiDeviceOrigin = this._sock.rQshift32(); if (this._gestures._giiDeviceOrigin) this._gestures._isUltraVNCTouchActivated = true; From b673af9925e57828783b5e2ef5a54aa858436ad9 Mon Sep 17 00:00:00 2001 From: Rui Reis Date: Wed, 31 Jul 2024 15:45:33 +0200 Subject: [PATCH 7/9] Fix touches with same id not being removed properly --- core/input/touchhandlerultravnc.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/core/input/touchhandlerultravnc.js b/core/input/touchhandlerultravnc.js index c9ccac91..05fba0ef 100644 --- a/core/input/touchhandlerultravnc.js +++ b/core/input/touchhandlerultravnc.js @@ -88,14 +88,20 @@ export default class TouchHandlerUltraVNC { } } else if (ev.type === "touchend" || ev.type === "touchcancel") { for (let i = 0; i < ev.changedTouches.length; i++) { - const index = this._currentTouches.findIndex(t => t.event.identifier === ev.changedTouches[i].identifier); - if (index !== -1) { - this._currentTouches[index].status = "POINTER_UP"; - } + const indexes = this._getAllIndexes(this._currentTouches, (t) => t.event.identifier === ev.changedTouches[i].identifier) + indexes.forEach((index) => this._currentTouches[index].status = "POINTER_UP"); } } } + _getAllIndexes(arr, func) { + var indexes = [], i; + for (i = 0; i < arr.length; i++) + if (func(arr[i])) + indexes.push(i); + return indexes; + } + _dispatchTouchEvent(ev) { let tev = new CustomEvent('ultravnctouch', { event: ev, detail: { currentTouches: this._currentTouches, giiDeviceOrigin: this._giiDeviceOrigin } }); this._target.dispatchEvent(tev); From d78d8752dff364533a23cefc0d4f5fbb0806e14a Mon Sep 17 00:00:00 2001 From: Rui Reis Date: Tue, 24 Sep 2024 09:23:45 +0200 Subject: [PATCH 8/9] Add preventDefault in UltraVNC touch and manage touch ids manually --- core/input/touchhandlerultravnc.js | 12 +++++++++++- core/rfb.js | 4 +--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/core/input/touchhandlerultravnc.js b/core/input/touchhandlerultravnc.js index 05fba0ef..48bb0015 100644 --- a/core/input/touchhandlerultravnc.js +++ b/core/input/touchhandlerultravnc.js @@ -61,7 +61,8 @@ export default class TouchHandlerUltraVNC { } _handleTouch(ev) { - Log.Debug("Gesture: " + ev.type); + ev.preventDefault(); + ev.stopImmediatePropagation(); if (!this._isUltraVNCTouchActivated) { return; @@ -69,6 +70,7 @@ export default class TouchHandlerUltraVNC { if (ev.type === "touchstart") { for (let i = 0; i < ev.changedTouches.length; i++) { + ev.changedTouches[i].touchIdentifier = this._getTouchIdentifier(); this._currentTouches.push({ event: ev.changedTouches[i], status: "POINTER_DOWN" }); } @@ -82,6 +84,7 @@ export default class TouchHandlerUltraVNC { for (let i = 0; i < ev.changedTouches.length; i++) { const index = this._currentTouches.findIndex(t => t.event.identifier === ev.changedTouches[i].identifier); if (index !== -1) { + ev.changedTouches[i].touchIdentifier = this._currentTouches[index].event.touchIdentifier; this._currentTouches[index].event = ev.changedTouches[i]; this._currentTouches[index].status = "POINTER_UPDATE"; } @@ -102,6 +105,13 @@ export default class TouchHandlerUltraVNC { return indexes; } + _getTouchIdentifier() { + const ids = this._currentTouches.map((ev) => ev.event.touchIdentifier); + let i = 0; + while (ids.includes(i)) { i++; } + return i; + } + _dispatchTouchEvent(ev) { let tev = new CustomEvent('ultravnctouch', { event: ev, detail: { currentTouches: this._currentTouches, giiDeviceOrigin: this._giiDeviceOrigin } }); this._target.dispatchEvent(tev); diff --git a/core/rfb.js b/core/rfb.js index ee9f5631..050a2e35 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1394,7 +1394,6 @@ export default class RFB extends EventTargetMixin { } _handleUltraVNCTouch(ev) { - Log.Debug("SENDING " + ev.detail.currentTouches.length + " TOUCH(ES)"); this._sock.sQpush8(253); // GII message type this._sock.sQpush8(128); // GII event this._sock.sQpush16(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // Length @@ -1409,7 +1408,6 @@ export default class RFB extends EventTargetMixin { // Send all current touches for (let i = 0; i < ev.detail.currentTouches.length; i++) { - Log.Debug("Touch Id: " + ev.detail.currentTouches[i].event.identifier); let valuatorFlag = 0x00000000; valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_32; @@ -1417,7 +1415,7 @@ export default class RFB extends EventTargetMixin { if (ev.detail.currentTouches[i].event.identifier === 0) valuatorFlag |= TouchHandlerUltraVNC.IF_flag; // IF_flag this._sock.sQpush32(valuatorFlag); - this._sock.sQpush32(ev.detail.currentTouches[i].event.identifier); + this._sock.sQpush32(ev.detail.currentTouches[i].event.touchIdentifier); let scaledPosition = clientToElement(ev.detail.currentTouches[i].event.clientX, ev.detail.currentTouches[i].event.clientY, this._canvas); From ac919414159d6035c71cca68b5e72c6866a73b72 Mon Sep 17 00:00:00 2001 From: Rui Reis Date: Tue, 10 Dec 2024 14:14:32 +0100 Subject: [PATCH 9/9] Change gestures selector to dropdown in UI, use constants instead of hardcoded values for gii events --- app/ui.js | 10 +- core/input/touchhandlerultravnc.js | 42 ++++ core/rfb.js | 313 ++++++++--------------------- vnc.html | 6 +- 4 files changed, 133 insertions(+), 238 deletions(-) diff --git a/app/ui.js b/app/ui.js index adab59a8..87649258 100644 --- a/app/ui.js +++ b/app/ui.js @@ -185,7 +185,7 @@ const UI = { UI.initSetting('bell', 'on'); UI.initSetting('view_only', false); UI.initSetting('show_dot', false); - UI.initSetting('ultravnc_gestures', false); + UI.initSetting('gestures_mode', 'novnc'); UI.initSetting('path', 'websockify'); UI.initSetting('repeaterID', ''); UI.initSetting('reconnect', false); @@ -372,7 +372,7 @@ const UI = { UI.addSettingChangeHandler('view_only', UI.updateViewOnly); UI.addSettingChangeHandler('show_dot'); UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor); - UI.addSettingChangeHandler('ultravnc_gestures'); + UI.addSettingChangeHandler('gestures_mode'); UI.addSettingChangeHandler('host'); UI.addSettingChangeHandler('port'); UI.addSettingChangeHandler('path'); @@ -443,7 +443,7 @@ const UI = { UI.disableSetting('port'); UI.disableSetting('path'); UI.disableSetting('repeaterID'); - UI.disableSetting('ultravnc_gestures'); + UI.disableSetting('gestures_mode'); // Hide the controlbar after 2 seconds UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000); @@ -454,7 +454,7 @@ const UI = { UI.enableSetting('port'); UI.enableSetting('path'); UI.enableSetting('repeaterID'); - UI.enableSetting('ultravnc_gestures'); + UI.enableSetting('gestures_mode'); UI.updatePowerButton(); UI.keepControlbar(); } @@ -1077,7 +1077,7 @@ const UI = { { shared: UI.getSetting('shared'), repeaterID: UI.getSetting('repeaterID'), credentials: { password: password }, - useUltraVNCGestures: UI.getSetting('ultravnc_gestures') }); + gesturesMode: UI.getSetting('gestures_mode') }); } catch (exc) { Log.Error("Failed to connect to server: " + exc); UI.updateVisualState('disconnected'); diff --git a/core/input/touchhandlerultravnc.js b/core/input/touchhandlerultravnc.js index 48bb0015..f44a660e 100644 --- a/core/input/touchhandlerultravnc.js +++ b/core/input/touchhandlerultravnc.js @@ -15,6 +15,48 @@ export default class TouchHandlerUltraVNC { static IDFORMAT_32 = 0x1; // 32 bits ID static IDFORMAT_CLEAR = 0xF; // No more touch points + // GII + static giiMsgType = 253; + static giiEventInjectionMsgType = 128; + static giiDeviceVersionMsgType = 129; + static giiDeviceCreationMsgType = 130; + + static giiDeviceCreationMsgSize = 172; + static giiDeviceVersion = 1; + static giiDeviceVersionMsgSize = 2; + + static giiEventInjectionHeaderSize = 4; + static giiEventInjectionSize = this.giiEventInjectionHeaderSize + 16; + static giiEventInjectionTouchSize = 12; + static giiEventInjectionEventType = 12; + + static giiDeviceName = "NOVNC-MT"; + static giiDeviceNameSize = 31; + static giiDeviceLongName = "noVNC Multitouch Device"; + static giiDeviceLongNameSize = 74; + static giiDeviceShortName = "NMD"; + static giiDeviceShortNameSize = 4; + + static giiDNTerm = 0; + static giiVendorID = 0x0908; + static giiProductID = 0x000b; + static giiEventMask = 0x00002000; + static giiNumRegisters = 0; + static giiNumValuators = 1; + static giiNumButtons = 5; + static giiNumTouches = 6; + static giiIndex = 0; + static giiLNTerm = 0; + static giiSNTerm = 0; + static giiRangeMin = 0; + static giiRangeCenter = 0; + static giiRangeMax = 0; + static giiSIUnit = 0; + static giiSIAdd = 0; + static giiSIMul = 0; + static giiSIDiv = 0; + static giiSIShift = 0; + constructor() { this._target = null; diff --git a/core/rfb.js b/core/rfb.js index 3ee10522..cc38f32b 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -120,7 +120,7 @@ export default class RFB extends EventTargetMixin { this._shared = 'shared' in options ? !!options.shared : true; this._repeaterID = options.repeaterID || ''; this._wsProtocols = options.wsProtocols || []; - this._useUltraVNCGestures = options.useUltraVNCGestures || false; + this._gesturesMode = options.gesturesMode || '' // Internal state this._rfbConnectionState = ''; @@ -270,10 +270,12 @@ export default class RFB extends EventTargetMixin { this._remoteCapsLock = null; // Null indicates unknown or irrelevant this._remoteNumLock = null; - if (this._useUltraVNCGestures) { - this._gestures = new TouchHandlerUltraVNC(); - } else { - this._gestures = new GestureHandler(); + switch (this._gesturesMode) { + case 'ultravnc': + this._gestures = new TouchHandlerUltraVNC(); + break; + default: + this._gestures = new GestureHandler(); } this._sock = new Websock(); @@ -605,12 +607,14 @@ export default class RFB extends EventTargetMixin { this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel); // Gesture events - if (this._useUltraVNCGestures) { - this._canvas.addEventListener('ultravnctouch', this._eventHandlers.handleUltraVNCTouch); - } else { - this._canvas.addEventListener('gesturestart', this._eventHandlers.handleGesture); - this._canvas.addEventListener('gesturemove', this._eventHandlers.handleGesture); - this._canvas.addEventListener('gestureend', this._eventHandlers.handleGesture); + switch (this._gesturesMode) { + case 'ultravnc': + this._canvas.addEventListener('ultravnctouch', this._eventHandlers.handleUltraVNCTouch); + break; + default: + this._canvas.addEventListener('gesturestart', this._eventHandlers.handleGesture); + this._canvas.addEventListener('gesturemove', this._eventHandlers.handleGesture); + this._canvas.addEventListener('gestureend', this._eventHandlers.handleGesture); } Log.Debug("<< RFB.connect"); @@ -619,13 +623,17 @@ export default class RFB extends EventTargetMixin { _disconnect() { Log.Debug(">> RFB.disconnect"); this._cursor.detach(); - if (this._useUltraVNCGestures) { - this._canvas.removeEventListener('ultravnctouch', this._eventHandlers.handleUltraVNCTouch); - } else { - this._canvas.removeEventListener('gesturestart', this._eventHandlers.handleGesture); - this._canvas.removeEventListener('gesturemove', this._eventHandlers.handleGesture); - this._canvas.removeEventListener('gestureend', this._eventHandlers.handleGesture); + + switch (this._gesturesMode) { + case 'ultravnc': + this._canvas.removeEventListener('ultravnctouch', this._eventHandlers.handleUltraVNCTouch); + break; + default: + this._canvas.removeEventListener('gesturestart', this._eventHandlers.handleGesture); + this._canvas.removeEventListener('gesturemove', this._eventHandlers.handleGesture); + this._canvas.removeEventListener('gestureend', this._eventHandlers.handleGesture); } + this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel); this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse); this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse); @@ -1394,18 +1402,18 @@ export default class RFB extends EventTargetMixin { } _handleUltraVNCTouch(ev) { - this._sock.sQpush8(253); // GII message type - this._sock.sQpush8(128); // GII event - this._sock.sQpush16(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // Length - this._sock.sQpush8(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // eventSize - this._sock.sQpush8(12); // eventType + this._sock.sQpush8(TouchHandlerUltraVNC.giiMsgType); // GII message type + this._sock.sQpush8(TouchHandlerUltraVNC.giiEventInjectionMsgType); // GII event + this._sock.sQpush16(TouchHandlerUltraVNC.giiEventInjectionSize + (TouchHandlerUltraVNC.giiEventInjectionTouchSize * ev.detail.currentTouches.length)); // length, not used + this._sock.sQpush8(TouchHandlerUltraVNC.giiEventInjectionSize + (TouchHandlerUltraVNC.giiEventInjectionTouchSize * ev.detail.currentTouches.length)); // eventSize, not used + this._sock.sQpush8(TouchHandlerUltraVNC.giiEventInjectionEventType); // eventType, not used this._sock.sQpush16(0); // padding - this._sock.sQpush32(ev.detail.giiDeviceOrigin); // deviceOrigin - this._sock.sQpush32(ev.detail.currentTouches.length); // first - this._sock.sQpush32(6 * ev.detail.currentTouches.length); // count - + this._sock.sQpush32(ev.detail.giiDeviceOrigin); + this._sock.sQpush32(ev.detail.currentTouches.length); // nb of touch events + this._sock.sQpush32(TouchHandlerUltraVNC.giiNumTouches * ev.detail.currentTouches.length); // count + let pointerUpIds = []; - + // Send all current touches for (let i = 0; i < ev.detail.currentTouches.length; i++) { let valuatorFlag = 0x00000000; @@ -1413,28 +1421,28 @@ export default class RFB extends EventTargetMixin { valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_32; if (ev.detail.currentTouches[i].status !== "POINTER_UP") valuatorFlag |= TouchHandlerUltraVNC.PF_flag; if (ev.detail.currentTouches[i].event.identifier === 0) valuatorFlag |= TouchHandlerUltraVNC.IF_flag; // IF_flag - + this._sock.sQpush32(valuatorFlag); this._sock.sQpush32(ev.detail.currentTouches[i].event.touchIdentifier); - + let scaledPosition = clientToElement(ev.detail.currentTouches[i].event.clientX, ev.detail.currentTouches[i].event.clientY, this._canvas); - + if ((valuatorFlag & TouchHandlerUltraVNC.LENGTH_16_flag) !== 0) { let scaledX16 = this._display.absX(scaledPosition.x) & 0xFFFF; let scaledY16 = this._display.absY(scaledPosition.y) & 0xFFFF; let coordinates = (scaledX16 << 16) | scaledY16; this._sock.sQpush32(coordinates); } - + // Keep track of last released touches if (ev.detail.currentTouches[i].status === "POINTER_UP") { pointerUpIds.push(ev.detail.currentTouches[i].event.identifier); } } - + this._sock.flush(); - + // Remove released touches from current touches in handler for (let i = 0; i < pointerUpIds.length; i++) { const index = ev.detail.currentTouches.findIndex(t => t.event.identifier === pointerUpIds[i]); @@ -1442,10 +1450,9 @@ export default class RFB extends EventTargetMixin { this._gestures._removeTouch(index); } } - + // Interrupt touch sending interval if (ev.detail.currentTouches.length === 0 && this._sendTouchesIntervalId !== -1) { - Log.Debug("NO MORE TOUCHES\n"); this._gestures._interruptTouches(); this._sendEmptyTouch(ev.detail.giiDeviceOrigin); return; @@ -1457,176 +1464,18 @@ export default class RFB extends EventTargetMixin { valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_CLEAR; - this._sock.sQpush8(253); // GII message type - this._sock.sQpush8(128); // GII event - this._sock.sQpush16(24); // Header length - this._sock.sQpush8(24); // Event size - this._sock.sQpush8(12); // eventType + this._sock.sQpush8(TouchHandlerUltraVNC.giiMsgType); // GII message type + this._sock.sQpush8(TouchHandlerUltraVNC.giiEventInjectionMsgType); // GII event + this._sock.sQpush16(TouchHandlerUltraVNC.giiEventInjectionHeaderSize + TouchHandlerUltraVNC.giiEventInjectionSize); + this._sock.sQpush8(TouchHandlerUltraVNC.giiEventInjectionHeaderSize + TouchHandlerUltraVNC.giiEventInjectionSize); + this._sock.sQpush8(TouchHandlerUltraVNC.giiEventInjectionEventType); this._sock.sQpush16(0); // padding this._sock.sQpush32(giiDeviceOrigin); // deviceOrigin - this._sock.sQpush32(1); // first - this._sock.sQpush32(4); // Count - this._sock.sQpush32(valuatorFlag); // Flag - this._sock.sQpush32(0); // Empty Id - this._sock.sQpush32(0); // Empty coordinates - this._sock.flush(); - } - - _handleUltraVNCTouch(ev) { - this._sock.sQpush8(253); // GII message type - this._sock.sQpush8(128); // GII event - this._sock.sQpush16(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // Length - this._sock.sQpush8(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // eventSize - this._sock.sQpush8(12); // eventType - this._sock.sQpush16(0); // padding - this._sock.sQpush32(ev.detail.giiDeviceOrigin); // deviceOrigin - this._sock.sQpush32(ev.detail.currentTouches.length); // first - this._sock.sQpush32(6 * ev.detail.currentTouches.length); // count - - let pointerUpIds = []; - - // Send all current touches - for (let i = 0; i < ev.detail.currentTouches.length; i++) { - let valuatorFlag = 0x00000000; - valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; - valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_32; - if (ev.detail.currentTouches[i].status !== "POINTER_UP") valuatorFlag |= TouchHandlerUltraVNC.PF_flag; - if (ev.detail.currentTouches[i].event.identifier === 0) valuatorFlag |= TouchHandlerUltraVNC.IF_flag; // IF_flag - - this._sock.sQpush32(valuatorFlag); - this._sock.sQpush32(ev.detail.currentTouches[i].event.touchIdentifier); - - let scaledPosition = clientToElement(ev.detail.currentTouches[i].event.clientX, ev.detail.currentTouches[i].event.clientY, - this._canvas); - - if ((valuatorFlag & TouchHandlerUltraVNC.LENGTH_16_flag) !== 0) { - let scaledX16 = this._display.absX(scaledPosition.x) & 0xFFFF; - let scaledY16 = this._display.absY(scaledPosition.y) & 0xFFFF; - let coordinates = (scaledX16 << 16) | scaledY16; - this._sock.sQpush32(coordinates); - } - - // Keep track of last released touches - if (ev.detail.currentTouches[i].status === "POINTER_UP") { - pointerUpIds.push(ev.detail.currentTouches[i].event.identifier); - } - } - - this._sock.flush(); - - // Remove released touches from current touches in handler - for (let i = 0; i < pointerUpIds.length; i++) { - const index = ev.detail.currentTouches.findIndex(t => t.event.identifier === pointerUpIds[i]); - if (index !== -1) { - this._gestures._removeTouch(index); - } - } - - // Interrupt touch sending interval - if (ev.detail.currentTouches.length === 0 && this._sendTouchesIntervalId !== -1) { - Log.Debug("NO MORE TOUCHES\n"); - this._gestures._interruptTouches(); - this._sendEmptyTouch(ev.detail.giiDeviceOrigin); - return; - } - } - - _sendEmptyTouch(giiDeviceOrigin) { - let valuatorFlag = 0x00000000; - valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; - valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_CLEAR; - - this._sock.sQpush8(253); // GII message type - this._sock.sQpush8(128); // GII event - this._sock.sQpush16(24); // Header length - this._sock.sQpush8(24); // Event size - this._sock.sQpush8(12); // eventType - this._sock.sQpush16(0); // padding - this._sock.sQpush32(giiDeviceOrigin); // deviceOrigin - this._sock.sQpush32(1); // first - this._sock.sQpush32(4); // Count - this._sock.sQpush32(valuatorFlag); // Flag - this._sock.sQpush32(0); // Empty Id - this._sock.sQpush32(0); // Empty coordinates - this._sock.flush(); - } - - _handleUltraVNCTouch(ev) { - this._sock.sQpush8(253); // GII message type - this._sock.sQpush8(128); // GII event - this._sock.sQpush16(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // Length - this._sock.sQpush8(4 + 16 + (6 * 2 * ev.detail.currentTouches.length)); // eventSize - this._sock.sQpush8(12); // eventType - this._sock.sQpush16(0); // padding - this._sock.sQpush32(ev.detail.giiDeviceOrigin); // deviceOrigin - this._sock.sQpush32(ev.detail.currentTouches.length); // first - this._sock.sQpush32(6 * ev.detail.currentTouches.length); // count - - let pointerUpIds = []; - - // Send all current touches - for (let i = 0; i < ev.detail.currentTouches.length; i++) { - let valuatorFlag = 0x00000000; - valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; - valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_32; - if (ev.detail.currentTouches[i].status !== "POINTER_UP") valuatorFlag |= TouchHandlerUltraVNC.PF_flag; - if (ev.detail.currentTouches[i].event.identifier === 0) valuatorFlag |= TouchHandlerUltraVNC.IF_flag; // IF_flag - - this._sock.sQpush32(valuatorFlag); - this._sock.sQpush32(ev.detail.currentTouches[i].event.touchIdentifier); - - let scaledPosition = clientToElement(ev.detail.currentTouches[i].event.clientX, ev.detail.currentTouches[i].event.clientY, - this._canvas); - - if ((valuatorFlag & TouchHandlerUltraVNC.LENGTH_16_flag) !== 0) { - let scaledX16 = this._display.absX(scaledPosition.x) & 0xFFFF; - let scaledY16 = this._display.absY(scaledPosition.y) & 0xFFFF; - let coordinates = (scaledX16 << 16) | scaledY16; - this._sock.sQpush32(coordinates); - } - - // Keep track of last released touches - if (ev.detail.currentTouches[i].status === "POINTER_UP") { - pointerUpIds.push(ev.detail.currentTouches[i].event.identifier); - } - } - - this._sock.flush(); - - // Remove released touches from current touches in handler - for (let i = 0; i < pointerUpIds.length; i++) { - const index = ev.detail.currentTouches.findIndex(t => t.event.identifier === pointerUpIds[i]); - if (index !== -1) { - this._gestures._removeTouch(index); - } - } - - // Interrupt touch sending interval - if (ev.detail.currentTouches.length === 0 && this._sendTouchesIntervalId !== -1) { - Log.Debug("NO MORE TOUCHES\n"); - this._gestures._interruptTouches(); - this._sendEmptyTouch(ev.detail.giiDeviceOrigin); - return; - } - } - - _sendEmptyTouch(giiDeviceOrigin) { - let valuatorFlag = 0x00000000; - valuatorFlag |= TouchHandlerUltraVNC.LENGTH_16_flag; - valuatorFlag |= TouchHandlerUltraVNC.IDFORMAT_CLEAR; - - this._sock.sQpush8(253); // GII message type - this._sock.sQpush8(128); // GII event - this._sock.sQpush16(24); // Header length - this._sock.sQpush8(24); // Event size - this._sock.sQpush8(12); // eventType - this._sock.sQpush16(0); // padding - this._sock.sQpush32(giiDeviceOrigin); // deviceOrigin - this._sock.sQpush32(1); // first - this._sock.sQpush32(4); // Count - this._sock.sQpush32(valuatorFlag); // Flag - this._sock.sQpush32(0); // Empty Id - this._sock.sQpush32(0); // Empty coordinates + this._sock.sQpush32(1); // nb of touchevents + this._sock.sQpush32(4); // nb of values, not used + this._sock.sQpush32(valuatorFlag); + this._sock.sQpush32(0); // empty Id + this._sock.sQpush32(0); // empty coordinates this._sock.flush(); } @@ -2399,7 +2248,7 @@ export default class RFB extends EventTargetMixin { encs.push(encodings.pseudoEncodingDesktopName); encs.push(encodings.pseudoEncodingExtendedClipboard); - if (this._useUltraVNCGestures) { + if (this._gesturesMode === 'ultravnc') { encs.push(encodings.pseudoEncodingGii); } @@ -3529,39 +3378,39 @@ RFB.messages = { }, giiVersionMessage(sock) { - sock.sQpush8(253); // gii msg-type - sock.sQpush8(129); // gii version sub-msg-type - sock.sQpush16(2); // length - sock.sQpush16(1); // version + sock.sQpush8(TouchHandlerUltraVNC.giiMsgType); + sock.sQpush8(TouchHandlerUltraVNC.giiDeviceVersionMsgType); + sock.sQpush16(TouchHandlerUltraVNC.giiDeviceVersionMsgSize); + sock.sQpush16(TouchHandlerUltraVNC.giiDeviceVersion); sock.flush(); }, giiDeviceCreation(sock) { - sock.sQpush8(253); // gii msg-type - sock.sQpush8(130); // gii device creation sub-msg-type - sock.sQpush16(172); // length - sock.sQpushBytes(RFB.stringAsByteArrayWithPadding("NOVNC-MT", 31)); // device name - sock.sQpush8(0); // DNTerm - sock.sQpush32(0x0908); // vendorID - sock.sQpush32(0x000b); // productID - sock.sQpush32(0x00002000); // eventMask - sock.sQpush32(0); // numRegisters - sock.sQpush32(1); // numValuators - sock.sQpush32(5); // numButtons - sock.sQpush32(0); // index - sock.sQpushBytes(RFB.stringAsByteArrayWithPadding("NOVNC Multitouch Device", 74)); // longName - sock.sQpush8(0); // LNTerm - sock.sQpushBytes(RFB.stringAsByteArrayWithPadding("NMD", 4)); // shortName - sock.sQpush8(0); // SNTerm - sock.sQpush32(0); // rangeMin - sock.sQpush32(0); // rangeCenter - sock.sQpush32(0); // rangeMax - sock.sQpush32(0); // SIUnit - sock.sQpush32(0); // SIAdd - sock.sQpush32(0); // SIMul - sock.sQpush32(0); // SIDiv - sock.sQpush32(0); // SIShift + sock.sQpush8(TouchHandlerUltraVNC.giiMsgType); + sock.sQpush8(TouchHandlerUltraVNC.giiDeviceCreationMsgType); + sock.sQpush16(TouchHandlerUltraVNC.giiDeviceCreationMsgSize); + sock.sQpushBytes(RFB.stringAsByteArrayWithPadding(TouchHandlerUltraVNC.giiDeviceName, TouchHandlerUltraVNC.giiDeviceNameSize)); + sock.sQpush8(TouchHandlerUltraVNC.giiDNTerm); + sock.sQpush32(TouchHandlerUltraVNC.giiVendorID); + sock.sQpush32(TouchHandlerUltraVNC.giiProductID); + sock.sQpush32(TouchHandlerUltraVNC.giiEventMask); + sock.sQpush32(TouchHandlerUltraVNC.giiNumRegisters); + sock.sQpush32(TouchHandlerUltraVNC.giiNumValuators); + sock.sQpush32(TouchHandlerUltraVNC.giiNumButtons); + sock.sQpush32(TouchHandlerUltraVNC.giiIndex); + sock.sQpushBytes(RFB.stringAsByteArrayWithPadding(TouchHandlerUltraVNC.giiDeviceLongName, TouchHandlerUltraVNC.giiDeviceLongNameSize)); + sock.sQpush8(TouchHandlerUltraVNC.giiLNTerm); + sock.sQpushBytes(RFB.stringAsByteArrayWithPadding(TouchHandlerUltraVNC.giiDeviceShortName, TouchHandlerUltraVNC.giiDeviceShortNameSize)); + sock.sQpush8(TouchHandlerUltraVNC.giiSNTerm); + sock.sQpush32(TouchHandlerUltraVNC.giiRangeMin); + sock.sQpush32(TouchHandlerUltraVNC.giiRangeCenter); + sock.sQpush32(TouchHandlerUltraVNC.giiRangeMax); + sock.sQpush32(TouchHandlerUltraVNC.giiSIUnit); + sock.sQpush32(TouchHandlerUltraVNC.giiSIAdd); + sock.sQpush32(TouchHandlerUltraVNC.giiSIMul); + sock.sQpush32(TouchHandlerUltraVNC.giiSIDiv); + sock.sQpush32(TouchHandlerUltraVNC.giiSIShift); sock.flush(); } diff --git a/vnc.html b/vnc.html index 44c43718..e4c9a601 100644 --- a/vnc.html +++ b/vnc.html @@ -270,7 +270,11 @@

  • - + +