Table of Contents
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.
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.
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).
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).
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:
Once built and installed, our ball client now has a bouncing ball, albeit a very square and plain one.
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.
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:
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.