From 0a67102838a1c47263a1bb928ed93e3b9d759b4b Mon Sep 17 00:00:00 2001 From: f-trycua Date: Sun, 27 Apr 2025 14:01:52 -0700 Subject: [PATCH] Improve detection of Qt VNC servers to handle BGR color mode --- core/decoders/raw.js | 21 +++++++++++++++------ core/rfb.js | 29 +++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/core/decoders/raw.js b/core/decoders/raw.js index bd5d2044..057510b7 100644 --- a/core/decoders/raw.js +++ b/core/decoders/raw.js @@ -12,7 +12,7 @@ export default class RawDecoder { this._lines = 0; } - decodeRect(x, y, width, height, sock, display, depth) { + decodeRect(x, y, width, height, sock, display, depth, bgrMode = false) { if ((width === 0) || (height === 0)) { return true; } @@ -43,11 +43,20 @@ export default class RawDecoder { newdata[i * 4 + 3] = 255; } data = newdata; - } - - // Max sure the image is fully opaque - for (let i = 0; i < width; i++) { - data[i * 4 + 3] = 255; + } else if (bgrMode) { + // In bgrMode we need to switch the red and blue bytes + // so that the data is in RGB order + for (let i = 0; i < width; i++) { + let j = i * 4; + let red = data[j]; + data[j] = data[j + 2]; + data[j + 2] = red; + } + } else { + // Make sure the image is fully opaque + for (let i = 0; i < width; i++) { + data[i * 4 + 3] = 255; + } } display.blitImage(x, curY, width, 1, data, 0); diff --git a/core/rfb.js b/core/rfb.js index e3266cc8..3dfa6554 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -2210,18 +2210,29 @@ export default class RFB extends EventTargetMixin { // we're past the point where we could backtrack, so it's safe to call this this._setDesktopName(name); + Log.Info("Connected to server: '" + this._fbName + "'"); this._resize(width, height); if (!this._viewOnly) { this._keyboard.grab(); } this._fbDepth = 24; + this._BGRmode = false; if (this._fbName === "Intel(r) AMT KVM") { Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode."); this._fbDepth = 8; + } else if (this._fbName === "Qt for Embedded Linux VNC Server" || + this._fbName.indexOf("Qt") !== -1) { + // Qt has a bug (as of QT 6.4) where, if the endian-ness is 'little' and the _fbDepth is 24, it does not honour the noVNC reqested + // redshift/blueshift values. In these conditions it triggers a performance bypass in the Qt code which just uses the raw + // Qt frambuffer byte order (https://github.com/qt/qtbase/blob/5.15.2/src/plugins/platforms/vnc/qvncclient.cpp#L113) which is BGR + // vs the RGB noVNC wants. Since noVNC always operates in little endian and fbDepth 24, it always triggers this bug + // So, for Qt we change to BGR ordering. Note that Qt only uses raw encoding and noVNC only supports this mode in the raw decoder + Log.Info("Detected Qt VNC server. Enabling BGR color mode."); + this._BGRmode = true; } - RFB.messages.pixelFormat(this._sock, this._fbDepth, true); + RFB.messages.pixelFormat(this._sock, this._fbDepth, true, this._BGRmode); this._sendEncodings(); RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight); @@ -2989,7 +3000,7 @@ export default class RFB extends EventTargetMixin { return decoder.decodeRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, this._sock, this._display, - this._fbDepth); + this._fbDepth, this._BGRmode); } catch (err) { this._fail("Error decoding rect: " + err); return false; @@ -3314,7 +3325,7 @@ RFB.messages = { sock.flush(); }, - pixelFormat(sock, depth, trueColor) { + pixelFormat(sock, depth, trueColor, bgrMode) { let bpp; if (depth > 16) { @@ -3342,9 +3353,15 @@ RFB.messages = { sock.sQpush16((1 << bits) - 1); // green-max sock.sQpush16((1 << bits) - 1); // blue-max - sock.sQpush8(bits * 0); // red-shift - sock.sQpush8(bits * 1); // green-shift - sock.sQpush8(bits * 2); // blue-shift + if (bgrMode) { + sock.sQpush8(bits * 2); // red-shift + sock.sQpush8(bits * 1); // green-shift + sock.sQpush8(bits * 0); // blue-shift + } else { + sock.sQpush8(bits * 0); // red-shift + sock.sQpush8(bits * 1); // green-shift + sock.sQpush8(bits * 2); // blue-shift + } sock.sQpush8(0); // padding sock.sQpush8(0); // padding