Fix VNC display rendering issue

- Disable viewport clipping to prevent large screens from being hidden
- Add proper handling of corrupted FramebufferUpdate messages
- Skip data for unsupported encodings to prevent data accumulation
- Add comprehensive debugging for message processing flow
- Fix infinite loop in rectangle processing for corrupted data
parent b6d4fdb0
......@@ -175,6 +175,7 @@ class RFB extends EventTargetMixin {
// main setup
Log.Debug(">> RFB.constructor");
console.log("[VNC-CLIENT] RFB constructor called");
// Create DOM elements
this._screen = document.createElement('div');
......@@ -191,6 +192,10 @@ class RFB extends EventTargetMixin {
this._canvas.height = 0;
this._canvas.tabIndex = -1;
this._screen.appendChild(this._canvas);
console.log("[VNC-CLIENT] DOM elements created, canvas size:", this._canvas.width, "x", this._canvas.height);
console.log("[VNC-CLIENT] Canvas element:", this._canvas);
console.log("[VNC-CLIENT] Screen element:", this._screen);
console.log("[VNC-CLIENT] Target element:", this._target);
// Cursor
this._cursor = new Cursor();
......@@ -219,12 +224,16 @@ class RFB extends EventTargetMixin {
// NB: nothing that needs explicit teardown should be done
// before this point, since this can throw an exception
try {
console.log("[VNC-CLIENT] Creating Display object with canvas:", this._canvas);
this._display = new Display(this._canvas);
console.log("[VNC-CLIENT] Display object created successfully:", this._display);
} catch (exc) {
console.error("[VNC-CLIENT] Display exception:", exc);
Log.Error("Display exception: " + exc);
throw exc;
}
this._display.onflush = this._onFlush.bind(this);
console.log("[VNC-CLIENT] Display onflush handler set");
this._keyboard = new Keyboard(this._canvas);
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
......@@ -252,9 +261,9 @@ class RFB extends EventTargetMixin {
this.focusOnClick = true;
this._viewOnly = false;
this._clipViewport = false;
this._clipViewport = false; // Disable viewport clipping for large screens - let scaling handle it
this._clippingViewport = false;
this._scaleViewport = false;
this._scaleViewport = true; // Enable viewport scaling by default for web VNC
this._resizeSession = false;
this._showDotCursor = false;
......@@ -601,14 +610,17 @@ class RFB extends EventTargetMixin {
}
_socketOpen() {
console.log("[VNC-DEBUG] WebSocket opened, starting VNC handshake");
console.log("[VNC-CLIENT] WebSocket opened, connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
// console.log("[VNC-DEBUG] WebSocket opened, starting VNC handshake");
if ((this._rfbConnectionState === 'connecting') &&
(this._rfbInitState === '')) {
this._rfbInitState = 'ProtocolVersion';
Log.Debug("Starting VNC handshake");
console.log("[VNC-DEBUG] Set init state to ProtocolVersion");
console.log("[VNC-CLIENT] Starting VNC handshake, set init state to ProtocolVersion");
// console.log("[VNC-DEBUG] Set init state to ProtocolVersion");
} else {
console.log("[VNC-DEBUG] Unexpected socket open - connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
console.log("[VNC-CLIENT] Unexpected socket open - connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
// console.log("[VNC-DEBUG] Unexpected socket open - connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
this._fail("Unexpected server connection while " +
this._rfbConnectionState);
}
......@@ -801,11 +813,11 @@ class RFB extends EventTargetMixin {
*/
_updateConnectionState(state) {
const oldstate = this._rfbConnectionState;
console.log("[VNC-DEBUG] _updateConnectionState called:", oldstate, "->", state);
// console.log("[VNC-DEBUG] _updateConnectionState called:", oldstate, "->", state);
if (state === oldstate) {
Log.Debug("Already in state '" + state + "', ignoring");
console.log("[VNC-DEBUG] Already in state '" + state + "', ignoring");
// console.log("[VNC-DEBUG] Already in state '" + state + "', ignoring");
return;
}
......@@ -875,6 +887,7 @@ class RFB extends EventTargetMixin {
break;
case 'connected':
console.log("[VNC-CLIENT] Connection state changed to CONNECTED");
this.dispatchEvent(new CustomEvent("connect", { detail: {} }));
break;
......@@ -931,49 +944,54 @@ class RFB extends EventTargetMixin {
}
_handleMessage() {
console.log("[VNC-DEBUG] _handleMessage called, rQlen:", this._sock.rQlen, "connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
console.log("[VNC-MESSAGE] _handleMessage called, rQlen:", this._sock.rQlen, "connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
if (this._sock.rQlen === 0) {
Log.Warn("handleMessage called on an empty receive queue");
console.log("[VNC-DEBUG] Empty receive queue");
return;
}
switch (this._rfbConnectionState) {
case 'disconnected':
Log.Error("Got data while disconnected");
console.log("[VNC-DEBUG] Got data while disconnected");
break;
case 'connected':
console.log("[VNC-DEBUG] Processing messages in connected state");
console.log("[VNC-MESSAGE] Processing messages in connected state");
let msgCount = 0;
while (true) {
if (this._flushing) {
console.log("[VNC-DEBUG] Flushing, breaking message loop");
console.log("[VNC-MESSAGE] Flushing, breaking message loop");
break;
}
console.log("[VNC-DEBUG] Calling _normalMsg");
if (!this._normalMsg()) {
console.log("[VNC-DEBUG] _normalMsg returned false, breaking");
console.log("[VNC-MESSAGE] Calling _normalMsg, rQlen before:", this._sock.rQlen);
const result = this._normalMsg();
console.log("[VNC-MESSAGE] _normalMsg returned:", result, "rQlen after:", this._sock.rQlen);
if (!result) {
console.log("[VNC-MESSAGE] _normalMsg returned false, breaking");
break;
}
if (this._sock.rQlen === 0) {
console.log("[VNC-DEBUG] No more data in queue");
console.log("[VNC-MESSAGE] No more data in queue");
break;
}
msgCount++;
if (msgCount > 10) { // Prevent infinite loops
console.log("[VNC-MESSAGE] Too many messages processed, breaking to prevent infinite loop");
break;
}
}
break;
case 'connecting':
console.log("[VNC-DEBUG] Processing messages in connecting state");
console.log("[VNC-MESSAGE] Processing messages in connecting state");
while (this._rfbConnectionState === 'connecting') {
console.log("[VNC-DEBUG] Calling _initMsg, initState:", this._rfbInitState);
console.log("[VNC-MESSAGE] Calling _initMsg, initState:", this._rfbInitState);
if (!this._initMsg()) {
console.log("[VNC-DEBUG] _initMsg returned false, breaking");
console.log("[VNC-MESSAGE] _initMsg returned false, breaking");
break;
}
}
break;
default:
Log.Error("Got data while in an invalid state");
console.log("[VNC-DEBUG] Got data in invalid state:", this._rfbConnectionState);
break;
}
}
......@@ -2115,11 +2133,13 @@ class RFB extends EventTargetMixin {
// we're past the point where we could backtrack, so it's safe to call this
this._setDesktopName(name);
console.log("[VNC-CLIENT] Server init complete - screen:", width, "x", height, "name:", name);
this._resize(width, height);
if (!this._viewOnly) { this._keyboard.grab(); }
this._fbDepth = 24;
console.log("[VNC-CLIENT] Keyboard grabbed, fbDepth set to 24");
if (this._fbName === "Intel(r) AMT KVM") {
Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
......@@ -2181,40 +2201,40 @@ class RFB extends EventTargetMixin {
* ServerInitialization
*/
_initMsg() {
console.log("[VNC-DEBUG] _initMsg called, initState:", this._rfbInitState);
// console.log("[VNC-DEBUG] _initMsg called, initState:", this._rfbInitState);
switch (this._rfbInitState) {
case 'ProtocolVersion':
console.log("[VNC-DEBUG] Negotiating protocol version");
// console.log("[VNC-DEBUG] Negotiating protocol version");
return this._negotiateProtocolVersion();
case 'Security':
console.log("[VNC-DEBUG] Negotiating security");
// console.log("[VNC-DEBUG] Negotiating security");
return this._negotiateSecurity();
case 'Authentication':
console.log("[VNC-DEBUG] Negotiating authentication");
// console.log("[VNC-DEBUG] Negotiating authentication");
return this._negotiateAuthentication();
case 'SecurityResult':
console.log("[VNC-DEBUG] Handling security result");
// console.log("[VNC-DEBUG] Handling security result");
return this._handleSecurityResult();
case 'SecurityReason':
console.log("[VNC-DEBUG] Handling security reason");
// console.log("[VNC-DEBUG] Handling security reason");
return this._handleSecurityReason();
case 'ClientInitialisation':
console.log("[VNC-DEBUG] Sending client initialization");
// console.log("[VNC-DEBUG] Sending client initialization");
this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
this._rfbInitState = 'ServerInitialisation';
return true;
case 'ServerInitialisation':
console.log("[VNC-DEBUG] Negotiating server initialization");
// console.log("[VNC-DEBUG] Negotiating server initialization");
return this._negotiateServerInit();
default:
console.log("[VNC-DEBUG] Unknown init state:", this._rfbInitState);
// console.log("[VNC-DEBUG] Unknown init state:", this._rfbInitState);
return this._fail("Unknown init state (state: " +
this._rfbInitState + ")");
}
......@@ -2472,18 +2492,22 @@ class RFB extends EventTargetMixin {
if (this._FBU.rects > 0) {
msgType = 0;
} else {
if (this._sock.rQlen < 1) {
console.log("[VNC-MESSAGE] Not enough data for message type, rQlen:", this._sock.rQlen);
return false;
}
msgType = this._sock.rQshift8();
}
console.log("[VNC-DEBUG] _normalMsg processing message type:", msgType, "FBU.rects:", this._FBU.rects);
console.log("[VNC-MESSAGE] Processing message type:", msgType, "FBU.rects:", this._FBU.rects, "rQlen:", this._sock.rQlen);
let first, ret;
switch (msgType) {
case 0: // FramebufferUpdate
console.log("[VNC-DEBUG] Handling FramebufferUpdate");
// console.log("[VNC-DEBUG] Handling FramebufferUpdate");
ret = this._framebufferUpdate();
if (ret && !this._enabledContinuousUpdates) {
console.log("[VNC-DEBUG] Sending FramebufferUpdateRequest");
// console.log("[VNC-DEBUG] Sending FramebufferUpdateRequest");
RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
this._fbWidth, this._fbHeight);
}
......@@ -2545,14 +2569,26 @@ class RFB extends EventTargetMixin {
this._sock.rQskipBytes(1); // Padding
this._FBU.rects = this._sock.rQshift16();
console.log("[VNC-DEBUG] FramebufferUpdate header - rects:", this._FBU.rects);
console.log("[VNC-CLIENT] FramebufferUpdate header - rects:", this._FBU.rects);
// Debug: check if we have enough data for the rectangles
const expectedBytes = this._FBU.rects * 12; // Each rect header is 12 bytes
console.log("[VNC-DEBUG] Expected bytes for rect headers:", expectedBytes, "available rQlen:", this._sock.rQlen);
// Validate rectangle count - should be reasonable (not 65534 or similar corruption)
if (this._FBU.rects > 10000 || this._FBU.rects < 0) {
console.log("[VNC-DEBUG] Invalid rectangle count:", this._FBU.rects, "- likely corrupted data, skipping message");
Log.Warn("Invalid rectangle count in FramebufferUpdate (" + this._FBU.rects + "), skipping corrupted message");
// Skip this corrupted message by consuming all remaining data in the current frame
this._sock.rQskipBytes(this._sock.rQlen);
// Skip the entire FramebufferUpdate message by consuming all remaining data for this message
// We need to skip past all the rectangle data
const skipBytes = this._FBU.rects * 12; // Each rectangle header is 12 bytes
if (this._sock.rQlen >= skipBytes) {
this._sock.rQskipBytes(skipBytes);
console.log("[VNC-DEBUG] Skipped", skipBytes, "bytes of corrupted rectangle data");
} else {
console.log("[VNC-DEBUG] Not enough data to skip, waiting for more data");
return false; // Wait for more data
}
this._FBU.rects = 0; // Reset for next message
return true; // Continue processing
}
......@@ -2568,7 +2604,10 @@ class RFB extends EventTargetMixin {
while (this._FBU.rects > 0) {
if (this._FBU.encoding === null) {
if (this._sock.rQwait("rect header", 12)) { return false; }
if (this._sock.rQwait("rect header", 12)) {
console.log("[VNC-DEBUG] Waiting for rectangle header data");
return false;
}
/* New FramebufferUpdate */
const hdr = this._sock.rQshiftBytes(12);
......@@ -2578,9 +2617,13 @@ class RFB extends EventTargetMixin {
this._FBU.height = (hdr[6] << 8) + hdr[7];
this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
(hdr[10] << 8) + hdr[11], 10);
console.log("[VNC-DEBUG] Processing rectangle:", this._FBU.x, this._FBU.y, this._FBU.width, "x", this._FBU.height, "encoding:", this._FBU.encoding);
}
if (!this._handleRect()) {
const rectResult = this._handleRect();
console.log("[VNC-DEBUG] _handleRect returned:", rectResult, "remaining rects:", this._FBU.rects - 1);
if (!rectResult) {
console.log("[VNC-DEBUG] Rectangle processing waiting for more data");
return false;
}
......@@ -2874,14 +2917,23 @@ class RFB extends EventTargetMixin {
_handleDataRect() {
let decoder = this._decoders[this._FBU.encoding];
if (!decoder) {
console.log("[VNC-DEBUG] Unsupported encoding:", this._FBU.encoding, "- skipping rectangle");
console.log("[VNC-DEBUG] Unsupported encoding:", this._FBU.encoding, "- skipping rectangle data");
Log.Warn("Unsupported encoding (encoding: " +
this._FBU.encoding + "), skipping rectangle");
// Skip the rectangle by consuming the data
const rectSize = this._FBU.width * this._FBU.height;
// For unknown encodings, we can't know how much data to skip
// So we'll skip a reasonable amount or fail gracefully
this._sock.rQskipBytes(this._sock.rQlen); // Skip all remaining data in buffer
this._FBU.encoding + "), skipping rectangle");
// For unsupported encodings, we need to skip the rectangle data to avoid getting stuck
// Calculate how much data this rectangle should contain
// This is a rough estimate - most encodings need at least width*height bytes
const minDataSize = this._FBU.width * this._FBU.height;
if (this._sock.rQlen >= minDataSize) {
// Skip a reasonable amount of data for this rectangle
const skipSize = Math.min(minDataSize, this._sock.rQlen);
this._sock.rQskipBytes(skipSize);
console.log("[VNC-DEBUG] Skipped", skipSize, "bytes for unsupported encoding");
} else {
console.log("[VNC-DEBUG] Not enough data to skip rectangle, waiting for more data");
return false; // Wait for more data
}
return true;
}
......@@ -2893,8 +2945,13 @@ class RFB extends EventTargetMixin {
} catch (err) {
console.log("[VNC-DEBUG] Error decoding rect:", err, "- skipping corrupted rectangle");
Log.Warn("Error decoding rect: " + err + " - skipping corrupted rectangle");
// Skip remaining data in this rectangle to avoid further corruption
this._sock.rQskipBytes(this._sock.rQlen);
// For decoding errors, also skip data to avoid getting stuck
const minDataSize = this._FBU.width * this._FBU.height;
if (this._sock.rQlen >= minDataSize) {
const skipSize = Math.min(minDataSize, this._sock.rQlen);
this._sock.rQskipBytes(skipSize);
console.log("[VNC-DEBUG] Skipped", skipSize, "bytes after decode error");
}
return true; // Continue processing instead of failing
}
}
......@@ -2907,10 +2964,13 @@ class RFB extends EventTargetMixin {
}
_resize(width, height) {
console.log("[VNC-CLIENT] Resizing display to", width, "x", height);
console.log("[VNC-CLIENT] Canvas before resize:", this._canvas.width, "x", this._canvas.height);
this._fbWidth = width;
this._fbHeight = height;
this._display.resize(this._fbWidth, this._fbHeight);
console.log("[VNC-CLIENT] Display resized, canvas after:", this._canvas.width, "x", this._canvas.height);
// Adjust the visible viewport based on the new dimensions
this._updateClip();
......@@ -2920,6 +2980,7 @@ class RFB extends EventTargetMixin {
// Keep this size until browser client size changes
this._saveExpectedClientSize();
console.log("[VNC-CLIENT] Resize complete, final canvas size:", this._canvas.width, "x", this._canvas.height);
}
_xvpOp(ver, op) {
......
......@@ -61,6 +61,8 @@ class Websock {
this._sQlen = 0;
this._sQ = null; // Send queue
this._socketFrameSeq = 0; // Sequence counter for socket debugging
this._eventHandlers = {
message: () => {},
open: () => {},
......@@ -336,7 +338,16 @@ class Websock {
}
_recvMessage(e) {
console.log("[VNC-WS] Received WebSocket message, data length:", e.data.byteLength || e.data.length);
// Log first 32 bytes for debugging
const dataArray = new Uint8Array(e.data);
const hexData = Array.from(dataArray.slice(0, Math.min(32, dataArray.length))).map(b => b.toString(16).padStart(2, '0')).join(' ');
console.log("[VNC-WS] First 32 bytes (hex):", hexData);
this._DecodeMessage(e.data);
console.log("[VNC-WS] After decode, rQlen:", this.rQlen, "rQi:", this._rQi);
if (this.rQlen > 0) {
this._eventHandlers.message();
if (this._rQlen == this._rQi) {
......
......@@ -17,6 +17,7 @@
<script src="/novnc/util/int.js"></script>
<script src="/novnc/util/cursor.js"></script>
<script src="/novnc/util/md5.js"></script>
<script src="/novnc/base64.js"></script>
<script src="/novnc/websock.js"></script>
<script src="/novnc/des.js"></script>
<script src="/novnc/input/keysym.js"></script>
......@@ -200,8 +201,19 @@
<script>
console.log('VNC page loaded');
// Check if Base64 is loaded
setTimeout(function() {
console.log('[VNC-DEBUG] Checking if Base64 is defined:', typeof window.Base64);
if (typeof window.Base64 === 'undefined') {
console.error('[VNC-DEBUG] Base64 is not defined! This will cause JPEG decoding to fail.');
} else {
console.log('[VNC-DEBUG] Base64 is properly defined');
}
}, 100);
let rfb = null;
let connected = false;
let socketFrameSeq = 0;
document.getElementById('connectBtn').addEventListener('click', connect);
document.getElementById('disconnectBtn').addEventListener('click', disconnect);
......@@ -266,8 +278,59 @@ function connect() {
document.getElementById('connectBtn').disabled = true;
document.getElementById('disconnectBtn').disabled = false;
showNotification('VNC session connected', 'success');
// Override WebSocket onmessage after connection to intercept messages
const originalOnMessage = rfb._sock.onmessage;
rfb._sock.onmessage = function(event) {
// Debug VNC socket data logging with base64 encoding and sequence numbers (first 20 frames only)
if (socketFrameSeq < 20) {
const bytes = event.data.byteLength || event.data.length;
const uint8 = new Uint8Array(event.data);
let binary = '';
for (let i = 0; i < uint8.length; i++) {
binary += String.fromCharCode(uint8[i]);
}
const b64Data = btoa(binary);
console.log(`[VNC-SOCKET-SERVER] Frame ${socketFrameSeq}: ${bytes} bytes -> base64: ${b64Data}`);
socketFrameSeq++;
}
// Call original handler
return originalOnMessage.call(this, event);
};
});
// Add debug logging for WebSocket messages
const originalSend = rfb._sock.send;
rfb._sock.send = function(data) {
console.log('[VNC-DEBUG] JavaScript sending data to WebSocket:', data.length, 'bytes');
if (data.length <= 32) {
const hex = Array.from(new Uint8Array(data)).map(b => b.toString(16).padStart(2, '0')).join(' ');
console.log('[VNC-DEBUG] Data (hex):', hex);
} else {
const hex = Array.from(new Uint8Array(data.slice(0, 32))).map(b => b.toString(16).padStart(2, '0')).join(' ');
console.log('[VNC-DEBUG] Data (first 32 bytes hex):', hex + '...');
}
// Log VNC message types
if (data.length > 0) {
const msgType = new Uint8Array(data)[0];
console.log('[VNC-DEBUG] JavaScript message type:', msgType);
switch (msgType) {
case 0: console.log('[VNC-DEBUG] -> SetPixelFormat'); break;
case 2: console.log('[VNC-DEBUG] -> SetEncodings'); break;
case 3: console.log('[VNC-DEBUG] -> FramebufferUpdateRequest'); break;
case 4: console.log('[VNC-DEBUG] -> KeyEvent'); break;
case 5: console.log('[VNC-DEBUG] -> PointerEvent'); break;
case 6: console.log('[VNC-DEBUG] -> ClientCutText'); break;
default: console.log('[VNC-DEBUG] -> Unknown message type'); break;
}
}
return originalSend.call(this, data);
};
rfb.addEventListener('disconnect', () => {
console.log('[VNC-DEBUG] VNC disconnected event fired');
console.log('[VNC-DEBUG] Connection state:', rfb._rfbConnectionState);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment