ThinVNC - Digging into the code, Part II

Introduction

In the first part of this article I showed how to do the screen capture, window by window and, using clipping regions and bitmap comparison, how to build a list of changed bitmap regions to send to the client. In this second part, we'll see how to send all this information to the client. 

The code - Part II

The following JSON is an example of a desktop capture.  
 Collapse
{"status:":1,"desktopWidth":1280,"desktopHeight":800,"cursor":"default","cursorX":241,"cursorY":525,
 "windows": [ { "hwnd":"196724","zidx":1,"desktop":"Default","left":0,"top":0,"width":1280,"height":800},
    { "hwnd":"8521744","zidx":2,"desktop":"Default","left":364,"top":59,"width":806,"height":667},
    { "hwnd":"8129930","zidx":3,"desktop":"Default","left":-8,"top":-8,"width":1296,"height":776},
    { "hwnd":"1247020","zidx":4,"desktop":"Default","left":244,"top":28,"width":1023,"height":728},
    { "hwnd":"8785068","zidx":5,"desktop":"Default","left":-8,"top":-8,"width":1296,"height":776,"imgs": [ { "x":8,"y":8,"w":1280,"h":123,"img": "data:image/jpeg;base64,/9j/4......" }]},
    { "hwnd":"6033806","zidx":6,"desktop":"Default","left":-1,"top":479,"width":426,"height":22,"imgs": [ { "x":1,"y":0,"w":425,"h":22,"img": "data:image/jpeg;base64,/9j/4AAQ......" }]},
    { "hwnd":"196708","zidx":7,"desktop":"Default","left":0,"top":760,"width":1280,"height":40,"imgs": [ { "x":53,"y":0,"w":1227,"h":40,"img": "data:image/jpeg;base64,/9j/4AA......" }]},
    { "hwnd":"131186","zidx":8,"desktop":"Default","left":0,"top":760,"width":54,"height":40,"imgs": [ { "x":0,"y":0,"w":54,"h":40,"img": "data:image/jpeg;base64,/9j/4AAQSkZJ......" }]}]]}   
The "windows" array contains all the visible windows on the desktop. Each item contains the window handle (for identification purpouses), its bounding rectangle, the desktop to which it belongs, its relative z-order and an array of images. Each image corresponds to changed region on that specific window. If there are no changes, the "imgs" array doesn't exist. 
 Collapse
function reload() {
    scale = getScale();
    var url = baseUrl + "json?id=" + sessionStatus.id;
    clearTimeout(jsonTimeout);
    jsonTimeout = setTimeout(onJsonTimeout,jsonTimeoutValue);
    $.getJSON(url, function (obj) {
        try {

            $.each(obj.windows, function (i, win) {
                processWindow(win);
            })

            for (var i = deskDiv.children.length - 1; i >= 0; i--) {

                var found = false;
                var canvas = deskDiv.children[i];

                $.each(obj.windows, function (i, win) {
                    var canvasid = "canvas" + win.hwnd;
                    if (canvas.id == canvasid) {
                        found = true;
                    }
                })
                if (!found) {
                    canvas.style.display = "none";
                    canvas.innerHTML = '';
                    deskDiv.removeChild(canvas);
                }
            }

        }
        catch (err) {
            if (sessionStatus.active) {
                setTimeout(reload, 1);
            }
        }
    });
}
For each "window" item received, the processWindow method is called, creating or reusing a canvas element. All canvas elements belonging to windows that aren't present in this JSON correspond to windows that have already been closed. These are erased on the "for (var i = deskDiv.children.length - 1; i >= 0; i--)" loop. 
ProcesWindows function creates or reuses a canvas per window, using the hwnd as part its id. It also sets the canvas coordinates and its zindex according to the received information, and iterates over the images array to copy them on the canvas surface.
 Collapse
function createCanvas(win) {
    var canvas = document.createElement("canvas");
    
    canvas.visibility = 'visible';
    canvas.display = 'block';
    canvas.style.position = 'absolute';
    canvas.style.left = (win.left-sessionStatus.viewLeft)+'px';
    canvas.style.top = (win.top-sessionStatus.viewTop)+'px';
    canvas.style.zIndex = win.zidx;
    canvas.width = deskDiv.offsetWidth;
    canvas.height = deskDiv.offsetHeight;
    canvas.id = "canvas" + win.hwnd;
    deskDiv.appendChild(canvas);
    return canvas;
}

function processWindow(win) {
    var canvasid = "canvas" + win.hwnd;
    var canvas = document.getElementById(canvasid);
    if (!canvas) {
        canvas = createCanvas(win);
    }

    deskDiv.style.marginLeft = getDeltaX() + 'px';
    deskDiv.style.marginTop = getDeltaY() + 'px';
    if ((win.width == 0) || (win.height == 0)) {
        canvas.style.visibility = "hidden";
        canvas.style.zIndex = -1;
    } else {
        canvas.style.left = (win.left-sessionStatus.viewLeft) + 'px';
        canvas.style.top = (win.top-sessionStatus.viewTop) + 'px';
        canvas.style.clip = 'rect(0px,' + win.width + 'px,' + win.height + 'px,0px)';

        canvas.style.visibility = "visible";
        canvas.style.zIndex = win.zidx;
    }
    
    if (win.imgs != null) {
        var context = canvas.getContext('2d');
        if (!context || !context.drawImage) {
            alert("no hay canvas");
            return;
        };

        $.each(win.imgs, function (i, imgpart) {
            var img = new Image();
            img.id = "imgcanvas";
            img.style.display = "none";
            img.onload = function () {
                context.drawImage(img, imgpart.x , imgpart.y, img.width, img.height);
            }
            img.src = imgpart.img;
        })
    }
};  

The updated source code can always be found here.