Chapter 17. Adding new protocols

While Guacamole as a bundle ships with support for multiple remote desktop protocols (VNC and RDP), this support is provided through plugins which guacd loads dynamically. The Guacamole API has been designed such that protocol support is easy to create, especially when a C library exists providing a basic client implementation.

In this tutorial, we implement a simple "client" which renders a bouncing ball using the Guacamole protocol. After completing the tutorial and installing the result, you will be able to add a connection to your Guacamole configuration using the "ball" protocol, and any users using that connection will see a bouncing ball.

This example client plugin doesn't actually act as a client, but this isn't important. The Guacamole client is really just a remote display, and this client plugin functions as a simple example application which renders to this display, just as the VNC or RDP support plugins function as VNC or RDP clients which render to the remote display.

Each step of this tutorial is intended to exercise a new concept, while also progressing towards the goal of a nifty bouncing ball. At the end of each step, you will have a buildable and working client plugin.

Minimal skeleton client

Very little needs too be done to implement the most basic client plugin possible:

#include <stdlib.h>
#include <guacamole/client.h>

/* Client plugin arguments */
const char* GUAC_CLIENT_ARGS[] = { NULL };

int guac_client_init(guac_client* client, int argc, char** argv) {

    /* Do nothing ... for now */
    return 0;

}

Notice the structure of this file. There is exactly one function, guac_client_init, which is the entry point for all Guacamole client plugins. Just as a typical C program has a main function which is executed when the program is run, a Guacamole client plugin has guac_client_init which is called when guacd loads the plugin when a new connection is made and your protocol is selected.

guac_client_init receives a single guac_client and the same argc and argv arguments that are typical of a C entry point. While we won't be using arguments in this tutorial, a typical client plugin implementation would register its arguments by specifying them in the GUAC_CLIENT_ARGS static variable, and would receive their values as received from the remote client through argv.

The guac_client given will live until the connection with the remote display closes. Your guac_client_init function is expected to parse any arguments in argv and initialize the given guac_client, returning a success code (zero) if the client was initialized successfully.

Place this code in a file called ball_client.c in a subdirectory called src. The build files provided by this tutorial assume this is the location of all source code.

This tutorial, as well as all other C-based Guacamole projects, uses the GNU Automake build system due to its ubiquity and ease of use. The minimal build files required for a libguac-based project that uses GNU Automake are fairly simple. We need a file called configure.in which describes the name of the project and what it needs configuration-wise:

# Project information
AC_INIT(src/ball_client.c)
AM_INIT_AUTOMAKE([libguac-client-ball], 0.1.0)
AC_CONFIG_MACRO_DIR([m4])

# Checks for required build tools
AC_PROG_CC
AC_PROG_LIBTOOL

# Check for libguac (http://guac-dev.org/)
AC_CHECK_LIB([guac], [guac_client_plugin_open],,
      AC_MSG_ERROR("libguac is required for communication via "
                   "the guacamole protocol"))

# Check for Cairo (http://www.cairo-graphics.org)
AC_CHECK_LIB([cairo], [cairo_create],,
      AC_MSG_ERROR("cairo is required for drawing"))

# Checks for header files.
AC_CHECK_HEADERS([stdlib.h \
                  string.h \
                  syslog.h \
                  guacamole/client.h \
                  guacamole/socket.h \
                  guacamole/protocol.h])

# Checks for library functions.
AC_FUNC_MALLOC

AC_CONFIG_FILES([Makefile])
AC_OUTPUT

We also need a Makefile.am, describing which files should be built and how when building libguac-client-ball:

AUTOMAKE_OPTIONS = foreign

ACLOCAL_AMFLAGS = -I m4
AM_CFLAGS = -Werror -Wall -pedantic

lib_LTLIBRARIES = libguac-client-ball.la

# All source files of libguac-client-ball
libguac_client_ball_la_SOURCES = src/ball_client.c

# libtool versioning information
libguac_client_ball_la_LDFLAGS = -version-info 0:0:0

The GNU Automake files will remain largely unchanged throughout the rest of the tutorial.

Once you have created all of the above files, you will have a functioning client plugin. It doesn't do anything yet, but it does work, and guacd will load it when requested, and unload it when the connection terminates.

Initializing the remote display

Now that we have a basic functioning skeleton, we need to actually do something with the remote display. A good first step would be initializing the display - giving the connection a name, setting the remote display size, and providing a basic background.

In this case, we name our connection "Bouncing Ball", set the display to a nice default of 1024x768, and fill the background with a simple gray:

int guac_client_init(guac_client* client, int argc, char** argv) {

    /* Send the name of the connection */
    guac_protocol_send_name(client->socket, "Bouncing Ball");

    /* Send the display size */
    guac_protocol_send_size(client->socket, GUAC_DEFAULT_LAYER, 1024, 768);

    /* Fill with solid color */
    guac_protocol_send_rect(client->socket, GUAC_DEFAULT_LAYER,
            0, 0, 1024, 768);

    guac_protocol_send_cfill(client->socket,
            GUAC_COMP_OVER, GUAC_DEFAULT_LAYER,
            0x80, 0x80, 0x80, 0xFF);

    /* Flush buffer */
    guac_socket_flush(client->socket);

    /* Done */
    return 0;

}

Note how communication is done with the remote display. The guac_client given to guac_client_init has a member, socket, which is used for bidirectional communication. Guacamole protocol functions, all starting with "guac_protocol_send_", provide a slightly high-level mechanism for sending specific Guacamole protocol instructions to the remote display over the client's socket.

Here, we set the name of the connection using a "name" instruction (using guac_protocol_send_name), we resize the display using a "size" instruction (using guac_protocol_send_size), and we then draw to the display using drawing instructions (rect and cfill).

Adding the ball

This tutorial is about making a bouncing ball "client", so naturally we need a ball to bounce.

While we could repeatedly draw and erase a ball on the remote display, a more efficient technique would be to leverage Guacamole's layers.

The remote display has a single root layer, GUAC_DEFAULT_LAYER, but there can be infinitely many other child layers, which can have themselves have child layers, and so on. Each layer can be dynamically repositioned within and relative to another layer. Because the compositing of these layers is handled by the remote display, and is likely hardware-accelerated, this is a much better way to repeatedly reposition something we expect to move a lot:

int guac_client_init(guac_client* client, int argc, char** argv) {

    /* The layer which will contain our ball */
    guac_layer* ball;

    ...

    /* Set up our ball layer */
    ball = guac_client_alloc_layer(client);
    guac_protocol_send_size(client->socket, ball, 128, 128);

    /* Fill with solid color */
    guac_protocol_send_rect(client->socket, ball,
            0, 0, 128, 128);

    guac_protocol_send_cfill(client->socket,
            GUAC_COMP_OVER, ball,
            0x00, 0x80, 0x80, 0xFF);

    ...

Beyond layers, Guacamole has the concept of buffers, which are identical in use to layers except they are invisible. Buffers are used to store image data for the sake of caching or drawing operations. We will use them later when we try to make this tutorial prettier.

If you build and install the ball client as-is now, you will see a large gray rectangle (the root layer) with a small blue square in the upper left corner (the ball layer).

Making the ball bounce

To make the ball bounce, we need to track the ball's state, including current position and velocity. This state information needs to be stored with the client such that it becomes available to all client handlers.

The best way to do this is to create a data structure that contains all the information we need and store it in the data member of the guac_client. We create a header file to declare the structure:

#ifndef _BALL_CLIENT_H
#define _BALL_CLIENT_H

#include <guacamole/client.h>

typedef struct ball_client_data {

    guac_layer* ball;

    int ball_x;
    int ball_y;

    int ball_velocity_x;
    int ball_velocity_y;

} ball_client_data;

int ball_client_handle_messages(guac_client* client);

#endif

We also need to implement an event handler for the handle_messages event triggered by guacd when the client plugin needs to handle any server messages received or, in this case, update the ball position:

int ball_client_handle_messages(guac_client* client) {

    /* Get data */
    ball_client_data* data = (ball_client_data*) client->data;

    /* Sleep a bit */
    usleep(30000);

    /* Update position */
    data->ball_x += data->ball_velocity_x * 30 / 1000;
    data->ball_y += data->ball_velocity_y * 30 / 1000;

    /* Bounce if necessary */
    if (data->ball_x < 0) {
        data->ball_x = -data->ball_x;
        data->ball_velocity_x = -data->ball_velocity_x;
    }
    else if (data->ball_x >= 1024-128) {
        data->ball_x = (2*(1024-128)) - data->ball_x;
        data->ball_velocity_x = -data->ball_velocity_x;
    }

    if (data->ball_y < 0) {
        data->ball_y = -data->ball_y;
        data->ball_velocity_y = -data->ball_velocity_y;
    }
    else if (data->ball_y >= (768-128)) {
        data->ball_y = (2*(768-128)) - data->ball_y;
        data->ball_velocity_y = -data->ball_velocity_y;
    }

    guac_protocol_send_move(client->socket, data->ball,
            GUAC_DEFAULT_LAYER, data->ball_x, data->ball_y, 0);

    return 0;

}

We also must update guac_client_init to initialize the structure, store it in the client, and register our new event handler:

#include "ball_client.h"

...

int guac_client_init(guac_client* client, int argc, char** argv) {


    ball_client_data* data = malloc(sizeof(ball_client_data));

    ...

    /* Set up client data and handlers */
    client->data = data;
    client->handle_messages = ball_client_handle_messages;

    /* Set up our ball layer */
    data->ball = guac_client_alloc_layer(client);

    /* Start ball at upper left */
    data->ball_x = 0;
    data->ball_y = 0;

    /* Move at a reasonable pace to the lower right */
    data->ball_velocity_x = 200; /* pixels per second */
    data->ball_velocity_y = 200;   /* pixels per second */

    ...

}

guacd will call the handle_messages handler of the guac_client repeatedly, if defined. It will stop calling handle_messages temporarily if the remote display appears to be lagging behind due to a slow network or slow browser or computer, so there is no guarantee that handle_messages will be called as frequently as we would like, but for now, we assume there will be essentially no delay between calls, and we include our own delay of 30ms between frames

Because we now have header files, we need to update Makefile.am to include our header and the directory it's in:

...

AM_CFLAGS = -Werror -Wall -pedantic -Iinclude

...

noinst_HEADERS = include/ball_client.h

Once built and installed, our ball client now has a bouncing ball, albeit a very square and plain one.

A prettier ball

Now that we have our ball bouncing, we might as well try to make it actually look like a ball, and try applying some of the fancier graphics features that Guacamole offers.

Guacamole provides instructions common to most 2D drawing APIs, including HTML5's canvas and Cairo. This means you can draw arcs, curves, apply fill and stroke, and even use the contents of another layer or buffer as the pattern for a fill or stroke.

We will try creating a simple gray checkerboard pattern in a buffer and use that for the background instead of the previous gray rectangle.

We will also modify the ball by removing the rectangle and replacing it with an arc, in this case a circle, complete with stroke (border) and translucent-blue fill.

int guac_client_init(guac_client* client, int argc, char** argv) {

    ...

    guac_layer* texture;

    ...

    /* Create background tile */
    texture = guac_client_alloc_buffer(client);

    guac_protocol_send_rect(client->socket, texture, 0, 0, 64, 64);
    guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, texture,
            0x88, 0x88, 0x88, 0xFF);

    guac_protocol_send_rect(client->socket, texture, 0, 0, 32, 32);
    guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, texture,
            0xDD, 0xDD, 0xDD, 0xFF);

    guac_protocol_send_rect(client->socket, texture, 32, 32, 32, 32);
    guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, texture,
            0xDD, 0xDD, 0xDD, 0xFF);


    /* Fill with solid color */
    guac_protocol_send_rect(client->socket, GUAC_DEFAULT_LAYER,
            0, 0, 1024, 768);


    guac_protocol_send_lfill(client->socket,
            GUAC_COMP_OVER, GUAC_DEFAULT_LAYER,
            texture);

    ...

    /* Fill with solid color */
    guac_protocol_send_arc(client->socket, data->ball,
            64, 64, 62, 0, 6.28, 0);

    guac_protocol_send_close(client->socket, data->ball);

    guac_protocol_send_cstroke(client->socket,
            GUAC_COMP_OVER, data->ball,
            GUAC_LINE_CAP_ROUND, GUAC_LINE_JOIN_ROUND, 4,
            0x00, 0x00, 0x00, 0xFF);

    guac_protocol_send_cfill(client->socket,
            GUAC_COMP_OVER, data->ball,
            0x00, 0x80, 0x80, 0x80);

    ...

}

Again, because we put the ball in its own layer, we don't have to worry about compositing it ourselves. The remote display will handle this, and will likely do so with hardware acceleration.

Build and install the ball client after this step, and you will have a rather nice-looking bouncing ball.

Handling the passage of time

Because the handle_messages handler will only be called as guacd deems appropriate, we cannot rely on instantaneous return of control. The server may experience load, causing guacd to lose priority and delay handling of messages, or the remote display may lag due to network or software issues, forcing guacd to temporarily pause updates.

We must modify our ball state to include the time the last update took place:

typedef struct ball_client_data {

    ...

    guac_timestamp last_update;

} ball_client_data;

Naturally, this new structure member must be initialized within guac_client_init:

int guac_client_init(guac_client* client, int argc, char** argv) {

    ball_client_data* data = malloc(sizeof(ball_client_data));

    ...

    data->last_update = guac_protocol_get_timestamp();

    ...

}

And we need to modify the message handler to check the last update time, updating the ball's position based on its current velocity and the elapsed time:

int ball_client_handle_messages(guac_client* client) {

    /* Get data */
    ball_client_data* data = (ball_client_data*) client->data;


    guac_timestamp current;
    int delta_t;

    /* Sleep for a bit, then get timestamp */
    usleep(30000);
    current = guac_protocol_get_timestamp();

    /* Calculate change in time */
    delta_t = current - data->last_update;

    /* Update position */
    data->ball_x += data->ball_velocity_x * delta_t / 1000;
    data->ball_y += data->ball_velocity_y * delta_t / 1000;

    ...

    /* Update timestamp */
    data->last_update = current;


    return 0;

}

At this point, we now have a robust Guacamole client plugin. It properly handles the lack of time guarantees for message handler calls, meanwhile providing the user with a seamlessly bouncing ball.