blob: 7a4974bd3fbdb8c240695dff0594850c66030068 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<chapter xml:id="custom-protocols" xmlns="http://docbook.org/ns/docbook"
version="5.0" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude">
<title>Adding new protocols</title>
<indexterm xmlns:xl="http://www.w3.org/1999/xlink">
<primary>protocols</primary>
<secondary>implementing</secondary>
</indexterm>
<para>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.</para>
<para>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.</para>
<para>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.</para>
<para>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.</para>
<section xml:id="libguac-client-ball-skeleton">
<title>Minimal skeleton client</title>
<para> Very little needs too be done to implement the most basic client
plugin possible: </para>
<informalexample>
<programlisting xml:id="ball-01-ball_client.c" version="5.0" xml:lang="en">#include &lt;stdlib.h>
#include &lt;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;
}</programlisting>
</informalexample>
<para>Notice the structure of this file. There is exactly one function,
<methodname>guac_client_init</methodname>, which is the entry
point for all Guacamole client plugins. Just as a typical C program
has a <methodname>main</methodname> function which is executed when
the program is run, a Guacamole client plugin has
<methodname>guac_client_init</methodname> which is called when
guacd loads the plugin when a new connection is made and your
protocol is selected.</para>
<para><methodname>guac_client_init</methodname> receives a single
<classname>guac_client</classname> and the same
<varname>argc</varname> and <varname>argv</varname> 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
<varname>GUAC_CLIENT_ARGS</varname> static variable, and would
receive their values as received from the remote client through
<varname>argv</varname>.</para>
<para>The <classname>guac_client</classname> given will live until the
connection with the remote display closes. Your
<methodname>guac_client_init</methodname> function is expected
to parse any arguments in <varname>argv</varname> and initialize the
given <classname>guac_client</classname>, returning a success code
(zero) if the client was initialized successfully.</para>
<para>Place this code in a file called
<filename>ball_client.c</filename> in a subdirectory called
<filename>src</filename>. The build files provided by this
tutorial assume this is the location of all source code.</para>
<para>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
<filename>configure.in</filename> which describes the name of
the project and what it needs configuration-wise:</para>
<informalexample>
<programlisting xml:id="ball-01-configure.in" version="5.0" xml:lang="en"># 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</programlisting></informalexample>
<para>We also need a <filename>Makefile.am</filename>, describing which files should be
built and how when building
libguac-client-ball:<programlisting xml:id="ball-01-Makefile.am" version="5.0" xml:lang="en">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</programlisting></para>
<para>The GNU Automake files will remain largely unchanged throughout
the rest of the tutorial. </para>
<para>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.</para>
</section>
<section xml:id="libguac-client-ball-display-init">
<title>Initializing the remote display</title>
<para>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.</para>
<para>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:</para>
<informalexample>
<programlisting xml:id="ball-02-ball_client.c" version="5.0" xml:lang="en">int guac_client_init(guac_client* client, int argc, char** argv) {
<emphasis>
/* 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);
</emphasis>
/* Done */
return 0;
}</programlisting></informalexample>
<para>Note how communication is done with the remote display. The
<classname>guac_client</classname> given to
<methodname>guac_client_init</methodname> has a member,
<property>socket</property>, which is used for bidirectional
communication. Guacamole protocol functions, all starting with
"<methodname>guac_protocol_send_</methodname>", provide a
slightly high-level mechanism for sending specific Guacamole
protocol instructions to the remote display over the client's
socket.</para>
<para>Here, we set the name of the connection using a "name" instruction
(using <methodname>guac_protocol_send_name</methodname>), we resize
the display using a "size" instruction (using
<methodname>guac_protocol_send_size</methodname>), and we then
draw to the display using drawing instructions (rect and
cfill).</para>
</section>
<section xml:id="libguac-client-ball-layer">
<title>Adding the ball</title>
<para>This tutorial is about making a bouncing ball "client", so
naturally we need a ball to bounce. </para>
<para>While we could repeatedly draw and erase a ball on the remote
display, a more efficient technique would be to leverage Guacamole's
layers.</para>
<para>The remote display has a single root layer,
<varname>GUAC_DEFAULT_LAYER</varname>, 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:</para>
<informalexample>
<programlisting xml:id="ball-03-ball_client.c" version="5.0" xml:lang="en">int guac_client_init(guac_client* client, int argc, char** argv) {
<emphasis>
/* The layer which will contain our ball */
guac_layer* ball;
</emphasis>
...
<emphasis>
/* 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);
</emphasis>
...</programlisting></informalexample>
<para>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.</para>
<para>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).</para>
</section>
<section xml:id="libguac-client-ball-bounce">
<title>Making the ball bounce</title>
<para>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.</para>
<para>The best way to do this is to create a data structure that
contains all the information we need and store it in the
<varname>data</varname> member of the
<classname>guac_client</classname>. We create a header file to
declare the structure:</para>
<informalexample>
<programlisting xml:id="ball-04-ball_client.h" version="5.0" xml:lang="en">#ifndef _BALL_CLIENT_H
#define _BALL_CLIENT_H
#include &lt;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</programlisting></informalexample>
<para>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:</para>
<informalexample>
<programlisting version="5.0" xml:lang="en">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 &lt; 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 &lt; 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;
}</programlisting></informalexample>
<para>We also must update <methodname>guac_client_init</methodname> to
initialize the structure, store it in the client, and register our
new event handler:</para>
<informalexample>
<programlisting xml:id="ball-04-ball_client.c" version="5.0" xml:lang="en"><emphasis>#include "ball_client.h"
</emphasis>
...
int guac_client_init(guac_client* client, int argc, char** argv) {
<emphasis>
ball_client_data* data = malloc(sizeof(ball_client_data));
</emphasis>
...
<emphasis>
/* 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 */
</emphasis>
...
}</programlisting></informalexample>
<para>guacd will call the <methodname>handle_messages</methodname>
handler of the <classname>guac_client</classname> repeatedly, if
defined. It will stop calling
<methodname>handle_messages</methodname> 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
<methodname>handle_messages</methodname> 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</para>
<para>Because we now have header files, we need to update
<filename>Makefile.am</filename> to include our header and the
directory it's in:</para>
<informalexample>
<programlisting xml:id="ball-04-Makefile.am" version="5.0" xml:lang="en">...
<emphasis>AM_CFLAGS = -Werror -Wall -pedantic -Iinclude</emphasis>
...
<emphasis>noinst_HEADERS = include/ball_client.h</emphasis></programlisting></informalexample>
<para>Once built and installed, our ball client now has a bouncing ball,
albeit a very square and plain one.</para>
</section>
<section xml:id="libguac-client-ball-pretty">
<title>A prettier ball</title>
<para>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.</para>
<para>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.</para>
<para>We will try creating a simple gray checkerboard pattern in a
buffer and use that for the background instead of the previous gray
rectangle.</para>
<para>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.</para>
<informalexample>
<programlisting xml:id="ball-05-ball_client.c" version="5.0" xml:lang="en">int guac_client_init(guac_client* client, int argc, char** argv) {
...
guac_layer* texture;
...
<emphasis>
/* 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);
</emphasis>
/* Fill with solid color */
guac_protocol_send_rect(client->socket, GUAC_DEFAULT_LAYER,
0, 0, 1024, 768);
<emphasis>
guac_protocol_send_lfill(client->socket,
GUAC_COMP_OVER, GUAC_DEFAULT_LAYER,
texture);
</emphasis>
...
<emphasis>
/* 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);
</emphasis>
...
}</programlisting></informalexample>
<para>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.</para>
<para>Build and install the ball client after this step, and you will
have a rather nice-looking bouncing ball.</para>
</section>
<section xml:id="libguac-client-ball-time">
<title>Handling the passage of time</title>
<para>Because the <methodname>handle_messages</methodname> 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.</para>
<para>We must modify our ball state to include the time the last update
took place:</para>
<informalexample>
<programlisting xml:id="ball-06-ball_client.h" version="5.0" xml:lang="en">typedef struct ball_client_data {
...
<emphasis>guac_timestamp last_update;</emphasis>
} ball_client_data;</programlisting></informalexample>
<para>Naturally, this new structure member must be initialized within
<methodname>guac_client_init</methodname>:</para>
<informalexample>
<programlisting xml:id="ball-06-ball_client.c" version="5.0" xml:lang="en">int guac_client_init(guac_client* client, int argc, char** argv) {
ball_client_data* data = malloc(sizeof(ball_client_data));
...
<emphasis> data->last_update = guac_protocol_get_timestamp();</emphasis>
...
}</programlisting></informalexample>
<para>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:</para>
<informalexample>
<programlisting version="5.0" xml:lang="en">int ball_client_handle_messages(guac_client* client) {
/* Get data */
ball_client_data* data = (ball_client_data*) client->data;
<emphasis>
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;
</emphasis>
...
<emphasis>
/* Update timestamp */
data->last_update = current;
</emphasis>
return 0;
}</programlisting></informalexample>
<para>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.</para>
</section>
</chapter>